diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 00000000..add2a628 --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "admin-platform", + "runtimeExecutable": "pnpm", + "runtimeArgs": ["--dir", "/Users/hy/Documents/hy/hyapp-admin-platform", "dev", "--port", "7002"], + "port": 7002, + "autoPort": false + } + ] +} diff --git a/.gitignore b/.gitignore index c0661130..88abf702 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ redis-data/ .tmp/ /tmp/ .claude/settings.local.json +backfill-last7 diff --git a/Makefile b/Makefile index 9b0018a5..167d271c 100644 --- a/Makefile +++ b/Makefile @@ -59,6 +59,7 @@ proto: proto/game/v1/game.proto \ proto/robot/v1/robot.proto \ proto/wallet/v1/wallet.proto \ + proto/events/luckygift/v1/events.proto \ proto/events/room/v1/events.proto # `vet` 使用当前 Go 工具链检查 workspace 内所有后端模块,升级 Go 版本时先暴露静态风险。 diff --git a/README.md b/README.md index d2b21520..75e9139a 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,7 @@ docs/flutter对接/Flutter App 通知与钱包余额对接.md 规则: -- `room_outbox` 只由 `room-service` 读取和发布;`wallet_outbox` 只由 `wallet-service` 读取和发布;`user_outbox` 只由 `user-service` 读取和发布;`game_outbox` 只由 `game-service` 读取和发布。 +- `room_outbox` 只由 `room-service` 读取和发布;`wallet_outbox` 只由 `wallet-service` 读取和发布;`user_outbox` 只由 `user-service` 读取和发布;`game_outbox` 只由 `game-service` 读取和发布;`lucky_gift_outbox` 只由 `lucky-gift-service` 读取和发布。 - `statistics-service`、`activity-service`、`notice-service`、`cron-service` 等非 owner service 不能配置 owner 数据库 DSN 或扫描其他服务 outbox 表。 - 每个业务消费者使用独立 consumer group。即使消费同一个 topic,不同业务也不能共用 group,否则会互相抢消息。 - 每个消费者在自己的库内维护幂等事实,例如按 `app_code + event_id` 或业务唯一键建唯一约束;RocketMQ 的消费位点只解决投递进度,不替代业务幂等。 @@ -201,10 +201,14 @@ docs/flutter对接/Flutter App 通知与钱包余额对接.md | `hyapp_wallet_outbox` | `hyapp-wallet-outbox-producer` | `hyapp-activity-red-packet-wallet-outbox` | activity-service 消费 `WalletRedPacketCreated` 并生成区域飘屏 outbox | | `hyapp_wallet_outbox` | `hyapp-wallet-outbox-producer` | `hyapp-notice-wallet-outbox` | notice-service 消费余额、账务等私有通知事实 | | `hyapp_wallet_outbox` | `hyapp-wallet-outbox-producer` | `hyapp-statistics-wallet-outbox` | statistics-service 消费 `WalletRechargeRecorded` 并更新充值聚合 | +| `hyapp_lucky_gift_outbox` | `hyapp-lucky-gift-outbox-producer` | `hyapp-room-lucky-gift-outbox` | room-service 消费已到账的 `LuckyGiftDrawn`,以本地 `room_outbox` 幂等投递房间顶部飘屏 | +| `hyapp_lucky_gift_outbox` | `hyapp-lucky-gift-outbox-producer` | `hyapp-activity-lucky-gift-outbox` | activity-service 消费已到账的 `LuckyGiftDrawn`,以本地 `im_broadcast_outbox` 幂等投递区域大赢家飘屏 | | `hyapp_game_outbox` | `hyapp-game-outbox-producer` | `hyapp-statistics-game-outbox` | statistics-service 消费 `GameOrderSettled` 并更新游戏聚合 | | `hyapp_room_rocket_launch` | `hyapp-room-rocket-launch-producer` | `hyapp-room-rocket-launch` | 火箭点火后到点唤醒 room-service 发射命令 | -RocketMQ 不拥有业务状态。`room_outbox`、`wallet_outbox`、`user_outbox`、`game_outbox` 仍是各 owner service MySQL 内的可靠事实源;MQ 发布失败时 outbox worker 退避重试,到达重试上限后进入死信。火箭延迟消息只负责唤醒,到点后 room-service 会重新校验 `rocket_id`、等级、`launch_at_ms`、状态和 UTC 重置边界。 +RocketMQ 不拥有业务状态。`room_outbox`、`wallet_outbox`、`user_outbox`、`game_outbox`、`lucky_gift_outbox` 仍是各 owner service MySQL 内的可靠事实源;MQ 发布失败时 outbox worker 退避重试,到达重试上限后进入死信。火箭延迟消息只负责唤醒,到点后 room-service 会重新校验 `rocket_id`、等级、`launch_at_ms`、状态和 UTC 重置边界。 + +`hyapp_lucky_gift_outbox` 的两个消费者保持实时副作用所需的 latest offset。首次上线必须先创建 topic/group,并先部署、确认 room/activity 两个 group 已在线订阅,再开启 lucky-gift producer;不能用 `ConsumeFromFirst` 扫历史大奖生成过期飘屏。 ## Storage Model diff --git a/api/proto/activity/v1/activity.pb.go b/api/proto/activity/v1/activity.pb.go index 60b2863d..399ffd34 100644 --- a/api/proto/activity/v1/activity.pb.go +++ b/api/proto/activity/v1/activity.pb.go @@ -13031,19 +13031,26 @@ type LevelRewardJob struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - RewardJobId string `protobuf:"bytes,1,opt,name=reward_job_id,json=rewardJobId,proto3" json:"reward_job_id,omitempty"` - UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - Track string `protobuf:"bytes,3,opt,name=track,proto3" json:"track,omitempty"` - RewardSourceType string `protobuf:"bytes,4,opt,name=reward_source_type,json=rewardSourceType,proto3" json:"reward_source_type,omitempty"` - RewardSourceId string `protobuf:"bytes,5,opt,name=reward_source_id,json=rewardSourceId,proto3" json:"reward_source_id,omitempty"` - ResourceGroupId int64 `protobuf:"varint,6,opt,name=resource_group_id,json=resourceGroupId,proto3" json:"resource_group_id,omitempty"` - WalletCommandId string `protobuf:"bytes,7,opt,name=wallet_command_id,json=walletCommandId,proto3" json:"wallet_command_id,omitempty"` - WalletGrantId string `protobuf:"bytes,8,opt,name=wallet_grant_id,json=walletGrantId,proto3" json:"wallet_grant_id,omitempty"` - Status string `protobuf:"bytes,9,opt,name=status,proto3" json:"status,omitempty"` - AttemptCount int32 `protobuf:"varint,10,opt,name=attempt_count,json=attemptCount,proto3" json:"attempt_count,omitempty"` - FailureReason string `protobuf:"bytes,11,opt,name=failure_reason,json=failureReason,proto3" json:"failure_reason,omitempty"` - CreatedAtMs int64 `protobuf:"varint,12,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` - UpdatedAtMs int64 `protobuf:"varint,13,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + RewardJobId string `protobuf:"bytes,1,opt,name=reward_job_id,json=rewardJobId,proto3" json:"reward_job_id,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Track string `protobuf:"bytes,3,opt,name=track,proto3" json:"track,omitempty"` + RewardSourceType string `protobuf:"bytes,4,opt,name=reward_source_type,json=rewardSourceType,proto3" json:"reward_source_type,omitempty"` + RewardSourceId string `protobuf:"bytes,5,opt,name=reward_source_id,json=rewardSourceId,proto3" json:"reward_source_id,omitempty"` + ResourceGroupId int64 `protobuf:"varint,6,opt,name=resource_group_id,json=resourceGroupId,proto3" json:"resource_group_id,omitempty"` + WalletCommandId string `protobuf:"bytes,7,opt,name=wallet_command_id,json=walletCommandId,proto3" json:"wallet_command_id,omitempty"` + WalletGrantId string `protobuf:"bytes,8,opt,name=wallet_grant_id,json=walletGrantId,proto3" json:"wallet_grant_id,omitempty"` + Status string `protobuf:"bytes,9,opt,name=status,proto3" json:"status,omitempty"` + AttemptCount int32 `protobuf:"varint,10,opt,name=attempt_count,json=attemptCount,proto3" json:"attempt_count,omitempty"` + FailureReason string `protobuf:"bytes,11,opt,name=failure_reason,json=failureReason,proto3" json:"failure_reason,omitempty"` + CreatedAtMs int64 `protobuf:"varint,12,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,13,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + RewardOrigin string `protobuf:"bytes,14,opt,name=reward_origin,json=rewardOrigin,proto3" json:"reward_origin,omitempty"` + TemporaryLevelId string `protobuf:"bytes,15,opt,name=temporary_level_id,json=temporaryLevelId,proto3" json:"temporary_level_id,omitempty"` + GrantGeneration int32 `protobuf:"varint,16,opt,name=grant_generation,json=grantGeneration,proto3" json:"grant_generation,omitempty"` + RewardLevel int32 `protobuf:"varint,17,opt,name=reward_level,json=rewardLevel,proto3" json:"reward_level,omitempty"` + Permanent bool `protobuf:"varint,18,opt,name=permanent,proto3" json:"permanent,omitempty"` + TemporaryExpiresAtMs int64 `protobuf:"varint,19,opt,name=temporary_expires_at_ms,json=temporaryExpiresAtMs,proto3" json:"temporary_expires_at_ms,omitempty"` + RevokeStatus string `protobuf:"bytes,20,opt,name=revoke_status,json=revokeStatus,proto3" json:"revoke_status,omitempty"` } func (x *LevelRewardJob) Reset() { @@ -13167,6 +13174,55 @@ func (x *LevelRewardJob) GetUpdatedAtMs() int64 { return 0 } +func (x *LevelRewardJob) GetRewardOrigin() string { + if x != nil { + return x.RewardOrigin + } + return "" +} + +func (x *LevelRewardJob) GetTemporaryLevelId() string { + if x != nil { + return x.TemporaryLevelId + } + return "" +} + +func (x *LevelRewardJob) GetGrantGeneration() int32 { + if x != nil { + return x.GrantGeneration + } + return 0 +} + +func (x *LevelRewardJob) GetRewardLevel() int32 { + if x != nil { + return x.RewardLevel + } + return 0 +} + +func (x *LevelRewardJob) GetPermanent() bool { + if x != nil { + return x.Permanent + } + return false +} + +func (x *LevelRewardJob) GetTemporaryExpiresAtMs() int64 { + if x != nil { + return x.TemporaryExpiresAtMs + } + return 0 +} + +func (x *LevelRewardJob) GetRevokeStatus() string { + if x != nil { + return x.RevokeStatus + } + return "" +} + type ListLevelRewardsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -13710,6 +13766,475 @@ func (x *SetUserLevelResponse) GetServerTimeMs() int64 { return 0 } +// AdminLevelTrackProfile keeps the permanent account and the currently effective display overlay separate. +type AdminLevelTrackProfile struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Track string `protobuf:"bytes,1,opt,name=track,proto3" json:"track,omitempty"` + RealLevel int32 `protobuf:"varint,2,opt,name=real_level,json=realLevel,proto3" json:"real_level,omitempty"` + RealTotalValue int64 `protobuf:"varint,3,opt,name=real_total_value,json=realTotalValue,proto3" json:"real_total_value,omitempty"` + DisplayLevel int32 `protobuf:"varint,4,opt,name=display_level,json=displayLevel,proto3" json:"display_level,omitempty"` + DisplayValue int64 `protobuf:"varint,5,opt,name=display_value,json=displayValue,proto3" json:"display_value,omitempty"` + TemporaryLevelId string `protobuf:"bytes,6,opt,name=temporary_level_id,json=temporaryLevelId,proto3" json:"temporary_level_id,omitempty"` + TemporaryTargetLevel int32 `protobuf:"varint,7,opt,name=temporary_target_level,json=temporaryTargetLevel,proto3" json:"temporary_target_level,omitempty"` + StartedAtMs int64 `protobuf:"varint,8,opt,name=started_at_ms,json=startedAtMs,proto3" json:"started_at_ms,omitempty"` + ExpiresAtMs int64 `protobuf:"varint,9,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` +} + +func (x *AdminLevelTrackProfile) Reset() { + *x = AdminLevelTrackProfile{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[171] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdminLevelTrackProfile) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdminLevelTrackProfile) ProtoMessage() {} + +func (x *AdminLevelTrackProfile) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[171] + 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 AdminLevelTrackProfile.ProtoReflect.Descriptor instead. +func (*AdminLevelTrackProfile) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{171} +} + +func (x *AdminLevelTrackProfile) GetTrack() string { + if x != nil { + return x.Track + } + return "" +} + +func (x *AdminLevelTrackProfile) GetRealLevel() int32 { + if x != nil { + return x.RealLevel + } + return 0 +} + +func (x *AdminLevelTrackProfile) GetRealTotalValue() int64 { + if x != nil { + return x.RealTotalValue + } + return 0 +} + +func (x *AdminLevelTrackProfile) GetDisplayLevel() int32 { + if x != nil { + return x.DisplayLevel + } + return 0 +} + +func (x *AdminLevelTrackProfile) GetDisplayValue() int64 { + if x != nil { + return x.DisplayValue + } + return 0 +} + +func (x *AdminLevelTrackProfile) GetTemporaryLevelId() string { + if x != nil { + return x.TemporaryLevelId + } + return "" +} + +func (x *AdminLevelTrackProfile) GetTemporaryTargetLevel() int32 { + if x != nil { + return x.TemporaryTargetLevel + } + return 0 +} + +func (x *AdminLevelTrackProfile) GetStartedAtMs() int64 { + if x != nil { + return x.StartedAtMs + } + return 0 +} + +func (x *AdminLevelTrackProfile) GetExpiresAtMs() int64 { + if x != nil { + return x.ExpiresAtMs + } + return 0 +} + +type AdminUserLevelProfile struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Tracks []*AdminLevelTrackProfile `protobuf:"bytes,2,rep,name=tracks,proto3" json:"tracks,omitempty"` +} + +func (x *AdminUserLevelProfile) Reset() { + *x = AdminUserLevelProfile{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[172] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdminUserLevelProfile) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdminUserLevelProfile) ProtoMessage() {} + +func (x *AdminUserLevelProfile) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[172] + 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 AdminUserLevelProfile.ProtoReflect.Descriptor instead. +func (*AdminUserLevelProfile) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{172} +} + +func (x *AdminUserLevelProfile) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *AdminUserLevelProfile) GetTracks() []*AdminLevelTrackProfile { + if x != nil { + return x.Tracks + } + return nil +} + +type BatchGetUserLevelAdminProfilesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + UserIds []int64 `protobuf:"varint,2,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"` +} + +func (x *BatchGetUserLevelAdminProfilesRequest) Reset() { + *x = BatchGetUserLevelAdminProfilesRequest{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[173] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchGetUserLevelAdminProfilesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchGetUserLevelAdminProfilesRequest) ProtoMessage() {} + +func (x *BatchGetUserLevelAdminProfilesRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[173] + 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 BatchGetUserLevelAdminProfilesRequest.ProtoReflect.Descriptor instead. +func (*BatchGetUserLevelAdminProfilesRequest) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{173} +} + +func (x *BatchGetUserLevelAdminProfilesRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *BatchGetUserLevelAdminProfilesRequest) GetUserIds() []int64 { + if x != nil { + return x.UserIds + } + return nil +} + +type BatchGetUserLevelAdminProfilesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Profiles []*AdminUserLevelProfile `protobuf:"bytes,1,rep,name=profiles,proto3" json:"profiles,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *BatchGetUserLevelAdminProfilesResponse) Reset() { + *x = BatchGetUserLevelAdminProfilesResponse{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[174] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchGetUserLevelAdminProfilesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchGetUserLevelAdminProfilesResponse) ProtoMessage() {} + +func (x *BatchGetUserLevelAdminProfilesResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[174] + 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 BatchGetUserLevelAdminProfilesResponse.ProtoReflect.Descriptor instead. +func (*BatchGetUserLevelAdminProfilesResponse) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{174} +} + +func (x *BatchGetUserLevelAdminProfilesResponse) GetProfiles() []*AdminUserLevelProfile { + if x != nil { + return x.Profiles + } + return nil +} + +func (x *BatchGetUserLevelAdminProfilesResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +// TemporaryLevelAdjustment is limited to wealth/charm; duration_days must be positive and level is 1..50. +type TemporaryLevelAdjustment struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Track string `protobuf:"bytes,1,opt,name=track,proto3" json:"track,omitempty"` + Level int32 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"` + DurationDays int32 `protobuf:"varint,3,opt,name=duration_days,json=durationDays,proto3" json:"duration_days,omitempty"` +} + +func (x *TemporaryLevelAdjustment) Reset() { + *x = TemporaryLevelAdjustment{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[175] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TemporaryLevelAdjustment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TemporaryLevelAdjustment) ProtoMessage() {} + +func (x *TemporaryLevelAdjustment) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[175] + 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 TemporaryLevelAdjustment.ProtoReflect.Descriptor instead. +func (*TemporaryLevelAdjustment) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{175} +} + +func (x *TemporaryLevelAdjustment) GetTrack() string { + if x != nil { + return x.Track + } + return "" +} + +func (x *TemporaryLevelAdjustment) GetLevel() int32 { + if x != nil { + return x.Level + } + return 0 +} + +func (x *TemporaryLevelAdjustment) GetDurationDays() int32 { + if x != nil { + return x.DurationDays + } + return 0 +} + +type AdjustTemporaryUserLevelsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + CommandId string `protobuf:"bytes,2,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Adjustments []*TemporaryLevelAdjustment `protobuf:"bytes,4,rep,name=adjustments,proto3" json:"adjustments,omitempty"` + OperatorAdminId int64 `protobuf:"varint,5,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"` + Reason string `protobuf:"bytes,6,opt,name=reason,proto3" json:"reason,omitempty"` +} + +func (x *AdjustTemporaryUserLevelsRequest) Reset() { + *x = AdjustTemporaryUserLevelsRequest{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[176] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdjustTemporaryUserLevelsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdjustTemporaryUserLevelsRequest) ProtoMessage() {} + +func (x *AdjustTemporaryUserLevelsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[176] + 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 AdjustTemporaryUserLevelsRequest.ProtoReflect.Descriptor instead. +func (*AdjustTemporaryUserLevelsRequest) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{176} +} + +func (x *AdjustTemporaryUserLevelsRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *AdjustTemporaryUserLevelsRequest) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *AdjustTemporaryUserLevelsRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *AdjustTemporaryUserLevelsRequest) GetAdjustments() []*TemporaryLevelAdjustment { + if x != nil { + return x.Adjustments + } + return nil +} + +func (x *AdjustTemporaryUserLevelsRequest) GetOperatorAdminId() int64 { + if x != nil { + return x.OperatorAdminId + } + return 0 +} + +func (x *AdjustTemporaryUserLevelsRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +type AdjustTemporaryUserLevelsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Profile *AdminUserLevelProfile `protobuf:"bytes,1,opt,name=profile,proto3" json:"profile,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *AdjustTemporaryUserLevelsResponse) Reset() { + *x = AdjustTemporaryUserLevelsResponse{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[177] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdjustTemporaryUserLevelsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdjustTemporaryUserLevelsResponse) ProtoMessage() {} + +func (x *AdjustTemporaryUserLevelsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[177] + 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 AdjustTemporaryUserLevelsResponse.ProtoReflect.Descriptor instead. +func (*AdjustTemporaryUserLevelsResponse) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{177} +} + +func (x *AdjustTemporaryUserLevelsResponse) GetProfile() *AdminUserLevelProfile { + if x != nil { + return x.Profile + } + return nil +} + +func (x *AdjustTemporaryUserLevelsResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + type RegistrationLevelBadgeGrant struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -13723,7 +14248,7 @@ type RegistrationLevelBadgeGrant struct { func (x *RegistrationLevelBadgeGrant) Reset() { *x = RegistrationLevelBadgeGrant{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[171] + mi := &file_proto_activity_v1_activity_proto_msgTypes[178] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13735,7 +14260,7 @@ func (x *RegistrationLevelBadgeGrant) String() string { func (*RegistrationLevelBadgeGrant) ProtoMessage() {} func (x *RegistrationLevelBadgeGrant) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[171] + mi := &file_proto_activity_v1_activity_proto_msgTypes[178] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13748,7 +14273,7 @@ func (x *RegistrationLevelBadgeGrant) ProtoReflect() protoreflect.Message { // Deprecated: Use RegistrationLevelBadgeGrant.ProtoReflect.Descriptor instead. func (*RegistrationLevelBadgeGrant) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{171} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{178} } func (x *RegistrationLevelBadgeGrant) GetTrack() string { @@ -13791,7 +14316,7 @@ type IssueRegistrationLevelBadgesRequest struct { func (x *IssueRegistrationLevelBadgesRequest) Reset() { *x = IssueRegistrationLevelBadgesRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[172] + mi := &file_proto_activity_v1_activity_proto_msgTypes[179] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13803,7 +14328,7 @@ func (x *IssueRegistrationLevelBadgesRequest) String() string { func (*IssueRegistrationLevelBadgesRequest) ProtoMessage() {} func (x *IssueRegistrationLevelBadgesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[172] + mi := &file_proto_activity_v1_activity_proto_msgTypes[179] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13816,7 +14341,7 @@ func (x *IssueRegistrationLevelBadgesRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use IssueRegistrationLevelBadgesRequest.ProtoReflect.Descriptor instead. func (*IssueRegistrationLevelBadgesRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{172} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{179} } func (x *IssueRegistrationLevelBadgesRequest) GetMeta() *RequestMeta { @@ -13851,7 +14376,7 @@ type IssueRegistrationLevelBadgesResponse struct { func (x *IssueRegistrationLevelBadgesResponse) Reset() { *x = IssueRegistrationLevelBadgesResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[173] + mi := &file_proto_activity_v1_activity_proto_msgTypes[180] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13863,7 +14388,7 @@ func (x *IssueRegistrationLevelBadgesResponse) String() string { func (*IssueRegistrationLevelBadgesResponse) ProtoMessage() {} func (x *IssueRegistrationLevelBadgesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[173] + mi := &file_proto_activity_v1_activity_proto_msgTypes[180] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13876,7 +14401,7 @@ func (x *IssueRegistrationLevelBadgesResponse) ProtoReflect() protoreflect.Messa // Deprecated: Use IssueRegistrationLevelBadgesResponse.ProtoReflect.Descriptor instead. func (*IssueRegistrationLevelBadgesResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{173} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{180} } func (x *IssueRegistrationLevelBadgesResponse) GetGrants() []*RegistrationLevelBadgeGrant { @@ -13908,7 +14433,7 @@ type UpsertLevelTrackRequest struct { func (x *UpsertLevelTrackRequest) Reset() { *x = UpsertLevelTrackRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[174] + mi := &file_proto_activity_v1_activity_proto_msgTypes[181] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13920,7 +14445,7 @@ func (x *UpsertLevelTrackRequest) String() string { func (*UpsertLevelTrackRequest) ProtoMessage() {} func (x *UpsertLevelTrackRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[174] + mi := &file_proto_activity_v1_activity_proto_msgTypes[181] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13933,7 +14458,7 @@ func (x *UpsertLevelTrackRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelTrackRequest.ProtoReflect.Descriptor instead. func (*UpsertLevelTrackRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{174} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{181} } func (x *UpsertLevelTrackRequest) GetMeta() *RequestMeta { @@ -13989,7 +14514,7 @@ type UpsertLevelTrackResponse struct { func (x *UpsertLevelTrackResponse) Reset() { *x = UpsertLevelTrackResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[175] + mi := &file_proto_activity_v1_activity_proto_msgTypes[182] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14001,7 +14526,7 @@ func (x *UpsertLevelTrackResponse) String() string { func (*UpsertLevelTrackResponse) ProtoMessage() {} func (x *UpsertLevelTrackResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[175] + mi := &file_proto_activity_v1_activity_proto_msgTypes[182] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14014,7 +14539,7 @@ func (x *UpsertLevelTrackResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelTrackResponse.ProtoReflect.Descriptor instead. func (*UpsertLevelTrackResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{175} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{182} } func (x *UpsertLevelTrackResponse) GetTrack() *LevelTrack { @@ -14050,7 +14575,7 @@ type UpsertLevelRuleRequest struct { func (x *UpsertLevelRuleRequest) Reset() { *x = UpsertLevelRuleRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[176] + mi := &file_proto_activity_v1_activity_proto_msgTypes[183] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14062,7 +14587,7 @@ func (x *UpsertLevelRuleRequest) String() string { func (*UpsertLevelRuleRequest) ProtoMessage() {} func (x *UpsertLevelRuleRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[176] + mi := &file_proto_activity_v1_activity_proto_msgTypes[183] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14075,7 +14600,7 @@ func (x *UpsertLevelRuleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelRuleRequest.ProtoReflect.Descriptor instead. func (*UpsertLevelRuleRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{176} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{183} } func (x *UpsertLevelRuleRequest) GetMeta() *RequestMeta { @@ -14159,7 +14684,7 @@ type UpsertLevelRuleResponse struct { func (x *UpsertLevelRuleResponse) Reset() { *x = UpsertLevelRuleResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[177] + mi := &file_proto_activity_v1_activity_proto_msgTypes[184] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14171,7 +14696,7 @@ func (x *UpsertLevelRuleResponse) String() string { func (*UpsertLevelRuleResponse) ProtoMessage() {} func (x *UpsertLevelRuleResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[177] + mi := &file_proto_activity_v1_activity_proto_msgTypes[184] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14184,7 +14709,7 @@ func (x *UpsertLevelRuleResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelRuleResponse.ProtoReflect.Descriptor instead. func (*UpsertLevelRuleResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{177} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{184} } func (x *UpsertLevelRuleResponse) GetRule() *LevelRule { @@ -14222,7 +14747,7 @@ type UpsertLevelTierRequest struct { func (x *UpsertLevelTierRequest) Reset() { *x = UpsertLevelTierRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[178] + mi := &file_proto_activity_v1_activity_proto_msgTypes[185] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14234,7 +14759,7 @@ func (x *UpsertLevelTierRequest) String() string { func (*UpsertLevelTierRequest) ProtoMessage() {} func (x *UpsertLevelTierRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[178] + mi := &file_proto_activity_v1_activity_proto_msgTypes[185] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14247,7 +14772,7 @@ func (x *UpsertLevelTierRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelTierRequest.ProtoReflect.Descriptor instead. func (*UpsertLevelTierRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{178} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{185} } func (x *UpsertLevelTierRequest) GetMeta() *RequestMeta { @@ -14345,7 +14870,7 @@ type UpsertLevelTierResponse struct { func (x *UpsertLevelTierResponse) Reset() { *x = UpsertLevelTierResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[179] + mi := &file_proto_activity_v1_activity_proto_msgTypes[186] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14357,7 +14882,7 @@ func (x *UpsertLevelTierResponse) String() string { func (*UpsertLevelTierResponse) ProtoMessage() {} func (x *UpsertLevelTierResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[179] + mi := &file_proto_activity_v1_activity_proto_msgTypes[186] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14370,7 +14895,7 @@ func (x *UpsertLevelTierResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelTierResponse.ProtoReflect.Descriptor instead. func (*UpsertLevelTierResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{179} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{186} } func (x *UpsertLevelTierResponse) GetTier() *LevelTier { @@ -14400,7 +14925,7 @@ type ListLevelConfigRequest struct { func (x *ListLevelConfigRequest) Reset() { *x = ListLevelConfigRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[180] + mi := &file_proto_activity_v1_activity_proto_msgTypes[187] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14412,7 +14937,7 @@ func (x *ListLevelConfigRequest) String() string { func (*ListLevelConfigRequest) ProtoMessage() {} func (x *ListLevelConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[180] + mi := &file_proto_activity_v1_activity_proto_msgTypes[187] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14425,7 +14950,7 @@ func (x *ListLevelConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLevelConfigRequest.ProtoReflect.Descriptor instead. func (*ListLevelConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{180} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{187} } func (x *ListLevelConfigRequest) GetMeta() *RequestMeta { @@ -14462,7 +14987,7 @@ type ListLevelConfigResponse struct { func (x *ListLevelConfigResponse) Reset() { *x = ListLevelConfigResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[181] + mi := &file_proto_activity_v1_activity_proto_msgTypes[188] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14474,7 +14999,7 @@ func (x *ListLevelConfigResponse) String() string { func (*ListLevelConfigResponse) ProtoMessage() {} func (x *ListLevelConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[181] + mi := &file_proto_activity_v1_activity_proto_msgTypes[188] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14487,7 +15012,7 @@ func (x *ListLevelConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLevelConfigResponse.ProtoReflect.Descriptor instead. func (*ListLevelConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{181} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{188} } func (x *ListLevelConfigResponse) GetTracks() []*LevelTrack { @@ -14534,7 +15059,7 @@ type AchievementCondition struct { func (x *AchievementCondition) Reset() { *x = AchievementCondition{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[182] + mi := &file_proto_activity_v1_activity_proto_msgTypes[189] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14546,7 +15071,7 @@ func (x *AchievementCondition) String() string { func (*AchievementCondition) ProtoMessage() {} func (x *AchievementCondition) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[182] + mi := &file_proto_activity_v1_activity_proto_msgTypes[189] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14559,7 +15084,7 @@ func (x *AchievementCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use AchievementCondition.ProtoReflect.Descriptor instead. func (*AchievementCondition) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{182} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{189} } func (x *AchievementCondition) GetConditionId() string { @@ -14641,7 +15166,7 @@ type AchievementDefinition struct { func (x *AchievementDefinition) Reset() { *x = AchievementDefinition{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[183] + mi := &file_proto_activity_v1_activity_proto_msgTypes[190] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14653,7 +15178,7 @@ func (x *AchievementDefinition) String() string { func (*AchievementDefinition) ProtoMessage() {} func (x *AchievementDefinition) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[183] + mi := &file_proto_activity_v1_activity_proto_msgTypes[190] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14666,7 +15191,7 @@ func (x *AchievementDefinition) ProtoReflect() protoreflect.Message { // Deprecated: Use AchievementDefinition.ProtoReflect.Descriptor instead. func (*AchievementDefinition) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{183} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{190} } func (x *AchievementDefinition) GetAchievementId() string { @@ -14832,7 +15357,7 @@ type UserAchievement struct { func (x *UserAchievement) Reset() { *x = UserAchievement{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[184] + mi := &file_proto_activity_v1_activity_proto_msgTypes[191] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14844,7 +15369,7 @@ func (x *UserAchievement) String() string { func (*UserAchievement) ProtoMessage() {} func (x *UserAchievement) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[184] + mi := &file_proto_activity_v1_activity_proto_msgTypes[191] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14857,7 +15382,7 @@ func (x *UserAchievement) ProtoReflect() protoreflect.Message { // Deprecated: Use UserAchievement.ProtoReflect.Descriptor instead. func (*UserAchievement) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{184} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{191} } func (x *UserAchievement) GetDefinition() *AchievementDefinition { @@ -14924,7 +15449,7 @@ type ListAchievementsRequest struct { func (x *ListAchievementsRequest) Reset() { *x = ListAchievementsRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[185] + mi := &file_proto_activity_v1_activity_proto_msgTypes[192] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14936,7 +15461,7 @@ func (x *ListAchievementsRequest) String() string { func (*ListAchievementsRequest) ProtoMessage() {} func (x *ListAchievementsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[185] + mi := &file_proto_activity_v1_activity_proto_msgTypes[192] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14949,7 +15474,7 @@ func (x *ListAchievementsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAchievementsRequest.ProtoReflect.Descriptor instead. func (*ListAchievementsRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{185} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{192} } func (x *ListAchievementsRequest) GetMeta() *RequestMeta { @@ -15006,7 +15531,7 @@ type ListAchievementsResponse struct { func (x *ListAchievementsResponse) Reset() { *x = ListAchievementsResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[186] + mi := &file_proto_activity_v1_activity_proto_msgTypes[193] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15018,7 +15543,7 @@ func (x *ListAchievementsResponse) String() string { func (*ListAchievementsResponse) ProtoMessage() {} func (x *ListAchievementsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[186] + mi := &file_proto_activity_v1_activity_proto_msgTypes[193] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15031,7 +15556,7 @@ func (x *ListAchievementsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAchievementsResponse.ProtoReflect.Descriptor instead. func (*ListAchievementsResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{186} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{193} } func (x *ListAchievementsResponse) GetAchievements() []*UserAchievement { @@ -15073,7 +15598,7 @@ type ConsumeAchievementEventRequest struct { func (x *ConsumeAchievementEventRequest) Reset() { *x = ConsumeAchievementEventRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[187] + mi := &file_proto_activity_v1_activity_proto_msgTypes[194] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15085,7 +15610,7 @@ func (x *ConsumeAchievementEventRequest) String() string { func (*ConsumeAchievementEventRequest) ProtoMessage() {} func (x *ConsumeAchievementEventRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[187] + mi := &file_proto_activity_v1_activity_proto_msgTypes[194] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15098,7 +15623,7 @@ func (x *ConsumeAchievementEventRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConsumeAchievementEventRequest.ProtoReflect.Descriptor instead. func (*ConsumeAchievementEventRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{187} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{194} } func (x *ConsumeAchievementEventRequest) GetMeta() *RequestMeta { @@ -15178,7 +15703,7 @@ type ConsumeAchievementEventResponse struct { func (x *ConsumeAchievementEventResponse) Reset() { *x = ConsumeAchievementEventResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[188] + mi := &file_proto_activity_v1_activity_proto_msgTypes[195] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15190,7 +15715,7 @@ func (x *ConsumeAchievementEventResponse) String() string { func (*ConsumeAchievementEventResponse) ProtoMessage() {} func (x *ConsumeAchievementEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[188] + mi := &file_proto_activity_v1_activity_proto_msgTypes[195] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15203,7 +15728,7 @@ func (x *ConsumeAchievementEventResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConsumeAchievementEventResponse.ProtoReflect.Descriptor instead. func (*ConsumeAchievementEventResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{188} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{195} } func (x *ConsumeAchievementEventResponse) GetEventId() string { @@ -15259,7 +15784,7 @@ type BadgeDisplayItem struct { func (x *BadgeDisplayItem) Reset() { *x = BadgeDisplayItem{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[189] + mi := &file_proto_activity_v1_activity_proto_msgTypes[196] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15271,7 +15796,7 @@ func (x *BadgeDisplayItem) String() string { func (*BadgeDisplayItem) ProtoMessage() {} func (x *BadgeDisplayItem) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[189] + mi := &file_proto_activity_v1_activity_proto_msgTypes[196] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15284,7 +15809,7 @@ func (x *BadgeDisplayItem) ProtoReflect() protoreflect.Message { // Deprecated: Use BadgeDisplayItem.ProtoReflect.Descriptor instead. func (*BadgeDisplayItem) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{189} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{196} } func (x *BadgeDisplayItem) GetSlot() string { @@ -15361,7 +15886,7 @@ type ListMyBadgesRequest struct { func (x *ListMyBadgesRequest) Reset() { *x = ListMyBadgesRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[190] + mi := &file_proto_activity_v1_activity_proto_msgTypes[197] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15373,7 +15898,7 @@ func (x *ListMyBadgesRequest) String() string { func (*ListMyBadgesRequest) ProtoMessage() {} func (x *ListMyBadgesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[190] + mi := &file_proto_activity_v1_activity_proto_msgTypes[197] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15386,7 +15911,7 @@ func (x *ListMyBadgesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListMyBadgesRequest.ProtoReflect.Descriptor instead. func (*ListMyBadgesRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{190} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{197} } func (x *ListMyBadgesRequest) GetMeta() *RequestMeta { @@ -15416,7 +15941,7 @@ type ListMyBadgesResponse struct { func (x *ListMyBadgesResponse) Reset() { *x = ListMyBadgesResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[191] + mi := &file_proto_activity_v1_activity_proto_msgTypes[198] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15428,7 +15953,7 @@ func (x *ListMyBadgesResponse) String() string { func (*ListMyBadgesResponse) ProtoMessage() {} func (x *ListMyBadgesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[191] + mi := &file_proto_activity_v1_activity_proto_msgTypes[198] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15441,7 +15966,7 @@ func (x *ListMyBadgesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListMyBadgesResponse.ProtoReflect.Descriptor instead. func (*ListMyBadgesResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{191} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{198} } func (x *ListMyBadgesResponse) GetStripBadges() []*BadgeDisplayItem { @@ -15486,7 +16011,7 @@ type SetBadgeDisplayRequest struct { func (x *SetBadgeDisplayRequest) Reset() { *x = SetBadgeDisplayRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[192] + mi := &file_proto_activity_v1_activity_proto_msgTypes[199] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15498,7 +16023,7 @@ func (x *SetBadgeDisplayRequest) String() string { func (*SetBadgeDisplayRequest) ProtoMessage() {} func (x *SetBadgeDisplayRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[192] + mi := &file_proto_activity_v1_activity_proto_msgTypes[199] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15511,7 +16036,7 @@ func (x *SetBadgeDisplayRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetBadgeDisplayRequest.ProtoReflect.Descriptor instead. func (*SetBadgeDisplayRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{192} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{199} } func (x *SetBadgeDisplayRequest) GetMeta() *RequestMeta { @@ -15559,7 +16084,7 @@ type SetBadgeDisplayResponse struct { func (x *SetBadgeDisplayResponse) Reset() { *x = SetBadgeDisplayResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[193] + mi := &file_proto_activity_v1_activity_proto_msgTypes[200] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15571,7 +16096,7 @@ func (x *SetBadgeDisplayResponse) String() string { func (*SetBadgeDisplayResponse) ProtoMessage() {} func (x *SetBadgeDisplayResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[193] + mi := &file_proto_activity_v1_activity_proto_msgTypes[200] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15584,7 +16109,7 @@ func (x *SetBadgeDisplayResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetBadgeDisplayResponse.ProtoReflect.Descriptor instead. func (*SetBadgeDisplayResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{193} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{200} } func (x *SetBadgeDisplayResponse) GetProfile() *ListMyBadgesResponse { @@ -15621,7 +16146,7 @@ type UpsertAchievementDefinitionRequest struct { func (x *UpsertAchievementDefinitionRequest) Reset() { *x = UpsertAchievementDefinitionRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[194] + mi := &file_proto_activity_v1_activity_proto_msgTypes[201] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15633,7 +16158,7 @@ func (x *UpsertAchievementDefinitionRequest) String() string { func (*UpsertAchievementDefinitionRequest) ProtoMessage() {} func (x *UpsertAchievementDefinitionRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[194] + mi := &file_proto_activity_v1_activity_proto_msgTypes[201] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15646,7 +16171,7 @@ func (x *UpsertAchievementDefinitionRequest) ProtoReflect() protoreflect.Message // Deprecated: Use UpsertAchievementDefinitionRequest.ProtoReflect.Descriptor instead. func (*UpsertAchievementDefinitionRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{194} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{201} } func (x *UpsertAchievementDefinitionRequest) GetMeta() *RequestMeta { @@ -15786,7 +16311,7 @@ type UpsertAchievementDefinitionResponse struct { func (x *UpsertAchievementDefinitionResponse) Reset() { *x = UpsertAchievementDefinitionResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[195] + mi := &file_proto_activity_v1_activity_proto_msgTypes[202] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15798,7 +16323,7 @@ func (x *UpsertAchievementDefinitionResponse) String() string { func (*UpsertAchievementDefinitionResponse) ProtoMessage() {} func (x *UpsertAchievementDefinitionResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[195] + mi := &file_proto_activity_v1_activity_proto_msgTypes[202] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15811,7 +16336,7 @@ func (x *UpsertAchievementDefinitionResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use UpsertAchievementDefinitionResponse.ProtoReflect.Descriptor instead. func (*UpsertAchievementDefinitionResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{195} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{202} } func (x *UpsertAchievementDefinitionResponse) GetAchievement() *AchievementDefinition { @@ -15840,7 +16365,7 @@ type DeleteAchievementDefinitionRequest struct { func (x *DeleteAchievementDefinitionRequest) Reset() { *x = DeleteAchievementDefinitionRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[196] + mi := &file_proto_activity_v1_activity_proto_msgTypes[203] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15852,7 +16377,7 @@ func (x *DeleteAchievementDefinitionRequest) String() string { func (*DeleteAchievementDefinitionRequest) ProtoMessage() {} func (x *DeleteAchievementDefinitionRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[196] + mi := &file_proto_activity_v1_activity_proto_msgTypes[203] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15865,7 +16390,7 @@ func (x *DeleteAchievementDefinitionRequest) ProtoReflect() protoreflect.Message // Deprecated: Use DeleteAchievementDefinitionRequest.ProtoReflect.Descriptor instead. func (*DeleteAchievementDefinitionRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{196} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{203} } func (x *DeleteAchievementDefinitionRequest) GetMeta() *RequestMeta { @@ -15899,7 +16424,7 @@ type DeleteAchievementDefinitionResponse struct { func (x *DeleteAchievementDefinitionResponse) Reset() { *x = DeleteAchievementDefinitionResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[197] + mi := &file_proto_activity_v1_activity_proto_msgTypes[204] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15911,7 +16436,7 @@ func (x *DeleteAchievementDefinitionResponse) String() string { func (*DeleteAchievementDefinitionResponse) ProtoMessage() {} func (x *DeleteAchievementDefinitionResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[197] + mi := &file_proto_activity_v1_activity_proto_msgTypes[204] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15924,7 +16449,7 @@ func (x *DeleteAchievementDefinitionResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use DeleteAchievementDefinitionResponse.ProtoReflect.Descriptor instead. func (*DeleteAchievementDefinitionResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{197} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{204} } func (x *DeleteAchievementDefinitionResponse) GetAchievement() *AchievementDefinition { @@ -15953,7 +16478,7 @@ type WheelPrizeTier struct { func (x *WheelPrizeTier) Reset() { *x = WheelPrizeTier{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[198] + mi := &file_proto_activity_v1_activity_proto_msgTypes[205] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15965,7 +16490,7 @@ func (x *WheelPrizeTier) String() string { func (*WheelPrizeTier) ProtoMessage() {} func (x *WheelPrizeTier) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[198] + mi := &file_proto_activity_v1_activity_proto_msgTypes[205] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15978,7 +16503,7 @@ func (x *WheelPrizeTier) ProtoReflect() protoreflect.Message { // Deprecated: Use WheelPrizeTier.ProtoReflect.Descriptor instead. func (*WheelPrizeTier) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{198} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{205} } func (x *WheelPrizeTier) GetTierId() string { @@ -16075,7 +16600,7 @@ type WheelRuleConfig struct { func (x *WheelRuleConfig) Reset() { *x = WheelRuleConfig{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[199] + mi := &file_proto_activity_v1_activity_proto_msgTypes[206] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16087,7 +16612,7 @@ func (x *WheelRuleConfig) String() string { func (*WheelRuleConfig) ProtoMessage() {} func (x *WheelRuleConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[199] + mi := &file_proto_activity_v1_activity_proto_msgTypes[206] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16100,7 +16625,7 @@ func (x *WheelRuleConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use WheelRuleConfig.ProtoReflect.Descriptor instead. func (*WheelRuleConfig) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{199} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{206} } func (x *WheelRuleConfig) GetAppCode() string { @@ -16226,7 +16751,7 @@ type WheelDrawMeta struct { func (x *WheelDrawMeta) Reset() { *x = WheelDrawMeta{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[200] + mi := &file_proto_activity_v1_activity_proto_msgTypes[207] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16238,7 +16763,7 @@ func (x *WheelDrawMeta) String() string { func (*WheelDrawMeta) ProtoMessage() {} func (x *WheelDrawMeta) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[200] + mi := &file_proto_activity_v1_activity_proto_msgTypes[207] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16251,7 +16776,7 @@ func (x *WheelDrawMeta) ProtoReflect() protoreflect.Message { // Deprecated: Use WheelDrawMeta.ProtoReflect.Descriptor instead. func (*WheelDrawMeta) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{200} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{207} } func (x *WheelDrawMeta) GetMeta() *RequestMeta { @@ -16345,7 +16870,7 @@ type WheelDrawResult struct { func (x *WheelDrawResult) Reset() { *x = WheelDrawResult{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[201] + mi := &file_proto_activity_v1_activity_proto_msgTypes[208] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16357,7 +16882,7 @@ func (x *WheelDrawResult) String() string { func (*WheelDrawResult) ProtoMessage() {} func (x *WheelDrawResult) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[201] + mi := &file_proto_activity_v1_activity_proto_msgTypes[208] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16370,7 +16895,7 @@ func (x *WheelDrawResult) ProtoReflect() protoreflect.Message { // Deprecated: Use WheelDrawResult.ProtoReflect.Descriptor instead. func (*WheelDrawResult) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{201} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{208} } func (x *WheelDrawResult) GetDrawId() string { @@ -16523,7 +17048,7 @@ type WheelDrawReward struct { func (x *WheelDrawReward) Reset() { *x = WheelDrawReward{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[202] + mi := &file_proto_activity_v1_activity_proto_msgTypes[209] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16535,7 +17060,7 @@ func (x *WheelDrawReward) String() string { func (*WheelDrawReward) ProtoMessage() {} func (x *WheelDrawReward) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[202] + mi := &file_proto_activity_v1_activity_proto_msgTypes[209] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16548,7 +17073,7 @@ func (x *WheelDrawReward) ProtoReflect() protoreflect.Message { // Deprecated: Use WheelDrawReward.ProtoReflect.Descriptor instead. func (*WheelDrawReward) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{202} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{209} } func (x *WheelDrawReward) GetSelectedTierId() string { @@ -16617,7 +17142,7 @@ type ExecuteWheelDrawRequest struct { func (x *ExecuteWheelDrawRequest) Reset() { *x = ExecuteWheelDrawRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[203] + mi := &file_proto_activity_v1_activity_proto_msgTypes[210] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16629,7 +17154,7 @@ func (x *ExecuteWheelDrawRequest) String() string { func (*ExecuteWheelDrawRequest) ProtoMessage() {} func (x *ExecuteWheelDrawRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[203] + mi := &file_proto_activity_v1_activity_proto_msgTypes[210] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16642,7 +17167,7 @@ func (x *ExecuteWheelDrawRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecuteWheelDrawRequest.ProtoReflect.Descriptor instead. func (*ExecuteWheelDrawRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{203} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{210} } func (x *ExecuteWheelDrawRequest) GetWheel() *WheelDrawMeta { @@ -16662,7 +17187,7 @@ type ExecuteWheelDrawResponse struct { func (x *ExecuteWheelDrawResponse) Reset() { *x = ExecuteWheelDrawResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[204] + mi := &file_proto_activity_v1_activity_proto_msgTypes[211] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16674,7 +17199,7 @@ func (x *ExecuteWheelDrawResponse) String() string { func (*ExecuteWheelDrawResponse) ProtoMessage() {} func (x *ExecuteWheelDrawResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[204] + mi := &file_proto_activity_v1_activity_proto_msgTypes[211] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16687,7 +17212,7 @@ func (x *ExecuteWheelDrawResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecuteWheelDrawResponse.ProtoReflect.Descriptor instead. func (*ExecuteWheelDrawResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{204} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{211} } func (x *ExecuteWheelDrawResponse) GetResult() *WheelDrawResult { @@ -16708,7 +17233,7 @@ type GetWheelConfigRequest struct { func (x *GetWheelConfigRequest) Reset() { *x = GetWheelConfigRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[205] + mi := &file_proto_activity_v1_activity_proto_msgTypes[212] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16720,7 +17245,7 @@ func (x *GetWheelConfigRequest) String() string { func (*GetWheelConfigRequest) ProtoMessage() {} func (x *GetWheelConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[205] + mi := &file_proto_activity_v1_activity_proto_msgTypes[212] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16733,7 +17258,7 @@ func (x *GetWheelConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWheelConfigRequest.ProtoReflect.Descriptor instead. func (*GetWheelConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{205} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{212} } func (x *GetWheelConfigRequest) GetMeta() *RequestMeta { @@ -16760,7 +17285,7 @@ type GetWheelConfigResponse struct { func (x *GetWheelConfigResponse) Reset() { *x = GetWheelConfigResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[206] + mi := &file_proto_activity_v1_activity_proto_msgTypes[213] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16772,7 +17297,7 @@ func (x *GetWheelConfigResponse) String() string { func (*GetWheelConfigResponse) ProtoMessage() {} func (x *GetWheelConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[206] + mi := &file_proto_activity_v1_activity_proto_msgTypes[213] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16785,7 +17310,7 @@ func (x *GetWheelConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWheelConfigResponse.ProtoReflect.Descriptor instead. func (*GetWheelConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{206} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{213} } func (x *GetWheelConfigResponse) GetConfig() *WheelRuleConfig { @@ -16807,7 +17332,7 @@ type UpsertWheelConfigRequest struct { func (x *UpsertWheelConfigRequest) Reset() { *x = UpsertWheelConfigRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[207] + mi := &file_proto_activity_v1_activity_proto_msgTypes[214] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16819,7 +17344,7 @@ func (x *UpsertWheelConfigRequest) String() string { func (*UpsertWheelConfigRequest) ProtoMessage() {} func (x *UpsertWheelConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[207] + mi := &file_proto_activity_v1_activity_proto_msgTypes[214] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16832,7 +17357,7 @@ func (x *UpsertWheelConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertWheelConfigRequest.ProtoReflect.Descriptor instead. func (*UpsertWheelConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{207} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{214} } func (x *UpsertWheelConfigRequest) GetMeta() *RequestMeta { @@ -16866,7 +17391,7 @@ type UpsertWheelConfigResponse struct { func (x *UpsertWheelConfigResponse) Reset() { *x = UpsertWheelConfigResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[208] + mi := &file_proto_activity_v1_activity_proto_msgTypes[215] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16878,7 +17403,7 @@ func (x *UpsertWheelConfigResponse) String() string { func (*UpsertWheelConfigResponse) ProtoMessage() {} func (x *UpsertWheelConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[208] + mi := &file_proto_activity_v1_activity_proto_msgTypes[215] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16891,7 +17416,7 @@ func (x *UpsertWheelConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertWheelConfigResponse.ProtoReflect.Descriptor instead. func (*UpsertWheelConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{208} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{215} } func (x *UpsertWheelConfigResponse) GetConfig() *WheelRuleConfig { @@ -16916,7 +17441,7 @@ type ListWheelDrawsRequest struct { func (x *ListWheelDrawsRequest) Reset() { *x = ListWheelDrawsRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[209] + mi := &file_proto_activity_v1_activity_proto_msgTypes[216] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16928,7 +17453,7 @@ func (x *ListWheelDrawsRequest) String() string { func (*ListWheelDrawsRequest) ProtoMessage() {} func (x *ListWheelDrawsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[209] + mi := &file_proto_activity_v1_activity_proto_msgTypes[216] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16941,7 +17466,7 @@ func (x *ListWheelDrawsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWheelDrawsRequest.ProtoReflect.Descriptor instead. func (*ListWheelDrawsRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{209} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{216} } func (x *ListWheelDrawsRequest) GetMeta() *RequestMeta { @@ -16997,7 +17522,7 @@ type ListWheelDrawsResponse struct { func (x *ListWheelDrawsResponse) Reset() { *x = ListWheelDrawsResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[210] + mi := &file_proto_activity_v1_activity_proto_msgTypes[217] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17009,7 +17534,7 @@ func (x *ListWheelDrawsResponse) String() string { func (*ListWheelDrawsResponse) ProtoMessage() {} func (x *ListWheelDrawsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[210] + mi := &file_proto_activity_v1_activity_proto_msgTypes[217] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17022,7 +17547,7 @@ func (x *ListWheelDrawsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWheelDrawsResponse.ProtoReflect.Descriptor instead. func (*ListWheelDrawsResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{210} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{217} } func (x *ListWheelDrawsResponse) GetDraws() []*WheelDrawResult { @@ -17057,7 +17582,7 @@ type WheelDrawSummary struct { func (x *WheelDrawSummary) Reset() { *x = WheelDrawSummary{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[211] + mi := &file_proto_activity_v1_activity_proto_msgTypes[218] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17069,7 +17594,7 @@ func (x *WheelDrawSummary) String() string { func (*WheelDrawSummary) ProtoMessage() {} func (x *WheelDrawSummary) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[211] + mi := &file_proto_activity_v1_activity_proto_msgTypes[218] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17082,7 +17607,7 @@ func (x *WheelDrawSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use WheelDrawSummary.ProtoReflect.Descriptor instead. func (*WheelDrawSummary) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{211} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{218} } func (x *WheelDrawSummary) GetWheelId() string { @@ -17161,7 +17686,7 @@ type GetWheelDrawSummaryRequest struct { func (x *GetWheelDrawSummaryRequest) Reset() { *x = GetWheelDrawSummaryRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[212] + mi := &file_proto_activity_v1_activity_proto_msgTypes[219] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17173,7 +17698,7 @@ func (x *GetWheelDrawSummaryRequest) String() string { func (*GetWheelDrawSummaryRequest) ProtoMessage() {} func (x *GetWheelDrawSummaryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[212] + mi := &file_proto_activity_v1_activity_proto_msgTypes[219] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17186,7 +17711,7 @@ func (x *GetWheelDrawSummaryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWheelDrawSummaryRequest.ProtoReflect.Descriptor instead. func (*GetWheelDrawSummaryRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{212} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{219} } func (x *GetWheelDrawSummaryRequest) GetMeta() *RequestMeta { @@ -17227,7 +17752,7 @@ type GetWheelDrawSummaryResponse struct { func (x *GetWheelDrawSummaryResponse) Reset() { *x = GetWheelDrawSummaryResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[213] + mi := &file_proto_activity_v1_activity_proto_msgTypes[220] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17239,7 +17764,7 @@ func (x *GetWheelDrawSummaryResponse) String() string { func (*GetWheelDrawSummaryResponse) ProtoMessage() {} func (x *GetWheelDrawSummaryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[213] + mi := &file_proto_activity_v1_activity_proto_msgTypes[220] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17252,7 +17777,7 @@ func (x *GetWheelDrawSummaryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWheelDrawSummaryResponse.ProtoReflect.Descriptor instead. func (*GetWheelDrawSummaryResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{213} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{220} } func (x *GetWheelDrawSummaryResponse) GetSummary() *WheelDrawSummary { @@ -17274,7 +17799,7 @@ type WeeklyStarGift struct { func (x *WeeklyStarGift) Reset() { *x = WeeklyStarGift{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[214] + mi := &file_proto_activity_v1_activity_proto_msgTypes[221] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17286,7 +17811,7 @@ func (x *WeeklyStarGift) String() string { func (*WeeklyStarGift) ProtoMessage() {} func (x *WeeklyStarGift) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[214] + mi := &file_proto_activity_v1_activity_proto_msgTypes[221] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17299,7 +17824,7 @@ func (x *WeeklyStarGift) ProtoReflect() protoreflect.Message { // Deprecated: Use WeeklyStarGift.ProtoReflect.Descriptor instead. func (*WeeklyStarGift) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{214} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{221} } func (x *WeeklyStarGift) GetGiftId() string { @@ -17328,7 +17853,7 @@ type WeeklyStarReward struct { func (x *WeeklyStarReward) Reset() { *x = WeeklyStarReward{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[215] + mi := &file_proto_activity_v1_activity_proto_msgTypes[222] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17340,7 +17865,7 @@ func (x *WeeklyStarReward) String() string { func (*WeeklyStarReward) ProtoMessage() {} func (x *WeeklyStarReward) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[215] + mi := &file_proto_activity_v1_activity_proto_msgTypes[222] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17353,7 +17878,7 @@ func (x *WeeklyStarReward) ProtoReflect() protoreflect.Message { // Deprecated: Use WeeklyStarReward.ProtoReflect.Descriptor instead. func (*WeeklyStarReward) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{215} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{222} } func (x *WeeklyStarReward) GetRankNo() int32 { @@ -17395,7 +17920,7 @@ type WeeklyStarCycle struct { func (x *WeeklyStarCycle) Reset() { *x = WeeklyStarCycle{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[216] + mi := &file_proto_activity_v1_activity_proto_msgTypes[223] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17407,7 +17932,7 @@ func (x *WeeklyStarCycle) String() string { func (*WeeklyStarCycle) ProtoMessage() {} func (x *WeeklyStarCycle) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[216] + mi := &file_proto_activity_v1_activity_proto_msgTypes[223] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17420,7 +17945,7 @@ func (x *WeeklyStarCycle) ProtoReflect() protoreflect.Message { // Deprecated: Use WeeklyStarCycle.ProtoReflect.Descriptor instead. func (*WeeklyStarCycle) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{216} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{223} } func (x *WeeklyStarCycle) GetCycleId() string { @@ -17543,7 +18068,7 @@ type WeeklyStarLeaderboardEntry struct { func (x *WeeklyStarLeaderboardEntry) Reset() { *x = WeeklyStarLeaderboardEntry{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[217] + mi := &file_proto_activity_v1_activity_proto_msgTypes[224] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17555,7 +18080,7 @@ func (x *WeeklyStarLeaderboardEntry) String() string { func (*WeeklyStarLeaderboardEntry) ProtoMessage() {} func (x *WeeklyStarLeaderboardEntry) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[217] + mi := &file_proto_activity_v1_activity_proto_msgTypes[224] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17568,7 +18093,7 @@ func (x *WeeklyStarLeaderboardEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use WeeklyStarLeaderboardEntry.ProtoReflect.Descriptor instead. func (*WeeklyStarLeaderboardEntry) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{217} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{224} } func (x *WeeklyStarLeaderboardEntry) GetRankNo() int32 { @@ -17629,7 +18154,7 @@ type WeeklyStarSettlement struct { func (x *WeeklyStarSettlement) Reset() { *x = WeeklyStarSettlement{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[218] + mi := &file_proto_activity_v1_activity_proto_msgTypes[225] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17641,7 +18166,7 @@ func (x *WeeklyStarSettlement) String() string { func (*WeeklyStarSettlement) ProtoMessage() {} func (x *WeeklyStarSettlement) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[218] + mi := &file_proto_activity_v1_activity_proto_msgTypes[225] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17654,7 +18179,7 @@ func (x *WeeklyStarSettlement) ProtoReflect() protoreflect.Message { // Deprecated: Use WeeklyStarSettlement.ProtoReflect.Descriptor instead. func (*WeeklyStarSettlement) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{218} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{225} } func (x *WeeklyStarSettlement) GetSettlementId() string { @@ -17764,7 +18289,7 @@ type ListWeeklyStarCyclesRequest struct { func (x *ListWeeklyStarCyclesRequest) Reset() { *x = ListWeeklyStarCyclesRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[219] + mi := &file_proto_activity_v1_activity_proto_msgTypes[226] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17776,7 +18301,7 @@ func (x *ListWeeklyStarCyclesRequest) String() string { func (*ListWeeklyStarCyclesRequest) ProtoMessage() {} func (x *ListWeeklyStarCyclesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[219] + mi := &file_proto_activity_v1_activity_proto_msgTypes[226] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17789,7 +18314,7 @@ func (x *ListWeeklyStarCyclesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWeeklyStarCyclesRequest.ProtoReflect.Descriptor instead. func (*ListWeeklyStarCyclesRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{219} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{226} } func (x *ListWeeklyStarCyclesRequest) GetMeta() *RequestMeta { @@ -17852,7 +18377,7 @@ type ListWeeklyStarCyclesResponse struct { func (x *ListWeeklyStarCyclesResponse) Reset() { *x = ListWeeklyStarCyclesResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[220] + mi := &file_proto_activity_v1_activity_proto_msgTypes[227] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17864,7 +18389,7 @@ func (x *ListWeeklyStarCyclesResponse) String() string { func (*ListWeeklyStarCyclesResponse) ProtoMessage() {} func (x *ListWeeklyStarCyclesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[220] + mi := &file_proto_activity_v1_activity_proto_msgTypes[227] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17877,7 +18402,7 @@ func (x *ListWeeklyStarCyclesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWeeklyStarCyclesResponse.ProtoReflect.Descriptor instead. func (*ListWeeklyStarCyclesResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{220} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{227} } func (x *ListWeeklyStarCyclesResponse) GetCycles() []*WeeklyStarCycle { @@ -17905,7 +18430,7 @@ type GetWeeklyStarCycleRequest struct { func (x *GetWeeklyStarCycleRequest) Reset() { *x = GetWeeklyStarCycleRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[221] + mi := &file_proto_activity_v1_activity_proto_msgTypes[228] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17917,7 +18442,7 @@ func (x *GetWeeklyStarCycleRequest) String() string { func (*GetWeeklyStarCycleRequest) ProtoMessage() {} func (x *GetWeeklyStarCycleRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[221] + mi := &file_proto_activity_v1_activity_proto_msgTypes[228] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17930,7 +18455,7 @@ func (x *GetWeeklyStarCycleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWeeklyStarCycleRequest.ProtoReflect.Descriptor instead. func (*GetWeeklyStarCycleRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{221} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{228} } func (x *GetWeeklyStarCycleRequest) GetMeta() *RequestMeta { @@ -17957,7 +18482,7 @@ type GetWeeklyStarCycleResponse struct { func (x *GetWeeklyStarCycleResponse) Reset() { *x = GetWeeklyStarCycleResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[222] + mi := &file_proto_activity_v1_activity_proto_msgTypes[229] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17969,7 +18494,7 @@ func (x *GetWeeklyStarCycleResponse) String() string { func (*GetWeeklyStarCycleResponse) ProtoMessage() {} func (x *GetWeeklyStarCycleResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[222] + mi := &file_proto_activity_v1_activity_proto_msgTypes[229] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17982,7 +18507,7 @@ func (x *GetWeeklyStarCycleResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWeeklyStarCycleResponse.ProtoReflect.Descriptor instead. func (*GetWeeklyStarCycleResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{222} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{229} } func (x *GetWeeklyStarCycleResponse) GetCycle() *WeeklyStarCycle { @@ -18004,7 +18529,7 @@ type UpsertWeeklyStarCycleRequest struct { func (x *UpsertWeeklyStarCycleRequest) Reset() { *x = UpsertWeeklyStarCycleRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[223] + mi := &file_proto_activity_v1_activity_proto_msgTypes[230] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18016,7 +18541,7 @@ func (x *UpsertWeeklyStarCycleRequest) String() string { func (*UpsertWeeklyStarCycleRequest) ProtoMessage() {} func (x *UpsertWeeklyStarCycleRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[223] + mi := &file_proto_activity_v1_activity_proto_msgTypes[230] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18029,7 +18554,7 @@ func (x *UpsertWeeklyStarCycleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertWeeklyStarCycleRequest.ProtoReflect.Descriptor instead. func (*UpsertWeeklyStarCycleRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{223} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{230} } func (x *UpsertWeeklyStarCycleRequest) GetMeta() *RequestMeta { @@ -18064,7 +18589,7 @@ type UpsertWeeklyStarCycleResponse struct { func (x *UpsertWeeklyStarCycleResponse) Reset() { *x = UpsertWeeklyStarCycleResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[224] + mi := &file_proto_activity_v1_activity_proto_msgTypes[231] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18076,7 +18601,7 @@ func (x *UpsertWeeklyStarCycleResponse) String() string { func (*UpsertWeeklyStarCycleResponse) ProtoMessage() {} func (x *UpsertWeeklyStarCycleResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[224] + mi := &file_proto_activity_v1_activity_proto_msgTypes[231] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18089,7 +18614,7 @@ func (x *UpsertWeeklyStarCycleResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertWeeklyStarCycleResponse.ProtoReflect.Descriptor instead. func (*UpsertWeeklyStarCycleResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{224} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{231} } func (x *UpsertWeeklyStarCycleResponse) GetCycle() *WeeklyStarCycle { @@ -18119,7 +18644,7 @@ type SetWeeklyStarCycleStatusRequest struct { func (x *SetWeeklyStarCycleStatusRequest) Reset() { *x = SetWeeklyStarCycleStatusRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[225] + mi := &file_proto_activity_v1_activity_proto_msgTypes[232] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18131,7 +18656,7 @@ func (x *SetWeeklyStarCycleStatusRequest) String() string { func (*SetWeeklyStarCycleStatusRequest) ProtoMessage() {} func (x *SetWeeklyStarCycleStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[225] + mi := &file_proto_activity_v1_activity_proto_msgTypes[232] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18144,7 +18669,7 @@ func (x *SetWeeklyStarCycleStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetWeeklyStarCycleStatusRequest.ProtoReflect.Descriptor instead. func (*SetWeeklyStarCycleStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{225} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{232} } func (x *SetWeeklyStarCycleStatusRequest) GetMeta() *RequestMeta { @@ -18185,7 +18710,7 @@ type SetWeeklyStarCycleStatusResponse struct { func (x *SetWeeklyStarCycleStatusResponse) Reset() { *x = SetWeeklyStarCycleStatusResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[226] + mi := &file_proto_activity_v1_activity_proto_msgTypes[233] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18197,7 +18722,7 @@ func (x *SetWeeklyStarCycleStatusResponse) String() string { func (*SetWeeklyStarCycleStatusResponse) ProtoMessage() {} func (x *SetWeeklyStarCycleStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[226] + mi := &file_proto_activity_v1_activity_proto_msgTypes[233] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18210,7 +18735,7 @@ func (x *SetWeeklyStarCycleStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetWeeklyStarCycleStatusResponse.ProtoReflect.Descriptor instead. func (*SetWeeklyStarCycleStatusResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{226} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{233} } func (x *SetWeeklyStarCycleStatusResponse) GetCycle() *WeeklyStarCycle { @@ -18234,7 +18759,7 @@ type ListWeeklyStarLeaderboardRequest struct { func (x *ListWeeklyStarLeaderboardRequest) Reset() { *x = ListWeeklyStarLeaderboardRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[227] + mi := &file_proto_activity_v1_activity_proto_msgTypes[234] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18246,7 +18771,7 @@ func (x *ListWeeklyStarLeaderboardRequest) String() string { func (*ListWeeklyStarLeaderboardRequest) ProtoMessage() {} func (x *ListWeeklyStarLeaderboardRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[227] + mi := &file_proto_activity_v1_activity_proto_msgTypes[234] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18259,7 +18784,7 @@ func (x *ListWeeklyStarLeaderboardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWeeklyStarLeaderboardRequest.ProtoReflect.Descriptor instead. func (*ListWeeklyStarLeaderboardRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{227} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{234} } func (x *ListWeeklyStarLeaderboardRequest) GetMeta() *RequestMeta { @@ -18310,7 +18835,7 @@ type ListWeeklyStarLeaderboardResponse struct { func (x *ListWeeklyStarLeaderboardResponse) Reset() { *x = ListWeeklyStarLeaderboardResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[228] + mi := &file_proto_activity_v1_activity_proto_msgTypes[235] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18322,7 +18847,7 @@ func (x *ListWeeklyStarLeaderboardResponse) String() string { func (*ListWeeklyStarLeaderboardResponse) ProtoMessage() {} func (x *ListWeeklyStarLeaderboardResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[228] + mi := &file_proto_activity_v1_activity_proto_msgTypes[235] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18335,7 +18860,7 @@ func (x *ListWeeklyStarLeaderboardResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ListWeeklyStarLeaderboardResponse.ProtoReflect.Descriptor instead. func (*ListWeeklyStarLeaderboardResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{228} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{235} } func (x *ListWeeklyStarLeaderboardResponse) GetCycle() *WeeklyStarCycle { @@ -18377,7 +18902,7 @@ type ListWeeklyStarSettlementsRequest struct { func (x *ListWeeklyStarSettlementsRequest) Reset() { *x = ListWeeklyStarSettlementsRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[229] + mi := &file_proto_activity_v1_activity_proto_msgTypes[236] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18389,7 +18914,7 @@ func (x *ListWeeklyStarSettlementsRequest) String() string { func (*ListWeeklyStarSettlementsRequest) ProtoMessage() {} func (x *ListWeeklyStarSettlementsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[229] + mi := &file_proto_activity_v1_activity_proto_msgTypes[236] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18402,7 +18927,7 @@ func (x *ListWeeklyStarSettlementsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWeeklyStarSettlementsRequest.ProtoReflect.Descriptor instead. func (*ListWeeklyStarSettlementsRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{229} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{236} } func (x *ListWeeklyStarSettlementsRequest) GetMeta() *RequestMeta { @@ -18429,7 +18954,7 @@ type ListWeeklyStarSettlementsResponse struct { func (x *ListWeeklyStarSettlementsResponse) Reset() { *x = ListWeeklyStarSettlementsResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[230] + mi := &file_proto_activity_v1_activity_proto_msgTypes[237] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18441,7 +18966,7 @@ func (x *ListWeeklyStarSettlementsResponse) String() string { func (*ListWeeklyStarSettlementsResponse) ProtoMessage() {} func (x *ListWeeklyStarSettlementsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[230] + mi := &file_proto_activity_v1_activity_proto_msgTypes[237] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18454,7 +18979,7 @@ func (x *ListWeeklyStarSettlementsResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ListWeeklyStarSettlementsResponse.ProtoReflect.Descriptor instead. func (*ListWeeklyStarSettlementsResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{230} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{237} } func (x *ListWeeklyStarSettlementsResponse) GetSettlements() []*WeeklyStarSettlement { @@ -18476,7 +19001,7 @@ type GetWeeklyStarCurrentRequest struct { func (x *GetWeeklyStarCurrentRequest) Reset() { *x = GetWeeklyStarCurrentRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[231] + mi := &file_proto_activity_v1_activity_proto_msgTypes[238] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18488,7 +19013,7 @@ func (x *GetWeeklyStarCurrentRequest) String() string { func (*GetWeeklyStarCurrentRequest) ProtoMessage() {} func (x *GetWeeklyStarCurrentRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[231] + mi := &file_proto_activity_v1_activity_proto_msgTypes[238] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18501,7 +19026,7 @@ func (x *GetWeeklyStarCurrentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWeeklyStarCurrentRequest.ProtoReflect.Descriptor instead. func (*GetWeeklyStarCurrentRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{231} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{238} } func (x *GetWeeklyStarCurrentRequest) GetMeta() *RequestMeta { @@ -18538,7 +19063,7 @@ type GetWeeklyStarCurrentResponse struct { func (x *GetWeeklyStarCurrentResponse) Reset() { *x = GetWeeklyStarCurrentResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[232] + mi := &file_proto_activity_v1_activity_proto_msgTypes[239] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18550,7 +19075,7 @@ func (x *GetWeeklyStarCurrentResponse) String() string { func (*GetWeeklyStarCurrentResponse) ProtoMessage() {} func (x *GetWeeklyStarCurrentResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[232] + mi := &file_proto_activity_v1_activity_proto_msgTypes[239] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18563,7 +19088,7 @@ func (x *GetWeeklyStarCurrentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWeeklyStarCurrentResponse.ProtoReflect.Descriptor instead. func (*GetWeeklyStarCurrentResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{232} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{239} } func (x *GetWeeklyStarCurrentResponse) GetCycle() *WeeklyStarCycle { @@ -18606,7 +19131,7 @@ type ListWeeklyStarHistoryRequest struct { func (x *ListWeeklyStarHistoryRequest) Reset() { *x = ListWeeklyStarHistoryRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[233] + mi := &file_proto_activity_v1_activity_proto_msgTypes[240] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18618,7 +19143,7 @@ func (x *ListWeeklyStarHistoryRequest) String() string { func (*ListWeeklyStarHistoryRequest) ProtoMessage() {} func (x *ListWeeklyStarHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[233] + mi := &file_proto_activity_v1_activity_proto_msgTypes[240] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18631,7 +19156,7 @@ func (x *ListWeeklyStarHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWeeklyStarHistoryRequest.ProtoReflect.Descriptor instead. func (*ListWeeklyStarHistoryRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{233} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{240} } func (x *ListWeeklyStarHistoryRequest) GetMeta() *RequestMeta { @@ -18666,7 +19191,7 @@ type WeeklyStarHistoryCycle struct { func (x *WeeklyStarHistoryCycle) Reset() { *x = WeeklyStarHistoryCycle{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[234] + mi := &file_proto_activity_v1_activity_proto_msgTypes[241] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18678,7 +19203,7 @@ func (x *WeeklyStarHistoryCycle) String() string { func (*WeeklyStarHistoryCycle) ProtoMessage() {} func (x *WeeklyStarHistoryCycle) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[234] + mi := &file_proto_activity_v1_activity_proto_msgTypes[241] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18691,7 +19216,7 @@ func (x *WeeklyStarHistoryCycle) ProtoReflect() protoreflect.Message { // Deprecated: Use WeeklyStarHistoryCycle.ProtoReflect.Descriptor instead. func (*WeeklyStarHistoryCycle) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{234} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{241} } func (x *WeeklyStarHistoryCycle) GetCycle() *WeeklyStarCycle { @@ -18719,7 +19244,7 @@ type ListWeeklyStarHistoryResponse struct { func (x *ListWeeklyStarHistoryResponse) Reset() { *x = ListWeeklyStarHistoryResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[235] + mi := &file_proto_activity_v1_activity_proto_msgTypes[242] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18731,7 +19256,7 @@ func (x *ListWeeklyStarHistoryResponse) String() string { func (*ListWeeklyStarHistoryResponse) ProtoMessage() {} func (x *ListWeeklyStarHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[235] + mi := &file_proto_activity_v1_activity_proto_msgTypes[242] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18744,7 +19269,7 @@ func (x *ListWeeklyStarHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWeeklyStarHistoryResponse.ProtoReflect.Descriptor instead. func (*ListWeeklyStarHistoryResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{235} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{242} } func (x *ListWeeklyStarHistoryResponse) GetCycles() []*WeeklyStarHistoryCycle { @@ -18773,7 +19298,7 @@ type CPWeeklyRankReward struct { func (x *CPWeeklyRankReward) Reset() { *x = CPWeeklyRankReward{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[236] + mi := &file_proto_activity_v1_activity_proto_msgTypes[243] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18785,7 +19310,7 @@ func (x *CPWeeklyRankReward) String() string { func (*CPWeeklyRankReward) ProtoMessage() {} func (x *CPWeeklyRankReward) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[236] + mi := &file_proto_activity_v1_activity_proto_msgTypes[243] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18798,7 +19323,7 @@ func (x *CPWeeklyRankReward) ProtoReflect() protoreflect.Message { // Deprecated: Use CPWeeklyRankReward.ProtoReflect.Descriptor instead. func (*CPWeeklyRankReward) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{236} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{243} } func (x *CPWeeklyRankReward) GetRankNo() int32 { @@ -18834,7 +19359,7 @@ type CPWeeklyRankConfig struct { func (x *CPWeeklyRankConfig) Reset() { *x = CPWeeklyRankConfig{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[237] + mi := &file_proto_activity_v1_activity_proto_msgTypes[244] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18846,7 +19371,7 @@ func (x *CPWeeklyRankConfig) String() string { func (*CPWeeklyRankConfig) ProtoMessage() {} func (x *CPWeeklyRankConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[237] + mi := &file_proto_activity_v1_activity_proto_msgTypes[244] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18859,7 +19384,7 @@ func (x *CPWeeklyRankConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use CPWeeklyRankConfig.ProtoReflect.Descriptor instead. func (*CPWeeklyRankConfig) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{237} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{244} } func (x *CPWeeklyRankConfig) GetActivityCode() string { @@ -18939,7 +19464,7 @@ type CPWeeklyRankUser struct { func (x *CPWeeklyRankUser) Reset() { *x = CPWeeklyRankUser{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[238] + mi := &file_proto_activity_v1_activity_proto_msgTypes[245] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18951,7 +19476,7 @@ func (x *CPWeeklyRankUser) String() string { func (*CPWeeklyRankUser) ProtoMessage() {} func (x *CPWeeklyRankUser) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[238] + mi := &file_proto_activity_v1_activity_proto_msgTypes[245] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18964,7 +19489,7 @@ func (x *CPWeeklyRankUser) ProtoReflect() protoreflect.Message { // Deprecated: Use CPWeeklyRankUser.ProtoReflect.Descriptor instead. func (*CPWeeklyRankUser) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{238} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{245} } func (x *CPWeeklyRankUser) GetUserId() int64 { @@ -19015,7 +19540,7 @@ type CPWeeklyRankEntry struct { func (x *CPWeeklyRankEntry) Reset() { *x = CPWeeklyRankEntry{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[239] + mi := &file_proto_activity_v1_activity_proto_msgTypes[246] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19027,7 +19552,7 @@ func (x *CPWeeklyRankEntry) String() string { func (*CPWeeklyRankEntry) ProtoMessage() {} func (x *CPWeeklyRankEntry) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[239] + mi := &file_proto_activity_v1_activity_proto_msgTypes[246] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19040,7 +19565,7 @@ func (x *CPWeeklyRankEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use CPWeeklyRankEntry.ProtoReflect.Descriptor instead. func (*CPWeeklyRankEntry) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{239} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{246} } func (x *CPWeeklyRankEntry) GetRank() int64 { @@ -19138,7 +19663,7 @@ type CPWeeklyRankSettlement struct { func (x *CPWeeklyRankSettlement) Reset() { *x = CPWeeklyRankSettlement{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[240] + mi := &file_proto_activity_v1_activity_proto_msgTypes[247] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19150,7 +19675,7 @@ func (x *CPWeeklyRankSettlement) String() string { func (*CPWeeklyRankSettlement) ProtoMessage() {} func (x *CPWeeklyRankSettlement) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[240] + mi := &file_proto_activity_v1_activity_proto_msgTypes[247] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19163,7 +19688,7 @@ func (x *CPWeeklyRankSettlement) ProtoReflect() protoreflect.Message { // Deprecated: Use CPWeeklyRankSettlement.ProtoReflect.Descriptor instead. func (*CPWeeklyRankSettlement) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{240} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{247} } func (x *CPWeeklyRankSettlement) GetSettlementId() string { @@ -19283,7 +19808,7 @@ type GetCPWeeklyRankStatusRequest struct { func (x *GetCPWeeklyRankStatusRequest) Reset() { *x = GetCPWeeklyRankStatusRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[241] + mi := &file_proto_activity_v1_activity_proto_msgTypes[248] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19295,7 +19820,7 @@ func (x *GetCPWeeklyRankStatusRequest) String() string { func (*GetCPWeeklyRankStatusRequest) ProtoMessage() {} func (x *GetCPWeeklyRankStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[241] + mi := &file_proto_activity_v1_activity_proto_msgTypes[248] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19308,7 +19833,7 @@ func (x *GetCPWeeklyRankStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCPWeeklyRankStatusRequest.ProtoReflect.Descriptor instead. func (*GetCPWeeklyRankStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{241} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{248} } func (x *GetCPWeeklyRankStatusRequest) GetMeta() *RequestMeta { @@ -19347,7 +19872,7 @@ type GetCPWeeklyRankStatusResponse struct { func (x *GetCPWeeklyRankStatusResponse) Reset() { *x = GetCPWeeklyRankStatusResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[242] + mi := &file_proto_activity_v1_activity_proto_msgTypes[249] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19359,7 +19884,7 @@ func (x *GetCPWeeklyRankStatusResponse) String() string { func (*GetCPWeeklyRankStatusResponse) ProtoMessage() {} func (x *GetCPWeeklyRankStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[242] + mi := &file_proto_activity_v1_activity_proto_msgTypes[249] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19372,7 +19897,7 @@ func (x *GetCPWeeklyRankStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCPWeeklyRankStatusResponse.ProtoReflect.Descriptor instead. func (*GetCPWeeklyRankStatusResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{242} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{249} } func (x *GetCPWeeklyRankStatusResponse) GetConfig() *CPWeeklyRankConfig { @@ -19427,7 +19952,7 @@ type GetCPWeeklyRankConfigRequest struct { func (x *GetCPWeeklyRankConfigRequest) Reset() { *x = GetCPWeeklyRankConfigRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[243] + mi := &file_proto_activity_v1_activity_proto_msgTypes[250] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19439,7 +19964,7 @@ func (x *GetCPWeeklyRankConfigRequest) String() string { func (*GetCPWeeklyRankConfigRequest) ProtoMessage() {} func (x *GetCPWeeklyRankConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[243] + mi := &file_proto_activity_v1_activity_proto_msgTypes[250] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19452,7 +19977,7 @@ func (x *GetCPWeeklyRankConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCPWeeklyRankConfigRequest.ProtoReflect.Descriptor instead. func (*GetCPWeeklyRankConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{243} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{250} } func (x *GetCPWeeklyRankConfigRequest) GetMeta() *RequestMeta { @@ -19472,7 +19997,7 @@ type GetCPWeeklyRankConfigResponse struct { func (x *GetCPWeeklyRankConfigResponse) Reset() { *x = GetCPWeeklyRankConfigResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[244] + mi := &file_proto_activity_v1_activity_proto_msgTypes[251] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19484,7 +20009,7 @@ func (x *GetCPWeeklyRankConfigResponse) String() string { func (*GetCPWeeklyRankConfigResponse) ProtoMessage() {} func (x *GetCPWeeklyRankConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[244] + mi := &file_proto_activity_v1_activity_proto_msgTypes[251] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19497,7 +20022,7 @@ func (x *GetCPWeeklyRankConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCPWeeklyRankConfigResponse.ProtoReflect.Descriptor instead. func (*GetCPWeeklyRankConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{244} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{251} } func (x *GetCPWeeklyRankConfigResponse) GetConfig() *CPWeeklyRankConfig { @@ -19519,7 +20044,7 @@ type UpdateCPWeeklyRankConfigRequest struct { func (x *UpdateCPWeeklyRankConfigRequest) Reset() { *x = UpdateCPWeeklyRankConfigRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[245] + mi := &file_proto_activity_v1_activity_proto_msgTypes[252] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19531,7 +20056,7 @@ func (x *UpdateCPWeeklyRankConfigRequest) String() string { func (*UpdateCPWeeklyRankConfigRequest) ProtoMessage() {} func (x *UpdateCPWeeklyRankConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[245] + mi := &file_proto_activity_v1_activity_proto_msgTypes[252] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19544,7 +20069,7 @@ func (x *UpdateCPWeeklyRankConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateCPWeeklyRankConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateCPWeeklyRankConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{245} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{252} } func (x *UpdateCPWeeklyRankConfigRequest) GetMeta() *RequestMeta { @@ -19578,7 +20103,7 @@ type UpdateCPWeeklyRankConfigResponse struct { func (x *UpdateCPWeeklyRankConfigResponse) Reset() { *x = UpdateCPWeeklyRankConfigResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[246] + mi := &file_proto_activity_v1_activity_proto_msgTypes[253] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19590,7 +20115,7 @@ func (x *UpdateCPWeeklyRankConfigResponse) String() string { func (*UpdateCPWeeklyRankConfigResponse) ProtoMessage() {} func (x *UpdateCPWeeklyRankConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[246] + mi := &file_proto_activity_v1_activity_proto_msgTypes[253] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19603,7 +20128,7 @@ func (x *UpdateCPWeeklyRankConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateCPWeeklyRankConfigResponse.ProtoReflect.Descriptor instead. func (*UpdateCPWeeklyRankConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{246} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{253} } func (x *UpdateCPWeeklyRankConfigResponse) GetConfig() *CPWeeklyRankConfig { @@ -19625,7 +20150,7 @@ type ListCPWeeklyRankSettlementsRequest struct { func (x *ListCPWeeklyRankSettlementsRequest) Reset() { *x = ListCPWeeklyRankSettlementsRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[247] + mi := &file_proto_activity_v1_activity_proto_msgTypes[254] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19637,7 +20162,7 @@ func (x *ListCPWeeklyRankSettlementsRequest) String() string { func (*ListCPWeeklyRankSettlementsRequest) ProtoMessage() {} func (x *ListCPWeeklyRankSettlementsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[247] + mi := &file_proto_activity_v1_activity_proto_msgTypes[254] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19650,7 +20175,7 @@ func (x *ListCPWeeklyRankSettlementsRequest) ProtoReflect() protoreflect.Message // Deprecated: Use ListCPWeeklyRankSettlementsRequest.ProtoReflect.Descriptor instead. func (*ListCPWeeklyRankSettlementsRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{247} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{254} } func (x *ListCPWeeklyRankSettlementsRequest) GetMeta() *RequestMeta { @@ -19684,7 +20209,7 @@ type ListCPWeeklyRankSettlementsResponse struct { func (x *ListCPWeeklyRankSettlementsResponse) Reset() { *x = ListCPWeeklyRankSettlementsResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[248] + mi := &file_proto_activity_v1_activity_proto_msgTypes[255] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19696,7 +20221,7 @@ func (x *ListCPWeeklyRankSettlementsResponse) String() string { func (*ListCPWeeklyRankSettlementsResponse) ProtoMessage() {} func (x *ListCPWeeklyRankSettlementsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[248] + mi := &file_proto_activity_v1_activity_proto_msgTypes[255] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19709,7 +20234,7 @@ func (x *ListCPWeeklyRankSettlementsResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use ListCPWeeklyRankSettlementsResponse.ProtoReflect.Descriptor instead. func (*ListCPWeeklyRankSettlementsResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{248} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{255} } func (x *ListCPWeeklyRankSettlementsResponse) GetSettlements() []*CPWeeklyRankSettlement { @@ -19733,7 +20258,7 @@ type AgencyOpeningReward struct { func (x *AgencyOpeningReward) Reset() { *x = AgencyOpeningReward{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[249] + mi := &file_proto_activity_v1_activity_proto_msgTypes[256] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19745,7 +20270,7 @@ func (x *AgencyOpeningReward) String() string { func (*AgencyOpeningReward) ProtoMessage() {} func (x *AgencyOpeningReward) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[249] + mi := &file_proto_activity_v1_activity_proto_msgTypes[256] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19758,7 +20283,7 @@ func (x *AgencyOpeningReward) ProtoReflect() protoreflect.Message { // Deprecated: Use AgencyOpeningReward.ProtoReflect.Descriptor instead. func (*AgencyOpeningReward) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{249} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{256} } func (x *AgencyOpeningReward) GetRankNo() int32 { @@ -19807,7 +20332,7 @@ type AgencyOpeningCycle struct { func (x *AgencyOpeningCycle) Reset() { *x = AgencyOpeningCycle{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[250] + mi := &file_proto_activity_v1_activity_proto_msgTypes[257] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19819,7 +20344,7 @@ func (x *AgencyOpeningCycle) String() string { func (*AgencyOpeningCycle) ProtoMessage() {} func (x *AgencyOpeningCycle) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[250] + mi := &file_proto_activity_v1_activity_proto_msgTypes[257] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19832,7 +20357,7 @@ func (x *AgencyOpeningCycle) ProtoReflect() protoreflect.Message { // Deprecated: Use AgencyOpeningCycle.ProtoReflect.Descriptor instead. func (*AgencyOpeningCycle) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{250} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{257} } func (x *AgencyOpeningCycle) GetCycleId() string { @@ -19972,7 +20497,7 @@ type AgencyOpeningApplication struct { func (x *AgencyOpeningApplication) Reset() { *x = AgencyOpeningApplication{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[251] + mi := &file_proto_activity_v1_activity_proto_msgTypes[258] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19984,7 +20509,7 @@ func (x *AgencyOpeningApplication) String() string { func (*AgencyOpeningApplication) ProtoMessage() {} func (x *AgencyOpeningApplication) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[251] + mi := &file_proto_activity_v1_activity_proto_msgTypes[258] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19997,7 +20522,7 @@ func (x *AgencyOpeningApplication) ProtoReflect() protoreflect.Message { // Deprecated: Use AgencyOpeningApplication.ProtoReflect.Descriptor instead. func (*AgencyOpeningApplication) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{251} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{258} } func (x *AgencyOpeningApplication) GetApplicationId() string { @@ -20170,7 +20695,7 @@ type AgencyOpeningAgencySnapshot struct { func (x *AgencyOpeningAgencySnapshot) Reset() { *x = AgencyOpeningAgencySnapshot{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[252] + mi := &file_proto_activity_v1_activity_proto_msgTypes[259] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20182,7 +20707,7 @@ func (x *AgencyOpeningAgencySnapshot) String() string { func (*AgencyOpeningAgencySnapshot) ProtoMessage() {} func (x *AgencyOpeningAgencySnapshot) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[252] + mi := &file_proto_activity_v1_activity_proto_msgTypes[259] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20195,7 +20720,7 @@ func (x *AgencyOpeningAgencySnapshot) ProtoReflect() protoreflect.Message { // Deprecated: Use AgencyOpeningAgencySnapshot.ProtoReflect.Descriptor instead. func (*AgencyOpeningAgencySnapshot) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{252} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{259} } func (x *AgencyOpeningAgencySnapshot) GetAgencyId() int64 { @@ -20255,7 +20780,7 @@ type ListAgencyOpeningCyclesRequest struct { func (x *ListAgencyOpeningCyclesRequest) Reset() { *x = ListAgencyOpeningCyclesRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[253] + mi := &file_proto_activity_v1_activity_proto_msgTypes[260] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20267,7 +20792,7 @@ func (x *ListAgencyOpeningCyclesRequest) String() string { func (*ListAgencyOpeningCyclesRequest) ProtoMessage() {} func (x *ListAgencyOpeningCyclesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[253] + mi := &file_proto_activity_v1_activity_proto_msgTypes[260] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20280,7 +20805,7 @@ func (x *ListAgencyOpeningCyclesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAgencyOpeningCyclesRequest.ProtoReflect.Descriptor instead. func (*ListAgencyOpeningCyclesRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{253} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{260} } func (x *ListAgencyOpeningCyclesRequest) GetMeta() *RequestMeta { @@ -20336,7 +20861,7 @@ type ListAgencyOpeningCyclesResponse struct { func (x *ListAgencyOpeningCyclesResponse) Reset() { *x = ListAgencyOpeningCyclesResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[254] + mi := &file_proto_activity_v1_activity_proto_msgTypes[261] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20348,7 +20873,7 @@ func (x *ListAgencyOpeningCyclesResponse) String() string { func (*ListAgencyOpeningCyclesResponse) ProtoMessage() {} func (x *ListAgencyOpeningCyclesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[254] + mi := &file_proto_activity_v1_activity_proto_msgTypes[261] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20361,7 +20886,7 @@ func (x *ListAgencyOpeningCyclesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAgencyOpeningCyclesResponse.ProtoReflect.Descriptor instead. func (*ListAgencyOpeningCyclesResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{254} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{261} } func (x *ListAgencyOpeningCyclesResponse) GetCycles() []*AgencyOpeningCycle { @@ -20389,7 +20914,7 @@ type GetAgencyOpeningCycleRequest struct { func (x *GetAgencyOpeningCycleRequest) Reset() { *x = GetAgencyOpeningCycleRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[255] + mi := &file_proto_activity_v1_activity_proto_msgTypes[262] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20401,7 +20926,7 @@ func (x *GetAgencyOpeningCycleRequest) String() string { func (*GetAgencyOpeningCycleRequest) ProtoMessage() {} func (x *GetAgencyOpeningCycleRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[255] + mi := &file_proto_activity_v1_activity_proto_msgTypes[262] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20414,7 +20939,7 @@ func (x *GetAgencyOpeningCycleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAgencyOpeningCycleRequest.ProtoReflect.Descriptor instead. func (*GetAgencyOpeningCycleRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{255} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{262} } func (x *GetAgencyOpeningCycleRequest) GetMeta() *RequestMeta { @@ -20441,7 +20966,7 @@ type GetAgencyOpeningCycleResponse struct { func (x *GetAgencyOpeningCycleResponse) Reset() { *x = GetAgencyOpeningCycleResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[256] + mi := &file_proto_activity_v1_activity_proto_msgTypes[263] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20453,7 +20978,7 @@ func (x *GetAgencyOpeningCycleResponse) String() string { func (*GetAgencyOpeningCycleResponse) ProtoMessage() {} func (x *GetAgencyOpeningCycleResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[256] + mi := &file_proto_activity_v1_activity_proto_msgTypes[263] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20466,7 +20991,7 @@ func (x *GetAgencyOpeningCycleResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAgencyOpeningCycleResponse.ProtoReflect.Descriptor instead. func (*GetAgencyOpeningCycleResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{256} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{263} } func (x *GetAgencyOpeningCycleResponse) GetCycle() *AgencyOpeningCycle { @@ -20488,7 +21013,7 @@ type UpsertAgencyOpeningCycleRequest struct { func (x *UpsertAgencyOpeningCycleRequest) Reset() { *x = UpsertAgencyOpeningCycleRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[257] + mi := &file_proto_activity_v1_activity_proto_msgTypes[264] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20500,7 +21025,7 @@ func (x *UpsertAgencyOpeningCycleRequest) String() string { func (*UpsertAgencyOpeningCycleRequest) ProtoMessage() {} func (x *UpsertAgencyOpeningCycleRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[257] + mi := &file_proto_activity_v1_activity_proto_msgTypes[264] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20513,7 +21038,7 @@ func (x *UpsertAgencyOpeningCycleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertAgencyOpeningCycleRequest.ProtoReflect.Descriptor instead. func (*UpsertAgencyOpeningCycleRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{257} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{264} } func (x *UpsertAgencyOpeningCycleRequest) GetMeta() *RequestMeta { @@ -20548,7 +21073,7 @@ type UpsertAgencyOpeningCycleResponse struct { func (x *UpsertAgencyOpeningCycleResponse) Reset() { *x = UpsertAgencyOpeningCycleResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[258] + mi := &file_proto_activity_v1_activity_proto_msgTypes[265] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20560,7 +21085,7 @@ func (x *UpsertAgencyOpeningCycleResponse) String() string { func (*UpsertAgencyOpeningCycleResponse) ProtoMessage() {} func (x *UpsertAgencyOpeningCycleResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[258] + mi := &file_proto_activity_v1_activity_proto_msgTypes[265] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20573,7 +21098,7 @@ func (x *UpsertAgencyOpeningCycleResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertAgencyOpeningCycleResponse.ProtoReflect.Descriptor instead. func (*UpsertAgencyOpeningCycleResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{258} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{265} } func (x *UpsertAgencyOpeningCycleResponse) GetCycle() *AgencyOpeningCycle { @@ -20603,7 +21128,7 @@ type SetAgencyOpeningCycleStatusRequest struct { func (x *SetAgencyOpeningCycleStatusRequest) Reset() { *x = SetAgencyOpeningCycleStatusRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[259] + mi := &file_proto_activity_v1_activity_proto_msgTypes[266] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20615,7 +21140,7 @@ func (x *SetAgencyOpeningCycleStatusRequest) String() string { func (*SetAgencyOpeningCycleStatusRequest) ProtoMessage() {} func (x *SetAgencyOpeningCycleStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[259] + mi := &file_proto_activity_v1_activity_proto_msgTypes[266] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20628,7 +21153,7 @@ func (x *SetAgencyOpeningCycleStatusRequest) ProtoReflect() protoreflect.Message // Deprecated: Use SetAgencyOpeningCycleStatusRequest.ProtoReflect.Descriptor instead. func (*SetAgencyOpeningCycleStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{259} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{266} } func (x *SetAgencyOpeningCycleStatusRequest) GetMeta() *RequestMeta { @@ -20669,7 +21194,7 @@ type SetAgencyOpeningCycleStatusResponse struct { func (x *SetAgencyOpeningCycleStatusResponse) Reset() { *x = SetAgencyOpeningCycleStatusResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[260] + mi := &file_proto_activity_v1_activity_proto_msgTypes[267] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20681,7 +21206,7 @@ func (x *SetAgencyOpeningCycleStatusResponse) String() string { func (*SetAgencyOpeningCycleStatusResponse) ProtoMessage() {} func (x *SetAgencyOpeningCycleStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[260] + mi := &file_proto_activity_v1_activity_proto_msgTypes[267] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20694,7 +21219,7 @@ func (x *SetAgencyOpeningCycleStatusResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use SetAgencyOpeningCycleStatusResponse.ProtoReflect.Descriptor instead. func (*SetAgencyOpeningCycleStatusResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{260} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{267} } func (x *SetAgencyOpeningCycleStatusResponse) GetCycle() *AgencyOpeningCycle { @@ -20720,7 +21245,7 @@ type ListAgencyOpeningApplicationsRequest struct { func (x *ListAgencyOpeningApplicationsRequest) Reset() { *x = ListAgencyOpeningApplicationsRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[261] + mi := &file_proto_activity_v1_activity_proto_msgTypes[268] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20732,7 +21257,7 @@ func (x *ListAgencyOpeningApplicationsRequest) String() string { func (*ListAgencyOpeningApplicationsRequest) ProtoMessage() {} func (x *ListAgencyOpeningApplicationsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[261] + mi := &file_proto_activity_v1_activity_proto_msgTypes[268] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20745,7 +21270,7 @@ func (x *ListAgencyOpeningApplicationsRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use ListAgencyOpeningApplicationsRequest.ProtoReflect.Descriptor instead. func (*ListAgencyOpeningApplicationsRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{261} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{268} } func (x *ListAgencyOpeningApplicationsRequest) GetMeta() *RequestMeta { @@ -20808,7 +21333,7 @@ type ListAgencyOpeningApplicationsResponse struct { func (x *ListAgencyOpeningApplicationsResponse) Reset() { *x = ListAgencyOpeningApplicationsResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[262] + mi := &file_proto_activity_v1_activity_proto_msgTypes[269] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20820,7 +21345,7 @@ func (x *ListAgencyOpeningApplicationsResponse) String() string { func (*ListAgencyOpeningApplicationsResponse) ProtoMessage() {} func (x *ListAgencyOpeningApplicationsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[262] + mi := &file_proto_activity_v1_activity_proto_msgTypes[269] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20833,7 +21358,7 @@ func (x *ListAgencyOpeningApplicationsResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use ListAgencyOpeningApplicationsResponse.ProtoReflect.Descriptor instead. func (*ListAgencyOpeningApplicationsResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{262} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{269} } func (x *ListAgencyOpeningApplicationsResponse) GetApplications() []*AgencyOpeningApplication { @@ -20863,7 +21388,7 @@ type ReviewAgencyOpeningApplicationRequest struct { func (x *ReviewAgencyOpeningApplicationRequest) Reset() { *x = ReviewAgencyOpeningApplicationRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[263] + mi := &file_proto_activity_v1_activity_proto_msgTypes[270] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20875,7 +21400,7 @@ func (x *ReviewAgencyOpeningApplicationRequest) String() string { func (*ReviewAgencyOpeningApplicationRequest) ProtoMessage() {} func (x *ReviewAgencyOpeningApplicationRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[263] + mi := &file_proto_activity_v1_activity_proto_msgTypes[270] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20888,7 +21413,7 @@ func (x *ReviewAgencyOpeningApplicationRequest) ProtoReflect() protoreflect.Mess // Deprecated: Use ReviewAgencyOpeningApplicationRequest.ProtoReflect.Descriptor instead. func (*ReviewAgencyOpeningApplicationRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{263} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{270} } func (x *ReviewAgencyOpeningApplicationRequest) GetMeta() *RequestMeta { @@ -20929,7 +21454,7 @@ type ReviewAgencyOpeningApplicationResponse struct { func (x *ReviewAgencyOpeningApplicationResponse) Reset() { *x = ReviewAgencyOpeningApplicationResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[264] + mi := &file_proto_activity_v1_activity_proto_msgTypes[271] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20941,7 +21466,7 @@ func (x *ReviewAgencyOpeningApplicationResponse) String() string { func (*ReviewAgencyOpeningApplicationResponse) ProtoMessage() {} func (x *ReviewAgencyOpeningApplicationResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[264] + mi := &file_proto_activity_v1_activity_proto_msgTypes[271] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20954,7 +21479,7 @@ func (x *ReviewAgencyOpeningApplicationResponse) ProtoReflect() protoreflect.Mes // Deprecated: Use ReviewAgencyOpeningApplicationResponse.ProtoReflect.Descriptor instead. func (*ReviewAgencyOpeningApplicationResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{264} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{271} } func (x *ReviewAgencyOpeningApplicationResponse) GetApplication() *AgencyOpeningApplication { @@ -20975,7 +21500,7 @@ type GetAgencyOpeningStatusRequest struct { func (x *GetAgencyOpeningStatusRequest) Reset() { *x = GetAgencyOpeningStatusRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[265] + mi := &file_proto_activity_v1_activity_proto_msgTypes[272] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20987,7 +21512,7 @@ func (x *GetAgencyOpeningStatusRequest) String() string { func (*GetAgencyOpeningStatusRequest) ProtoMessage() {} func (x *GetAgencyOpeningStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[265] + mi := &file_proto_activity_v1_activity_proto_msgTypes[272] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -21000,7 +21525,7 @@ func (x *GetAgencyOpeningStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAgencyOpeningStatusRequest.ProtoReflect.Descriptor instead. func (*GetAgencyOpeningStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{265} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{272} } func (x *GetAgencyOpeningStatusRequest) GetMeta() *RequestMeta { @@ -21034,7 +21559,7 @@ type GetAgencyOpeningStatusResponse struct { func (x *GetAgencyOpeningStatusResponse) Reset() { *x = GetAgencyOpeningStatusResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[266] + mi := &file_proto_activity_v1_activity_proto_msgTypes[273] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -21046,7 +21571,7 @@ func (x *GetAgencyOpeningStatusResponse) String() string { func (*GetAgencyOpeningStatusResponse) ProtoMessage() {} func (x *GetAgencyOpeningStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[266] + mi := &file_proto_activity_v1_activity_proto_msgTypes[273] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -21059,7 +21584,7 @@ func (x *GetAgencyOpeningStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAgencyOpeningStatusResponse.ProtoReflect.Descriptor instead. func (*GetAgencyOpeningStatusResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{266} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{273} } func (x *GetAgencyOpeningStatusResponse) GetCycle() *AgencyOpeningCycle { @@ -21130,7 +21655,7 @@ type ApplyAgencyOpeningRequest struct { func (x *ApplyAgencyOpeningRequest) Reset() { *x = ApplyAgencyOpeningRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[267] + mi := &file_proto_activity_v1_activity_proto_msgTypes[274] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -21142,7 +21667,7 @@ func (x *ApplyAgencyOpeningRequest) String() string { func (*ApplyAgencyOpeningRequest) ProtoMessage() {} func (x *ApplyAgencyOpeningRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[267] + mi := &file_proto_activity_v1_activity_proto_msgTypes[274] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -21155,7 +21680,7 @@ func (x *ApplyAgencyOpeningRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyAgencyOpeningRequest.ProtoReflect.Descriptor instead. func (*ApplyAgencyOpeningRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{267} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{274} } func (x *ApplyAgencyOpeningRequest) GetMeta() *RequestMeta { @@ -21190,7 +21715,7 @@ type ApplyAgencyOpeningResponse struct { func (x *ApplyAgencyOpeningResponse) Reset() { *x = ApplyAgencyOpeningResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[268] + mi := &file_proto_activity_v1_activity_proto_msgTypes[275] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -21202,7 +21727,7 @@ func (x *ApplyAgencyOpeningResponse) String() string { func (*ApplyAgencyOpeningResponse) ProtoMessage() {} func (x *ApplyAgencyOpeningResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[268] + mi := &file_proto_activity_v1_activity_proto_msgTypes[275] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -21215,7 +21740,7 @@ func (x *ApplyAgencyOpeningResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyAgencyOpeningResponse.ProtoReflect.Descriptor instead. func (*ApplyAgencyOpeningResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{268} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{275} } func (x *ApplyAgencyOpeningResponse) GetApplication() *AgencyOpeningApplication { @@ -23640,7 +24165,7 @@ var file_proto_activity_v1_activity_proto_rawDesc = []byte{ 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xe7, 0x03, 0x0a, 0x0e, 0x4c, + 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x82, 0x06, 0x0a, 0x0e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x4a, 0x6f, 0x62, 0x49, @@ -23671,98 +24196,197 @@ var file_proto_activity_v1_activity_proto_rawDesc = []byte{ 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x4d, 0x73, 0x22, 0xc5, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x32, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, - 0x05, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x72, - 0x61, 0x63, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, - 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, - 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x6d, 0x0a, 0x18, - 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x07, 0x72, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x07, 0x72, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0xa4, 0x03, 0x0a, 0x18, - 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x41, 0x74, 0x4d, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x6f, + 0x72, 0x69, 0x67, 0x69, 0x6e, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x65, 0x6d, + 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, + 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x67, 0x72, 0x61, 0x6e, 0x74, + 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x6c, 0x65, 0x76, + 0x65, 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x65, 0x72, 0x6d, 0x61, 0x6e, 0x65, + 0x6e, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x70, 0x65, 0x72, 0x6d, 0x61, 0x6e, + 0x65, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, + 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x13, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x45, + 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, + 0x76, 0x6f, 0x6b, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, + 0xc5, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, + 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x72, 0x61, 0x63, + 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, + 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x6d, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x4c, + 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x07, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, + 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x07, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, + 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0xa4, 0x03, 0x0a, 0x18, 0x43, 0x6f, 0x6e, 0x73, 0x75, + 0x6d, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, + 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, + 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, + 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, + 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, + 0x0b, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x24, + 0x0a, 0x0e, 0x6f, 0x63, 0x63, 0x75, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6f, 0x63, 0x63, 0x75, 0x72, 0x72, 0x65, 0x64, + 0x41, 0x74, 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, + 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x4a, 0x73, 0x6f, 0x6e, 0x22, 0xe1, 0x01, + 0x0a, 0x19, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, + 0x0a, 0x0d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, + 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x70, 0x72, 0x65, + 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x65, + 0x77, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6e, + 0x65, 0x77, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x77, 0x61, 0x72, + 0x64, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0e, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x4a, 0x6f, 0x62, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x22, 0xef, 0x01, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, + 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, + 0x6c, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x22, 0x94, 0x02, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, + 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x14, 0x0a, 0x05, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x74, 0x72, 0x61, 0x63, 0x6b, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, + 0x73, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x70, + 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, + 0x6e, 0x65, 0x77, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x08, 0x6e, 0x65, 0x77, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x4a, 0x6f, 0x62, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xed, 0x02, 0x0a, 0x16, 0x41, + 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x12, 0x1d, 0x0a, 0x0a, 0x72, + 0x65, 0x61, 0x6c, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x09, 0x72, 0x65, 0x61, 0x6c, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, + 0x61, 0x6c, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x61, 0x6c, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, + 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2c, + 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x6c, 0x65, 0x76, 0x65, + 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x74, 0x65, 0x6d, 0x70, + 0x6f, 0x72, 0x61, 0x72, 0x79, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x34, 0x0a, 0x16, + 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x74, 0x65, + 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, + 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x73, 0x0a, 0x15, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x50, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x41, 0x0a, 0x06, + 0x74, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, + 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x06, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x22, + 0x76, 0x0a, 0x25, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, + 0x65, 0x76, 0x65, 0x6c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, - 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, - 0x25, 0x0a, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, - 0x72, 0x61, 0x63, 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x72, 0x61, 0x63, - 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, - 0x61, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x44, 0x65, - 0x6c, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x63, 0x63, 0x75, 0x72, 0x72, 0x65, 0x64, 0x5f, - 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6f, 0x63, 0x63, - 0x75, 0x72, 0x72, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x69, 0x6d, - 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0e, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x4a, 0x73, - 0x6f, 0x6e, 0x22, 0xe1, 0x01, 0x0a, 0x19, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6c, 0x65, 0x76, 0x65, - 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x65, 0x76, - 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0d, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, - 0x1b, 0x0a, 0x09, 0x6e, 0x65, 0x77, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x08, 0x6e, 0x65, 0x77, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x28, 0x0a, 0x10, - 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x4a, 0x6f, - 0x62, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xef, 0x01, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, - 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, - 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x72, - 0x61, 0x63, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x72, 0x61, 0x63, 0x6b, - 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x94, 0x02, 0x0a, 0x14, 0x53, 0x65, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, - 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x0d, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4c, 0x65, 0x76, 0x65, - 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x65, 0x77, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6e, 0x65, 0x77, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1f, - 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, - 0x28, 0x0a, 0x10, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x4a, 0x6f, 0x62, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x07, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, 0x94, 0x01, 0x0a, 0x26, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x41, 0x64, 0x6d, + 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x44, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, + 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, + 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x08, + 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x6b, + 0x0a, 0x18, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x72, + 0x61, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x72, 0x61, 0x63, 0x6b, + 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x79, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x64, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x79, 0x73, 0x22, 0xa1, 0x02, 0x0a, 0x20, + 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x55, + 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x32, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x4d, 0x0a, 0x0b, + 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, + 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x4c, + 0x65, 0x76, 0x65, 0x6c, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0b, + 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, + 0x8d, 0x01, 0x0a, 0x21, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, + 0x61, 0x72, 0x79, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, + 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x52, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x85, 0x01, 0x0a, 0x1b, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x42, 0x61, 0x64, 0x67, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x12, @@ -25248,7 +25872,7 @@ var file_proto_activity_v1_activity_proto_rawDesc = []byte{ 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x32, 0x98, 0x06, 0x0a, 0x13, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, + 0x6e, 0x73, 0x65, 0x32, 0x81, 0x07, 0x0a, 0x13, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x43, 0x72, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x66, 0x0a, 0x19, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x46, 0x61, 0x6e, 0x6f, 0x75, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, @@ -25262,853 +25886,878 @@ var file_proto_activity_v1_activity_proto_rawDesc = []byte{ 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x1d, 0x50, 0x72, 0x6f, - 0x63, 0x65, 0x73, 0x73, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x75, 0x0a, 0x28, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, - 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, - 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, - 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x20, - 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, - 0x72, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, - 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, - 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x22, 0x50, - 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, - 0x6e, 0x6b, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, - 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, - 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x23, - 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, - 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, + 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, 0x1a, 0x50, 0x72, 0x6f, + 0x63, 0x65, 0x73, 0x73, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x1d, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x41, 0x63, 0x68, + 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, - 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc8, - 0x02, 0x0a, 0x0b, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x62, - 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, - 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x54, 0x61, 0x73, 0x6b, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x55, 0x73, 0x65, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x68, 0x0a, 0x0f, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x54, 0x61, 0x73, 0x6b, 0x52, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x54, - 0x61, 0x73, 0x6b, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, - 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x10, - 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, - 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x54, 0x61, 0x73, 0x6b, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, + 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x75, + 0x0a, 0x28, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, + 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x74, 0x74, 0x6c, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x20, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, + 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, + 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x22, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, + 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x53, 0x65, 0x74, 0x74, 0x6c, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x23, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, + 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, + 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xd9, 0x06, 0x0a, 0x12, 0x47, 0x72, - 0x6f, 0x77, 0x74, 0x68, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x71, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x4f, 0x76, - 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, - 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x79, - 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, - 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, - 0x72, 0x61, 0x63, 0x6b, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, - 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, - 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, + 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, + 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc8, 0x02, 0x0a, 0x0b, 0x54, 0x61, 0x73, 0x6b, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x62, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x55, + 0x73, 0x65, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, + 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x54, 0x61, + 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x0f, 0x43, + 0x6c, 0x61, 0x69, 0x6d, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x29, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, + 0x61, 0x69, 0x6d, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, + 0x54, 0x61, 0x73, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, + 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, + 0x65, 0x54, 0x61, 0x73, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x32, 0xd9, 0x06, 0x0a, 0x12, 0x47, 0x72, 0x6f, 0x77, 0x74, 0x68, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x71, 0x0a, 0x12, 0x47, 0x65, 0x74, + 0x4d, 0x79, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x12, + 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x4f, 0x76, + 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x4f, 0x76, 0x65, 0x72, + 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x0d, + 0x47, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x9b, 0x01, 0x0a, 0x20, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x44, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x3a, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, - 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, - 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x44, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, + 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x65, + 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x9b, 0x01, 0x0a, 0x20, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x3a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, + 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x44, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x3b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, + 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, + 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, + 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, 0x72, + 0x64, 0x73, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x11, 0x43, + 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, + 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x0c, 0x53, + 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x26, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, + 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8f, 0x01, 0x0a, + 0x1c, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x42, 0x61, 0x64, 0x67, 0x65, 0x73, 0x12, 0x36, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, + 0x31, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x42, 0x61, 0x64, 0x67, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x42, 0x61, 0x64, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xcf, + 0x03, 0x0a, 0x12, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6b, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x68, + 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x52, 0x65, + 0x73, 0x74, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x11, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, - 0x75, 0x6d, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, - 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, - 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x0c, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x8f, 0x01, 0x0a, 0x1c, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x42, 0x61, - 0x64, 0x67, 0x65, 0x73, 0x12, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, - 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, - 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x42, - 0x61, 0x64, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, - 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x42, 0x61, 0x64, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xcf, 0x03, 0x0a, 0x12, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6b, 0x0a, 0x10, - 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, - 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x17, 0x43, 0x6f, - 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, - 0x65, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, - 0x73, 0x75, 0x6d, 0x65, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x0c, - 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x79, 0x42, 0x61, 0x64, 0x67, 0x65, 0x73, 0x12, 0x26, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x79, 0x42, 0x61, 0x64, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, - 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x79, 0x42, - 0x61, 0x64, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, - 0x0f, 0x53, 0x65, 0x74, 0x42, 0x61, 0x64, 0x67, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, - 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x42, 0x61, 0x64, 0x67, 0x65, 0x44, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, + 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x17, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x41, 0x63, + 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x31, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, + 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x41, 0x63, 0x68, + 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x79, 0x42, + 0x61, 0x64, 0x67, 0x65, 0x73, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x79, + 0x42, 0x61, 0x64, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x79, 0x42, 0x61, 0x64, 0x67, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x42, 0x61, 0x64, + 0x67, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, + 0x74, 0x42, 0x61, 0x64, 0x67, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, + 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x42, 0x61, 0x64, 0x67, + 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x32, 0x7b, 0x0a, 0x0c, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x6b, 0x0a, 0x10, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x57, 0x68, 0x65, 0x65, 0x6c, + 0x44, 0x72, 0x61, 0x77, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, + 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, + 0x57, 0x68, 0x65, 0x65, 0x6c, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, + 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x57, 0x68, 0x65, 0x65, + 0x6c, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xaa, 0x01, + 0x0a, 0x19, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x1b, + 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, + 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, - 0x53, 0x65, 0x74, 0x42, 0x61, 0x64, 0x67, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x7b, 0x0a, 0x0c, 0x57, 0x68, 0x65, 0x65, 0x6c, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6b, 0x0a, 0x10, 0x45, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x65, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x44, 0x72, 0x61, 0x77, 0x12, 0x2a, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, - 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x44, 0x72, 0x61, 0x77, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x65, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xaa, 0x01, 0x0a, 0x19, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, - 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, - 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, + 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, - 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x32, 0x91, 0x03, 0x0a, 0x11, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x77, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x57, 0x65, - 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12, - 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, - 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, - 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x86, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, - 0x74, 0x61, 0x72, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x33, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, - 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, - 0x72, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, - 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, - 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x15, 0x4c, 0x69, 0x73, - 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, - 0x72, 0x79, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, - 0x79, 0x53, 0x74, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, - 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x91, 0x01, 0x0a, 0x13, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, - 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7a, 0x0a, - 0x15, 0x47, 0x65, 0x74, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, - 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x50, - 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, - 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x9c, 0x03, 0x0a, 0x14, 0x41, 0x67, - 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x12, 0x7d, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, - 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x2e, 0x68, + 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x91, 0x03, 0x0a, 0x11, 0x57, + 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x77, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, + 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x86, 0x01, 0x0a, 0x19, 0x4c, 0x69, + 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x4c, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, - 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x4c, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, + 0x53, 0x74, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x2f, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x48, 0x69, + 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x48, + 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x91, + 0x01, 0x0a, 0x13, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7a, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x43, 0x50, 0x57, + 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, + 0x61, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, + 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, + 0x52, 0x61, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x32, 0x9c, 0x03, 0x0a, 0x14, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, + 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7d, 0x0a, 0x16, 0x47, + 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, + 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, + 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x12, 0x41, 0x70, + 0x70, 0x6c, 0x79, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, + 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, + 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, + 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, - 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x71, 0x0a, 0x12, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, - 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, - 0x79, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x41, - 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x91, 0x01, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, - 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, - 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x70, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, + 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, + 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x91, 0x01, + 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, + 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x8b, 0x05, 0x0a, 0x10, 0x42, 0x72, 0x6f, - 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7a, 0x0a, - 0x15, 0x45, 0x6e, 0x73, 0x75, 0x72, 0x65, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, - 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x73, 0x75, 0x72, - 0x65, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x73, 0x75, - 0x72, 0x65, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, 0x0a, 0x16, 0x50, 0x75, 0x62, - 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, - 0x61, 0x73, 0x74, 0x12, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, - 0x68, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x77, 0x0a, 0x16, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x47, 0x6c, 0x6f, - 0x62, 0x61, 0x6c, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x12, 0x30, 0x2e, 0x68, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x70, 0x70, + 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x32, 0x8b, 0x05, 0x0a, 0x10, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7a, 0x0a, 0x15, 0x45, 0x6e, 0x73, 0x75, 0x72, 0x65, + 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, + 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, + 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x73, 0x75, 0x72, 0x65, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, + 0x61, 0x73, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, + 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x73, 0x75, 0x72, 0x65, 0x42, 0x72, 0x6f, 0x61, 0x64, + 0x63, 0x61, 0x73, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x77, 0x0a, 0x16, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x12, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, - 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x42, 0x72, + 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, - 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x1b, - 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x42, 0x72, 0x6f, 0x61, - 0x64, 0x63, 0x61, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x35, 0x2e, 0x68, 0x79, + 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, 0x0a, 0x16, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x42, 0x72, 0x6f, 0x61, + 0x64, 0x63, 0x61, 0x73, 0x74, 0x12, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x62, 0x6c, + 0x69, 0x73, 0x68, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x1b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x4d, 0x65, + 0x6d, 0x62, 0x65, 0x72, 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, + 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x4d, 0x65, + 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x42, 0x72, 0x6f, 0x61, - 0x64, 0x63, 0x61, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, - 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x1b, 0x50, 0x72, - 0x6f, 0x63, 0x65, 0x73, 0x73, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x4f, 0x75, - 0x74, 0x62, 0x6f, 0x78, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, - 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, - 0x61, 0x73, 0x74, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x78, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x87, 0x01, 0x0a, 0x18, 0x52, 0x6f, 0x6f, 0x6d, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x12, 0x6b, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x6f, - 0x6f, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, - 0x75, 0x6d, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x52, - 0x6f, 0x6f, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x32, 0x87, 0x04, 0x0a, 0x10, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x74, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x73, - 0x6b, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2d, 0x2e, 0x68, + 0x64, 0x63, 0x61, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x1b, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x42, 0x72, + 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x78, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x63, + 0x65, 0x73, 0x73, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x4f, 0x75, 0x74, 0x62, + 0x6f, 0x78, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, + 0x87, 0x01, 0x0a, 0x18, 0x52, 0x6f, 0x6f, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, + 0x73, 0x75, 0x6d, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6b, 0x0a, 0x10, + 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, + 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x6f, 0x6f, 0x6d, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, + 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x87, 0x04, 0x0a, 0x10, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x74, + 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, + 0x73, 0x6b, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, + 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x73, + 0x6b, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, 0x0a, 0x14, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x54, 0x61, + 0x73, 0x6b, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, + 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x65, 0x66, 0x69, 0x6e, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, + 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x65, 0x66, 0x69, 0x6e, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x80, 0x01, + 0x0a, 0x17, 0x53, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, + 0x74, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x80, 0x01, 0x0a, 0x17, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x54, 0x61, 0x73, 0x6b, + 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x31, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, + 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x54, 0x61, 0x73, 0x6b, 0x52, + 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x32, 0xbc, 0x02, 0x0a, 0x19, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x9b, 0x01, 0x0a, 0x20, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x45, 0x6c, 0x69, 0x67, 0x69, + 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x3a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, + 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x45, 0x6c, 0x69, 0x67, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x45, 0x6c, 0x69, 0x67, + 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x80, 0x01, 0x0a, 0x17, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, 0x0a, 0x14, 0x55, - 0x70, 0x73, 0x65, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x54, 0x61, - 0x73, 0x6b, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x54, 0x61, - 0x73, 0x6b, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, - 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, - 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x65, 0x66, 0x69, - 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x44, - 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x17, 0x50, 0x75, 0x62, 0x6c, - 0x69, 0x73, 0x68, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x12, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x54, - 0x61, 0x73, 0x6b, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, - 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, - 0x73, 0x68, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xbc, 0x02, 0x0a, 0x19, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x9b, 0x01, 0x0a, 0x20, 0x47, 0x65, 0x74, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x45, 0x6c, 0x69, 0x67, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x3a, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x45, 0x6c, 0x69, 0x67, 0x69, 0x62, 0x69, 0x6c, 0x69, - 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, - 0x61, 0x72, 0x64, 0x45, 0x6c, 0x69, 0x67, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x17, 0x49, 0x73, 0x73, 0x75, 0x65, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x12, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xd9, 0x03, 0x0a, 0x1e, 0x41, 0x64, - 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x8c, 0x01, 0x0a, - 0x1b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x2e, 0x68, + 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, + 0x76, 0x31, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x32, 0xd9, 0x03, 0x0a, 0x1e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x95, 0x01, 0x0a, 0x1e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x38, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, - 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x8f, 0x01, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6c, - 0x61, 0x69, 0x6d, 0x73, 0x12, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, - 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, - 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xba, 0x02, 0x0a, 0x1a, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, - 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x8f, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x46, 0x69, 0x72, 0x73, - 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x69, 0x72, - 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, - 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x89, 0x01, 0x0a, 0x1a, 0x43, 0x6f, 0x6e, 0x73, 0x75, - 0x6d, 0x65, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, - 0x65, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, - 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x32, 0xe3, 0x03, 0x0a, 0x1f, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x46, 0x69, 0x72, 0x73, - 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x8f, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x46, 0x69, - 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x46, - 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, - 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x98, 0x01, 0x0a, 0x1f, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, - 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x39, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, - 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x95, 0x01, 0x0a, 0x1e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, + 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x92, 0x01, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x69, 0x72, 0x73, - 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, - 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x69, + 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x39, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, + 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8f, 0x01, 0x0a, + 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x36, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xba, + 0x02, 0x0a, 0x1a, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, + 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x8f, 0x01, + 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x36, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, + 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, + 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, + 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x89, 0x01, 0x0a, 0x1a, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x46, 0x69, 0x72, 0x73, 0x74, + 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x34, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, + 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, + 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, + 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xe3, 0x03, 0x0a, 0x1f, + 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x8f, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, + 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, + 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, + 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x98, 0x01, 0x0a, 0x1f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x69, 0x72, 0x73, + 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x39, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x3a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, + 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x69, 0x72, 0x73, 0x74, + 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x92, 0x01, 0x0a, + 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xdd, 0x02, 0x0a, 0x1f, 0x43, 0x75, 0x6d, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x32, 0xdd, 0x02, 0x0a, 0x1f, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, + 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x9e, 0x01, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x9e, 0x01, 0x0a, - 0x21, 0x47, 0x65, 0x74, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, - 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x3b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, - 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x3c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, - 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x98, 0x01, - 0x0a, 0x1f, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, - 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x12, 0x39, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, - 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x43, 0x75, 0x6d, - 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, - 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xd0, 0x03, 0x0a, 0x1b, 0x49, 0x6e, 0x76, - 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x92, 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x74, - 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x37, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, - 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, + 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x3b, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, + 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x98, 0x01, 0x0a, 0x1f, 0x43, 0x6f, 0x6e, 0x73, 0x75, + 0x6d, 0x65, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, + 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x39, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, + 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, + 0x65, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, + 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x32, 0xd0, 0x03, 0x0a, 0x1b, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, + 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x92, 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, + 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x86, 0x01, - 0x0a, 0x19, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, - 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x33, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, - 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, - 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, - 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x92, 0x01, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x49, - 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x4c, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x4c, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, + 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x86, 0x01, 0x0a, 0x19, 0x43, 0x6c, 0x61, 0x69, 0x6d, + 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x12, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, + 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x49, 0x6e, + 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, + 0x61, 0x69, 0x6d, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, + 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x92, 0x01, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, + 0x64, 0x12, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, - 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x95, 0x04, 0x0a, 0x24, - 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, - 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x9e, 0x01, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x43, 0x75, 0x6d, 0x75, - 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3b, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, - 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, - 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, - 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0xa7, 0x01, 0x0a, 0x24, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, - 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3e, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, - 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, - 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3f, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, - 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, - 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0xa1, 0x01, 0x0a, 0x22, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, + 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, + 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x95, 0x04, 0x0a, 0x24, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x75, + 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, + 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x9e, 0x01, + 0x0a, 0x21, 0x47, 0x65, 0x74, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, + 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x3b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x75, 0x6d, 0x75, 0x6c, + 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x3c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, + 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x3c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, - 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, - 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, - 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, - 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x75, 0x6d, - 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x32, 0x8e, 0x05, 0x0a, 0x20, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x6e, 0x76, - 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x92, 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x74, - 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x37, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, - 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0xa7, + 0x01, 0x0a, 0x24, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, + 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, + 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, + 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, + 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0xa1, 0x01, 0x0a, 0x22, 0x4c, 0x69, 0x73, + 0x74, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, + 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, + 0x3c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, + 0x76, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3d, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, + 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x47, 0x72, + 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x8e, 0x05, 0x0a, + 0x20, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, + 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x92, 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, + 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x9b, 0x01, - 0x0a, 0x20, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x3a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x76, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, + 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x9b, 0x01, 0x0a, 0x20, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, + 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3a, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, + 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, + 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x95, 0x01, 0x0a, 0x1e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, - 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, - 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x95, 0x01, 0x0a, 0x1e, - 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, - 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x38, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, - 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, - 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x9e, 0x01, 0x0a, 0x21, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x69, - 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x69, 0x65, 0x73, 0x12, 0x3b, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, - 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x69, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, - 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, - 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, - 0x61, 0x72, 0x64, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x89, 0x05, 0x0a, 0x1e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, - 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x52, - 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x39, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, + 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, + 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6c, + 0x61, 0x69, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x9e, 0x01, 0x0a, + 0x21, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, + 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x69, + 0x65, 0x73, 0x12, 0x3b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, + 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x3c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x89, 0x05, + 0x0a, 0x1e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, + 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x8c, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, + 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, + 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, + 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, - 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x95, 0x01, 0x0a, 0x1e, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, - 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, - 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, - 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x9e, - 0x01, 0x0a, 0x21, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, - 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, - 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, - 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, - 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x3c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, - 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, - 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x74, 0x74, - 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x9e, 0x01, 0x0a, 0x21, 0x52, 0x65, 0x74, 0x72, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, - 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x74, 0x74, 0x6c, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x3b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x79, 0x52, - 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x3c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x79, 0x52, 0x6f, 0x6f, 0x6d, - 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, - 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x32, 0x94, 0x07, 0x0a, 0x16, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, - 0x53, 0x74, 0x61, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x77, 0x0a, 0x14, 0x4c, - 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x79, 0x63, - 0x6c, 0x65, 0x73, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, - 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, - 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x65, - 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x2f, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, - 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, - 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, - 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, - 0x74, 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x71, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, - 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, - 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x65, - 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, - 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x65, 0x65, 0x6b, - 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x57, 0x65, 0x65, - 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x2f, 0x2e, 0x68, + 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x95, 0x01, 0x0a, 0x1e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, + 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, + 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, - 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, - 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, + 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x9e, 0x01, 0x0a, 0x21, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, - 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, - 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x83, 0x01, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, - 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x2e, 0x68, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, + 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3c, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, + 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x9e, 0x01, 0x0a, 0x21, 0x52, 0x65, 0x74, + 0x72, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x3b, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, + 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3c, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x74, 0x72, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, + 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x94, 0x07, 0x0a, 0x16, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x77, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, + 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x65, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x79, - 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, - 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, - 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x86, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, - 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, - 0x61, 0x72, 0x64, 0x12, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, - 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, - 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x4c, 0x65, 0x61, 0x64, 0x65, - 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x86, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, + 0x79, 0x63, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, + 0x79, 0x63, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, + 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, + 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, + 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, + 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, + 0x72, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x12, 0x47, 0x65, 0x74, + 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x12, + 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, + 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, + 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x15, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, + 0x43, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, + 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, + 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, + 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x83, 0x01, 0x0a, 0x18, 0x53, 0x65, 0x74, + 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x57, 0x65, 0x65, + 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, + 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x43, 0x79, 0x63, 0x6c, 0x65, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x86, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, - 0x72, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x33, 0x2e, 0x68, + 0x72, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x53, - 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x4c, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, - 0x53, 0x74, 0x61, 0x72, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xab, 0x03, 0x0a, 0x18, 0x41, 0x64, 0x6d, 0x69, - 0x6e, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x7a, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x43, 0x50, 0x57, 0x65, 0x65, - 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2f, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, - 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, - 0x6e, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x83, 0x01, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x50, 0x57, 0x65, 0x65, - 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x32, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, - 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, - 0x52, 0x61, 0x6e, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, - 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x50, 0x57, 0x65, - 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x43, - 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x53, 0x65, 0x74, 0x74, 0x6c, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, + 0x53, 0x74, 0x61, 0x72, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x86, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, + 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, + 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x72, 0x53, 0x65, 0x74, + 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x32, 0xab, 0x03, 0x0a, 0x18, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, + 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7a, 0x0a, + 0x15, 0x47, 0x65, 0x74, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, + 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x50, + 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, + 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x83, 0x01, 0x0a, 0x18, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, + 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, + 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x8c, 0x01, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, + 0x52, 0x61, 0x6e, 0x6b, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, + 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, + 0x52, 0x61, 0x6e, 0x6b, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x53, 0x65, 0x74, 0x74, 0x6c, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, - 0x6e, 0x6b, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xe2, 0x07, 0x0a, 0x19, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x41, - 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, - 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x12, - 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, - 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, - 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x83, 0x01, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, - 0x63, 0x6c, 0x65, 0x12, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x41, 0x67, - 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, - 0x72, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, - 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x15, - 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, - 0x43, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xe2, + 0x07, 0x0a, 0x19, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, + 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x80, 0x01, 0x0a, + 0x17, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, + 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x12, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, + 0x63, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, + 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x83, 0x01, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, + 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x32, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, + 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, + 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, + 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, + 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, + 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x2f, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, + 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, + 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x83, 0x01, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, + 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x32, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, + 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, + 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, - 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, - 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x83, 0x01, 0x0a, 0x18, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, - 0x43, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, - 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, - 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, - 0x73, 0x65, 0x72, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, - 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8c, - 0x01, 0x0a, 0x1b, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, - 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x35, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x1b, 0x53, 0x65, 0x74, 0x41, + 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, + 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, + 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, + 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, - 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x92, 0x01, - 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, - 0x69, 0x6e, 0x67, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, - 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, - 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x92, 0x01, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x41, + 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x70, - 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x95, 0x01, 0x0a, 0x1e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x41, 0x67, 0x65, - 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, - 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x70, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x39, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, + 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x94, 0x02, 0x0a, 0x16, 0x53, - 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x83, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x53, 0x65, 0x76, - 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, - 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, - 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, - 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x13, 0x53, - 0x69, 0x67, 0x6e, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x49, 0x6e, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x53, 0x65, 0x76, 0x65, 0x6e, - 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, - 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, - 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x32, 0xbb, 0x03, 0x0a, 0x1b, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x76, 0x65, 0x6e, - 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x83, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, - 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x32, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x95, 0x01, 0x0a, 0x1e, + 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, + 0x69, 0x6e, 0x67, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, - 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, - 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, - 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, + 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, + 0x69, 0x65, 0x77, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, + 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x32, 0x94, 0x02, 0x0a, 0x16, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x83, + 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x49, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x13, 0x53, 0x69, 0x67, 0x6e, 0x53, 0x65, 0x76, 0x65, + 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x12, 0x2d, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x69, 0x67, 0x6e, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x49, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x69, 0x67, 0x6e, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x49, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xbb, 0x03, 0x0a, 0x1b, 0x41, + 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x49, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x83, 0x01, 0x0a, 0x18, 0x47, + 0x65, 0x74, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, + 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, + 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x49, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x8c, 0x01, 0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x76, 0x65, 0x6e, + 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, + 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x76, 0x65, 0x6e, + 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, - 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, + 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x86, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x33, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x76, 0x65, 0x6e, + 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xe5, 0x05, 0x0a, 0x17, 0x41, 0x64, 0x6d, + 0x69, 0x6e, 0x47, 0x72, 0x6f, 0x77, 0x74, 0x68, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x68, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, + 0x0a, 0x10, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, + 0x63, 0x6b, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, - 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, - 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x86, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x53, - 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x43, 0x6c, - 0x61, 0x69, 0x6d, 0x73, 0x12, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, - 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x76, - 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x43, 0x6c, 0x61, 0x69, - 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x53, 0x65, 0x76, 0x65, 0x6e, 0x44, 0x61, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, - 0x6e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, - 0xc4, 0x03, 0x0a, 0x17, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x72, 0x6f, 0x77, 0x74, 0x68, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x68, 0x0a, 0x0f, 0x4c, - 0x69, 0x73, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, + 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, + 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x0f, 0x55, + 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, - 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x10, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x75, + 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, - 0x73, 0x65, 0x72, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, - 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x68, 0x0a, 0x0f, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x65, 0x76, 0x65, - 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, - 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, - 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, - 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x0f, - 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x69, 0x65, 0x72, 0x12, - 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, - 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, - 0x69, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x69, 0x65, 0x72, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xae, 0x03, 0x0a, 0x17, 0x41, 0x64, 0x6d, 0x69, 0x6e, - 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x12, 0x75, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, - 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, + 0x73, 0x65, 0x72, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x0f, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, + 0x65, 0x76, 0x65, 0x6c, 0x54, 0x69, 0x65, 0x72, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, + 0x65, 0x72, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x69, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x65, + 0x76, 0x65, 0x6c, 0x54, 0x69, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x95, 0x01, 0x0a, 0x1e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x73, 0x12, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, + 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x1b, 0x55, 0x70, - 0x73, 0x65, 0x72, 0x74, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x44, - 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, - 0x73, 0x65, 0x72, 0x74, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x44, - 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, - 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x41, 0x63, 0x68, 0x69, 0x65, - 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x1b, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x65, - 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x65, - 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, - 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc7, 0x03, 0x0a, 0x11, 0x41, 0x64, 0x6d, 0x69, - 0x6e, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x65, 0x0a, - 0x0e, 0x47, 0x65, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x86, 0x01, 0x0a, 0x19, 0x41, 0x64, 0x6a, 0x75, + 0x73, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x55, 0x73, 0x65, 0x72, 0x4c, + 0x65, 0x76, 0x65, 0x6c, 0x73, 0x12, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, + 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x41, + 0x64, 0x6a, 0x75, 0x73, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x55, 0x73, + 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x32, 0xae, 0x03, 0x0a, 0x17, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x75, 0x0a, 0x1a, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x44, + 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, + 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, + 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x1b, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x41, 0x63, + 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x41, 0x63, + 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, + 0x70, 0x73, 0x65, 0x72, 0x74, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x1b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x63, 0x68, + 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x63, 0x68, + 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x41, 0x63, 0x68, 0x69, 0x65, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x44, + 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x32, 0xc7, 0x03, 0x0a, 0x11, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x57, 0x68, 0x65, 0x65, 0x6c, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x65, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x57, 0x68, + 0x65, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x11, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x57, 0x68, - 0x65, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, - 0x73, 0x65, 0x72, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, - 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, - 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x68, 0x65, 0x65, - 0x6c, 0x44, 0x72, 0x61, 0x77, 0x73, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, - 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, - 0x68, 0x65, 0x65, 0x6c, 0x44, 0x72, 0x61, 0x77, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, + 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6e, + 0x0a, 0x11, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x57, 0x68, + 0x65, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, + 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, + 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x44, 0x72, 0x61, 0x77, 0x73, + 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x44, 0x72, - 0x61, 0x77, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x13, 0x47, - 0x65, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, 0x61, - 0x72, 0x79, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x44, - 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, - 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x44, 0x72, - 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x42, 0x2e, 0x5a, 0x2c, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x69, 0x74, 0x79, 0x2f, 0x76, 0x31, 0x3b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x76, - 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x77, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x44, 0x72, 0x61, 0x77, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x57, 0x68, 0x65, 0x65, + 0x6c, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x2d, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2e, 0x5a, 0x2c, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2f, 0x76, 0x31, + 0x3b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -26123,7 +26772,7 @@ func file_proto_activity_v1_activity_proto_rawDescGZIP() []byte { return file_proto_activity_v1_activity_proto_rawDescData } -var file_proto_activity_v1_activity_proto_msgTypes = make([]protoimpl.MessageInfo, 269) +var file_proto_activity_v1_activity_proto_msgTypes = make([]protoimpl.MessageInfo, 276) var file_proto_activity_v1_activity_proto_goTypes = []any{ (*RequestMeta)(nil), // 0: hyapp.activity.v1.RequestMeta (*PingActivityRequest)(nil), // 1: hyapp.activity.v1.PingActivityRequest @@ -26296,105 +26945,112 @@ var file_proto_activity_v1_activity_proto_goTypes = []any{ (*ConsumeLevelEventResponse)(nil), // 168: hyapp.activity.v1.ConsumeLevelEventResponse (*SetUserLevelRequest)(nil), // 169: hyapp.activity.v1.SetUserLevelRequest (*SetUserLevelResponse)(nil), // 170: hyapp.activity.v1.SetUserLevelResponse - (*RegistrationLevelBadgeGrant)(nil), // 171: hyapp.activity.v1.RegistrationLevelBadgeGrant - (*IssueRegistrationLevelBadgesRequest)(nil), // 172: hyapp.activity.v1.IssueRegistrationLevelBadgesRequest - (*IssueRegistrationLevelBadgesResponse)(nil), // 173: hyapp.activity.v1.IssueRegistrationLevelBadgesResponse - (*UpsertLevelTrackRequest)(nil), // 174: hyapp.activity.v1.UpsertLevelTrackRequest - (*UpsertLevelTrackResponse)(nil), // 175: hyapp.activity.v1.UpsertLevelTrackResponse - (*UpsertLevelRuleRequest)(nil), // 176: hyapp.activity.v1.UpsertLevelRuleRequest - (*UpsertLevelRuleResponse)(nil), // 177: hyapp.activity.v1.UpsertLevelRuleResponse - (*UpsertLevelTierRequest)(nil), // 178: hyapp.activity.v1.UpsertLevelTierRequest - (*UpsertLevelTierResponse)(nil), // 179: hyapp.activity.v1.UpsertLevelTierResponse - (*ListLevelConfigRequest)(nil), // 180: hyapp.activity.v1.ListLevelConfigRequest - (*ListLevelConfigResponse)(nil), // 181: hyapp.activity.v1.ListLevelConfigResponse - (*AchievementCondition)(nil), // 182: hyapp.activity.v1.AchievementCondition - (*AchievementDefinition)(nil), // 183: hyapp.activity.v1.AchievementDefinition - (*UserAchievement)(nil), // 184: hyapp.activity.v1.UserAchievement - (*ListAchievementsRequest)(nil), // 185: hyapp.activity.v1.ListAchievementsRequest - (*ListAchievementsResponse)(nil), // 186: hyapp.activity.v1.ListAchievementsResponse - (*ConsumeAchievementEventRequest)(nil), // 187: hyapp.activity.v1.ConsumeAchievementEventRequest - (*ConsumeAchievementEventResponse)(nil), // 188: hyapp.activity.v1.ConsumeAchievementEventResponse - (*BadgeDisplayItem)(nil), // 189: hyapp.activity.v1.BadgeDisplayItem - (*ListMyBadgesRequest)(nil), // 190: hyapp.activity.v1.ListMyBadgesRequest - (*ListMyBadgesResponse)(nil), // 191: hyapp.activity.v1.ListMyBadgesResponse - (*SetBadgeDisplayRequest)(nil), // 192: hyapp.activity.v1.SetBadgeDisplayRequest - (*SetBadgeDisplayResponse)(nil), // 193: hyapp.activity.v1.SetBadgeDisplayResponse - (*UpsertAchievementDefinitionRequest)(nil), // 194: hyapp.activity.v1.UpsertAchievementDefinitionRequest - (*UpsertAchievementDefinitionResponse)(nil), // 195: hyapp.activity.v1.UpsertAchievementDefinitionResponse - (*DeleteAchievementDefinitionRequest)(nil), // 196: hyapp.activity.v1.DeleteAchievementDefinitionRequest - (*DeleteAchievementDefinitionResponse)(nil), // 197: hyapp.activity.v1.DeleteAchievementDefinitionResponse - (*WheelPrizeTier)(nil), // 198: hyapp.activity.v1.WheelPrizeTier - (*WheelRuleConfig)(nil), // 199: hyapp.activity.v1.WheelRuleConfig - (*WheelDrawMeta)(nil), // 200: hyapp.activity.v1.WheelDrawMeta - (*WheelDrawResult)(nil), // 201: hyapp.activity.v1.WheelDrawResult - (*WheelDrawReward)(nil), // 202: hyapp.activity.v1.WheelDrawReward - (*ExecuteWheelDrawRequest)(nil), // 203: hyapp.activity.v1.ExecuteWheelDrawRequest - (*ExecuteWheelDrawResponse)(nil), // 204: hyapp.activity.v1.ExecuteWheelDrawResponse - (*GetWheelConfigRequest)(nil), // 205: hyapp.activity.v1.GetWheelConfigRequest - (*GetWheelConfigResponse)(nil), // 206: hyapp.activity.v1.GetWheelConfigResponse - (*UpsertWheelConfigRequest)(nil), // 207: hyapp.activity.v1.UpsertWheelConfigRequest - (*UpsertWheelConfigResponse)(nil), // 208: hyapp.activity.v1.UpsertWheelConfigResponse - (*ListWheelDrawsRequest)(nil), // 209: hyapp.activity.v1.ListWheelDrawsRequest - (*ListWheelDrawsResponse)(nil), // 210: hyapp.activity.v1.ListWheelDrawsResponse - (*WheelDrawSummary)(nil), // 211: hyapp.activity.v1.WheelDrawSummary - (*GetWheelDrawSummaryRequest)(nil), // 212: hyapp.activity.v1.GetWheelDrawSummaryRequest - (*GetWheelDrawSummaryResponse)(nil), // 213: hyapp.activity.v1.GetWheelDrawSummaryResponse - (*WeeklyStarGift)(nil), // 214: hyapp.activity.v1.WeeklyStarGift - (*WeeklyStarReward)(nil), // 215: hyapp.activity.v1.WeeklyStarReward - (*WeeklyStarCycle)(nil), // 216: hyapp.activity.v1.WeeklyStarCycle - (*WeeklyStarLeaderboardEntry)(nil), // 217: hyapp.activity.v1.WeeklyStarLeaderboardEntry - (*WeeklyStarSettlement)(nil), // 218: hyapp.activity.v1.WeeklyStarSettlement - (*ListWeeklyStarCyclesRequest)(nil), // 219: hyapp.activity.v1.ListWeeklyStarCyclesRequest - (*ListWeeklyStarCyclesResponse)(nil), // 220: hyapp.activity.v1.ListWeeklyStarCyclesResponse - (*GetWeeklyStarCycleRequest)(nil), // 221: hyapp.activity.v1.GetWeeklyStarCycleRequest - (*GetWeeklyStarCycleResponse)(nil), // 222: hyapp.activity.v1.GetWeeklyStarCycleResponse - (*UpsertWeeklyStarCycleRequest)(nil), // 223: hyapp.activity.v1.UpsertWeeklyStarCycleRequest - (*UpsertWeeklyStarCycleResponse)(nil), // 224: hyapp.activity.v1.UpsertWeeklyStarCycleResponse - (*SetWeeklyStarCycleStatusRequest)(nil), // 225: hyapp.activity.v1.SetWeeklyStarCycleStatusRequest - (*SetWeeklyStarCycleStatusResponse)(nil), // 226: hyapp.activity.v1.SetWeeklyStarCycleStatusResponse - (*ListWeeklyStarLeaderboardRequest)(nil), // 227: hyapp.activity.v1.ListWeeklyStarLeaderboardRequest - (*ListWeeklyStarLeaderboardResponse)(nil), // 228: hyapp.activity.v1.ListWeeklyStarLeaderboardResponse - (*ListWeeklyStarSettlementsRequest)(nil), // 229: hyapp.activity.v1.ListWeeklyStarSettlementsRequest - (*ListWeeklyStarSettlementsResponse)(nil), // 230: hyapp.activity.v1.ListWeeklyStarSettlementsResponse - (*GetWeeklyStarCurrentRequest)(nil), // 231: hyapp.activity.v1.GetWeeklyStarCurrentRequest - (*GetWeeklyStarCurrentResponse)(nil), // 232: hyapp.activity.v1.GetWeeklyStarCurrentResponse - (*ListWeeklyStarHistoryRequest)(nil), // 233: hyapp.activity.v1.ListWeeklyStarHistoryRequest - (*WeeklyStarHistoryCycle)(nil), // 234: hyapp.activity.v1.WeeklyStarHistoryCycle - (*ListWeeklyStarHistoryResponse)(nil), // 235: hyapp.activity.v1.ListWeeklyStarHistoryResponse - (*CPWeeklyRankReward)(nil), // 236: hyapp.activity.v1.CPWeeklyRankReward - (*CPWeeklyRankConfig)(nil), // 237: hyapp.activity.v1.CPWeeklyRankConfig - (*CPWeeklyRankUser)(nil), // 238: hyapp.activity.v1.CPWeeklyRankUser - (*CPWeeklyRankEntry)(nil), // 239: hyapp.activity.v1.CPWeeklyRankEntry - (*CPWeeklyRankSettlement)(nil), // 240: hyapp.activity.v1.CPWeeklyRankSettlement - (*GetCPWeeklyRankStatusRequest)(nil), // 241: hyapp.activity.v1.GetCPWeeklyRankStatusRequest - (*GetCPWeeklyRankStatusResponse)(nil), // 242: hyapp.activity.v1.GetCPWeeklyRankStatusResponse - (*GetCPWeeklyRankConfigRequest)(nil), // 243: hyapp.activity.v1.GetCPWeeklyRankConfigRequest - (*GetCPWeeklyRankConfigResponse)(nil), // 244: hyapp.activity.v1.GetCPWeeklyRankConfigResponse - (*UpdateCPWeeklyRankConfigRequest)(nil), // 245: hyapp.activity.v1.UpdateCPWeeklyRankConfigRequest - (*UpdateCPWeeklyRankConfigResponse)(nil), // 246: hyapp.activity.v1.UpdateCPWeeklyRankConfigResponse - (*ListCPWeeklyRankSettlementsRequest)(nil), // 247: hyapp.activity.v1.ListCPWeeklyRankSettlementsRequest - (*ListCPWeeklyRankSettlementsResponse)(nil), // 248: hyapp.activity.v1.ListCPWeeklyRankSettlementsResponse - (*AgencyOpeningReward)(nil), // 249: hyapp.activity.v1.AgencyOpeningReward - (*AgencyOpeningCycle)(nil), // 250: hyapp.activity.v1.AgencyOpeningCycle - (*AgencyOpeningApplication)(nil), // 251: hyapp.activity.v1.AgencyOpeningApplication - (*AgencyOpeningAgencySnapshot)(nil), // 252: hyapp.activity.v1.AgencyOpeningAgencySnapshot - (*ListAgencyOpeningCyclesRequest)(nil), // 253: hyapp.activity.v1.ListAgencyOpeningCyclesRequest - (*ListAgencyOpeningCyclesResponse)(nil), // 254: hyapp.activity.v1.ListAgencyOpeningCyclesResponse - (*GetAgencyOpeningCycleRequest)(nil), // 255: hyapp.activity.v1.GetAgencyOpeningCycleRequest - (*GetAgencyOpeningCycleResponse)(nil), // 256: hyapp.activity.v1.GetAgencyOpeningCycleResponse - (*UpsertAgencyOpeningCycleRequest)(nil), // 257: hyapp.activity.v1.UpsertAgencyOpeningCycleRequest - (*UpsertAgencyOpeningCycleResponse)(nil), // 258: hyapp.activity.v1.UpsertAgencyOpeningCycleResponse - (*SetAgencyOpeningCycleStatusRequest)(nil), // 259: hyapp.activity.v1.SetAgencyOpeningCycleStatusRequest - (*SetAgencyOpeningCycleStatusResponse)(nil), // 260: hyapp.activity.v1.SetAgencyOpeningCycleStatusResponse - (*ListAgencyOpeningApplicationsRequest)(nil), // 261: hyapp.activity.v1.ListAgencyOpeningApplicationsRequest - (*ListAgencyOpeningApplicationsResponse)(nil), // 262: hyapp.activity.v1.ListAgencyOpeningApplicationsResponse - (*ReviewAgencyOpeningApplicationRequest)(nil), // 263: hyapp.activity.v1.ReviewAgencyOpeningApplicationRequest - (*ReviewAgencyOpeningApplicationResponse)(nil), // 264: hyapp.activity.v1.ReviewAgencyOpeningApplicationResponse - (*GetAgencyOpeningStatusRequest)(nil), // 265: hyapp.activity.v1.GetAgencyOpeningStatusRequest - (*GetAgencyOpeningStatusResponse)(nil), // 266: hyapp.activity.v1.GetAgencyOpeningStatusResponse - (*ApplyAgencyOpeningRequest)(nil), // 267: hyapp.activity.v1.ApplyAgencyOpeningRequest - (*ApplyAgencyOpeningResponse)(nil), // 268: hyapp.activity.v1.ApplyAgencyOpeningResponse - (*v1.EventEnvelope)(nil), // 269: hyapp.events.room.v1.EventEnvelope + (*AdminLevelTrackProfile)(nil), // 171: hyapp.activity.v1.AdminLevelTrackProfile + (*AdminUserLevelProfile)(nil), // 172: hyapp.activity.v1.AdminUserLevelProfile + (*BatchGetUserLevelAdminProfilesRequest)(nil), // 173: hyapp.activity.v1.BatchGetUserLevelAdminProfilesRequest + (*BatchGetUserLevelAdminProfilesResponse)(nil), // 174: hyapp.activity.v1.BatchGetUserLevelAdminProfilesResponse + (*TemporaryLevelAdjustment)(nil), // 175: hyapp.activity.v1.TemporaryLevelAdjustment + (*AdjustTemporaryUserLevelsRequest)(nil), // 176: hyapp.activity.v1.AdjustTemporaryUserLevelsRequest + (*AdjustTemporaryUserLevelsResponse)(nil), // 177: hyapp.activity.v1.AdjustTemporaryUserLevelsResponse + (*RegistrationLevelBadgeGrant)(nil), // 178: hyapp.activity.v1.RegistrationLevelBadgeGrant + (*IssueRegistrationLevelBadgesRequest)(nil), // 179: hyapp.activity.v1.IssueRegistrationLevelBadgesRequest + (*IssueRegistrationLevelBadgesResponse)(nil), // 180: hyapp.activity.v1.IssueRegistrationLevelBadgesResponse + (*UpsertLevelTrackRequest)(nil), // 181: hyapp.activity.v1.UpsertLevelTrackRequest + (*UpsertLevelTrackResponse)(nil), // 182: hyapp.activity.v1.UpsertLevelTrackResponse + (*UpsertLevelRuleRequest)(nil), // 183: hyapp.activity.v1.UpsertLevelRuleRequest + (*UpsertLevelRuleResponse)(nil), // 184: hyapp.activity.v1.UpsertLevelRuleResponse + (*UpsertLevelTierRequest)(nil), // 185: hyapp.activity.v1.UpsertLevelTierRequest + (*UpsertLevelTierResponse)(nil), // 186: hyapp.activity.v1.UpsertLevelTierResponse + (*ListLevelConfigRequest)(nil), // 187: hyapp.activity.v1.ListLevelConfigRequest + (*ListLevelConfigResponse)(nil), // 188: hyapp.activity.v1.ListLevelConfigResponse + (*AchievementCondition)(nil), // 189: hyapp.activity.v1.AchievementCondition + (*AchievementDefinition)(nil), // 190: hyapp.activity.v1.AchievementDefinition + (*UserAchievement)(nil), // 191: hyapp.activity.v1.UserAchievement + (*ListAchievementsRequest)(nil), // 192: hyapp.activity.v1.ListAchievementsRequest + (*ListAchievementsResponse)(nil), // 193: hyapp.activity.v1.ListAchievementsResponse + (*ConsumeAchievementEventRequest)(nil), // 194: hyapp.activity.v1.ConsumeAchievementEventRequest + (*ConsumeAchievementEventResponse)(nil), // 195: hyapp.activity.v1.ConsumeAchievementEventResponse + (*BadgeDisplayItem)(nil), // 196: hyapp.activity.v1.BadgeDisplayItem + (*ListMyBadgesRequest)(nil), // 197: hyapp.activity.v1.ListMyBadgesRequest + (*ListMyBadgesResponse)(nil), // 198: hyapp.activity.v1.ListMyBadgesResponse + (*SetBadgeDisplayRequest)(nil), // 199: hyapp.activity.v1.SetBadgeDisplayRequest + (*SetBadgeDisplayResponse)(nil), // 200: hyapp.activity.v1.SetBadgeDisplayResponse + (*UpsertAchievementDefinitionRequest)(nil), // 201: hyapp.activity.v1.UpsertAchievementDefinitionRequest + (*UpsertAchievementDefinitionResponse)(nil), // 202: hyapp.activity.v1.UpsertAchievementDefinitionResponse + (*DeleteAchievementDefinitionRequest)(nil), // 203: hyapp.activity.v1.DeleteAchievementDefinitionRequest + (*DeleteAchievementDefinitionResponse)(nil), // 204: hyapp.activity.v1.DeleteAchievementDefinitionResponse + (*WheelPrizeTier)(nil), // 205: hyapp.activity.v1.WheelPrizeTier + (*WheelRuleConfig)(nil), // 206: hyapp.activity.v1.WheelRuleConfig + (*WheelDrawMeta)(nil), // 207: hyapp.activity.v1.WheelDrawMeta + (*WheelDrawResult)(nil), // 208: hyapp.activity.v1.WheelDrawResult + (*WheelDrawReward)(nil), // 209: hyapp.activity.v1.WheelDrawReward + (*ExecuteWheelDrawRequest)(nil), // 210: hyapp.activity.v1.ExecuteWheelDrawRequest + (*ExecuteWheelDrawResponse)(nil), // 211: hyapp.activity.v1.ExecuteWheelDrawResponse + (*GetWheelConfigRequest)(nil), // 212: hyapp.activity.v1.GetWheelConfigRequest + (*GetWheelConfigResponse)(nil), // 213: hyapp.activity.v1.GetWheelConfigResponse + (*UpsertWheelConfigRequest)(nil), // 214: hyapp.activity.v1.UpsertWheelConfigRequest + (*UpsertWheelConfigResponse)(nil), // 215: hyapp.activity.v1.UpsertWheelConfigResponse + (*ListWheelDrawsRequest)(nil), // 216: hyapp.activity.v1.ListWheelDrawsRequest + (*ListWheelDrawsResponse)(nil), // 217: hyapp.activity.v1.ListWheelDrawsResponse + (*WheelDrawSummary)(nil), // 218: hyapp.activity.v1.WheelDrawSummary + (*GetWheelDrawSummaryRequest)(nil), // 219: hyapp.activity.v1.GetWheelDrawSummaryRequest + (*GetWheelDrawSummaryResponse)(nil), // 220: hyapp.activity.v1.GetWheelDrawSummaryResponse + (*WeeklyStarGift)(nil), // 221: hyapp.activity.v1.WeeklyStarGift + (*WeeklyStarReward)(nil), // 222: hyapp.activity.v1.WeeklyStarReward + (*WeeklyStarCycle)(nil), // 223: hyapp.activity.v1.WeeklyStarCycle + (*WeeklyStarLeaderboardEntry)(nil), // 224: hyapp.activity.v1.WeeklyStarLeaderboardEntry + (*WeeklyStarSettlement)(nil), // 225: hyapp.activity.v1.WeeklyStarSettlement + (*ListWeeklyStarCyclesRequest)(nil), // 226: hyapp.activity.v1.ListWeeklyStarCyclesRequest + (*ListWeeklyStarCyclesResponse)(nil), // 227: hyapp.activity.v1.ListWeeklyStarCyclesResponse + (*GetWeeklyStarCycleRequest)(nil), // 228: hyapp.activity.v1.GetWeeklyStarCycleRequest + (*GetWeeklyStarCycleResponse)(nil), // 229: hyapp.activity.v1.GetWeeklyStarCycleResponse + (*UpsertWeeklyStarCycleRequest)(nil), // 230: hyapp.activity.v1.UpsertWeeklyStarCycleRequest + (*UpsertWeeklyStarCycleResponse)(nil), // 231: hyapp.activity.v1.UpsertWeeklyStarCycleResponse + (*SetWeeklyStarCycleStatusRequest)(nil), // 232: hyapp.activity.v1.SetWeeklyStarCycleStatusRequest + (*SetWeeklyStarCycleStatusResponse)(nil), // 233: hyapp.activity.v1.SetWeeklyStarCycleStatusResponse + (*ListWeeklyStarLeaderboardRequest)(nil), // 234: hyapp.activity.v1.ListWeeklyStarLeaderboardRequest + (*ListWeeklyStarLeaderboardResponse)(nil), // 235: hyapp.activity.v1.ListWeeklyStarLeaderboardResponse + (*ListWeeklyStarSettlementsRequest)(nil), // 236: hyapp.activity.v1.ListWeeklyStarSettlementsRequest + (*ListWeeklyStarSettlementsResponse)(nil), // 237: hyapp.activity.v1.ListWeeklyStarSettlementsResponse + (*GetWeeklyStarCurrentRequest)(nil), // 238: hyapp.activity.v1.GetWeeklyStarCurrentRequest + (*GetWeeklyStarCurrentResponse)(nil), // 239: hyapp.activity.v1.GetWeeklyStarCurrentResponse + (*ListWeeklyStarHistoryRequest)(nil), // 240: hyapp.activity.v1.ListWeeklyStarHistoryRequest + (*WeeklyStarHistoryCycle)(nil), // 241: hyapp.activity.v1.WeeklyStarHistoryCycle + (*ListWeeklyStarHistoryResponse)(nil), // 242: hyapp.activity.v1.ListWeeklyStarHistoryResponse + (*CPWeeklyRankReward)(nil), // 243: hyapp.activity.v1.CPWeeklyRankReward + (*CPWeeklyRankConfig)(nil), // 244: hyapp.activity.v1.CPWeeklyRankConfig + (*CPWeeklyRankUser)(nil), // 245: hyapp.activity.v1.CPWeeklyRankUser + (*CPWeeklyRankEntry)(nil), // 246: hyapp.activity.v1.CPWeeklyRankEntry + (*CPWeeklyRankSettlement)(nil), // 247: hyapp.activity.v1.CPWeeklyRankSettlement + (*GetCPWeeklyRankStatusRequest)(nil), // 248: hyapp.activity.v1.GetCPWeeklyRankStatusRequest + (*GetCPWeeklyRankStatusResponse)(nil), // 249: hyapp.activity.v1.GetCPWeeklyRankStatusResponse + (*GetCPWeeklyRankConfigRequest)(nil), // 250: hyapp.activity.v1.GetCPWeeklyRankConfigRequest + (*GetCPWeeklyRankConfigResponse)(nil), // 251: hyapp.activity.v1.GetCPWeeklyRankConfigResponse + (*UpdateCPWeeklyRankConfigRequest)(nil), // 252: hyapp.activity.v1.UpdateCPWeeklyRankConfigRequest + (*UpdateCPWeeklyRankConfigResponse)(nil), // 253: hyapp.activity.v1.UpdateCPWeeklyRankConfigResponse + (*ListCPWeeklyRankSettlementsRequest)(nil), // 254: hyapp.activity.v1.ListCPWeeklyRankSettlementsRequest + (*ListCPWeeklyRankSettlementsResponse)(nil), // 255: hyapp.activity.v1.ListCPWeeklyRankSettlementsResponse + (*AgencyOpeningReward)(nil), // 256: hyapp.activity.v1.AgencyOpeningReward + (*AgencyOpeningCycle)(nil), // 257: hyapp.activity.v1.AgencyOpeningCycle + (*AgencyOpeningApplication)(nil), // 258: hyapp.activity.v1.AgencyOpeningApplication + (*AgencyOpeningAgencySnapshot)(nil), // 259: hyapp.activity.v1.AgencyOpeningAgencySnapshot + (*ListAgencyOpeningCyclesRequest)(nil), // 260: hyapp.activity.v1.ListAgencyOpeningCyclesRequest + (*ListAgencyOpeningCyclesResponse)(nil), // 261: hyapp.activity.v1.ListAgencyOpeningCyclesResponse + (*GetAgencyOpeningCycleRequest)(nil), // 262: hyapp.activity.v1.GetAgencyOpeningCycleRequest + (*GetAgencyOpeningCycleResponse)(nil), // 263: hyapp.activity.v1.GetAgencyOpeningCycleResponse + (*UpsertAgencyOpeningCycleRequest)(nil), // 264: hyapp.activity.v1.UpsertAgencyOpeningCycleRequest + (*UpsertAgencyOpeningCycleResponse)(nil), // 265: hyapp.activity.v1.UpsertAgencyOpeningCycleResponse + (*SetAgencyOpeningCycleStatusRequest)(nil), // 266: hyapp.activity.v1.SetAgencyOpeningCycleStatusRequest + (*SetAgencyOpeningCycleStatusResponse)(nil), // 267: hyapp.activity.v1.SetAgencyOpeningCycleStatusResponse + (*ListAgencyOpeningApplicationsRequest)(nil), // 268: hyapp.activity.v1.ListAgencyOpeningApplicationsRequest + (*ListAgencyOpeningApplicationsResponse)(nil), // 269: hyapp.activity.v1.ListAgencyOpeningApplicationsResponse + (*ReviewAgencyOpeningApplicationRequest)(nil), // 270: hyapp.activity.v1.ReviewAgencyOpeningApplicationRequest + (*ReviewAgencyOpeningApplicationResponse)(nil), // 271: hyapp.activity.v1.ReviewAgencyOpeningApplicationResponse + (*GetAgencyOpeningStatusRequest)(nil), // 272: hyapp.activity.v1.GetAgencyOpeningStatusRequest + (*GetAgencyOpeningStatusResponse)(nil), // 273: hyapp.activity.v1.GetAgencyOpeningStatusResponse + (*ApplyAgencyOpeningRequest)(nil), // 274: hyapp.activity.v1.ApplyAgencyOpeningRequest + (*ApplyAgencyOpeningResponse)(nil), // 275: hyapp.activity.v1.ApplyAgencyOpeningResponse + (*v1.EventEnvelope)(nil), // 276: hyapp.events.room.v1.EventEnvelope } var file_proto_activity_v1_activity_proto_depIdxs = []int32{ 0, // 0: hyapp.activity.v1.PingActivityRequest.meta:type_name -> hyapp.activity.v1.RequestMeta @@ -26427,7 +27083,7 @@ var file_proto_activity_v1_activity_proto_depIdxs = []int32{ 0, // 27: hyapp.activity.v1.PublishGlobalBroadcastRequest.meta:type_name -> hyapp.activity.v1.RequestMeta 0, // 28: hyapp.activity.v1.RemoveRegionBroadcastMemberRequest.meta:type_name -> hyapp.activity.v1.RequestMeta 0, // 29: hyapp.activity.v1.ConsumeRoomEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 269, // 30: hyapp.activity.v1.ConsumeRoomEventRequest.envelope:type_name -> hyapp.events.room.v1.EventEnvelope + 276, // 30: hyapp.activity.v1.ConsumeRoomEventRequest.envelope:type_name -> hyapp.events.room.v1.EventEnvelope 0, // 31: hyapp.activity.v1.ListTaskDefinitionsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta 51, // 32: hyapp.activity.v1.ListTaskDefinitionsResponse.tasks:type_name -> hyapp.activity.v1.TaskDefinition 0, // 33: hyapp.activity.v1.UpsertTaskDefinitionRequest.meta:type_name -> hyapp.activity.v1.RequestMeta @@ -26538,336 +27194,348 @@ var file_proto_activity_v1_activity_proto_depIdxs = []int32{ 164, // 138: hyapp.activity.v1.ListLevelRewardsResponse.rewards:type_name -> hyapp.activity.v1.LevelRewardJob 0, // 139: hyapp.activity.v1.ConsumeLevelEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta 0, // 140: hyapp.activity.v1.SetUserLevelRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 0, // 141: hyapp.activity.v1.IssueRegistrationLevelBadgesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 171, // 142: hyapp.activity.v1.IssueRegistrationLevelBadgesResponse.grants:type_name -> hyapp.activity.v1.RegistrationLevelBadgeGrant - 0, // 143: hyapp.activity.v1.UpsertLevelTrackRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 152, // 144: hyapp.activity.v1.UpsertLevelTrackResponse.track:type_name -> hyapp.activity.v1.LevelTrack - 0, // 145: hyapp.activity.v1.UpsertLevelRuleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 153, // 146: hyapp.activity.v1.UpsertLevelRuleResponse.rule:type_name -> hyapp.activity.v1.LevelRule - 0, // 147: hyapp.activity.v1.UpsertLevelTierRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 154, // 148: hyapp.activity.v1.UpsertLevelTierResponse.tier:type_name -> hyapp.activity.v1.LevelTier - 0, // 149: hyapp.activity.v1.ListLevelConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 152, // 150: hyapp.activity.v1.ListLevelConfigResponse.tracks:type_name -> hyapp.activity.v1.LevelTrack - 153, // 151: hyapp.activity.v1.ListLevelConfigResponse.rules:type_name -> hyapp.activity.v1.LevelRule - 154, // 152: hyapp.activity.v1.ListLevelConfigResponse.tiers:type_name -> hyapp.activity.v1.LevelTier - 182, // 153: hyapp.activity.v1.AchievementDefinition.conditions:type_name -> hyapp.activity.v1.AchievementCondition - 183, // 154: hyapp.activity.v1.UserAchievement.definition:type_name -> hyapp.activity.v1.AchievementDefinition - 0, // 155: hyapp.activity.v1.ListAchievementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 184, // 156: hyapp.activity.v1.ListAchievementsResponse.achievements:type_name -> hyapp.activity.v1.UserAchievement - 0, // 157: hyapp.activity.v1.ConsumeAchievementEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 0, // 158: hyapp.activity.v1.ListMyBadgesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 189, // 159: hyapp.activity.v1.ListMyBadgesResponse.strip_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem - 189, // 160: hyapp.activity.v1.ListMyBadgesResponse.profile_tile_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem - 189, // 161: hyapp.activity.v1.ListMyBadgesResponse.honor_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem - 0, // 162: hyapp.activity.v1.SetBadgeDisplayRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 189, // 163: hyapp.activity.v1.SetBadgeDisplayRequest.items:type_name -> hyapp.activity.v1.BadgeDisplayItem - 191, // 164: hyapp.activity.v1.SetBadgeDisplayResponse.profile:type_name -> hyapp.activity.v1.ListMyBadgesResponse - 0, // 165: hyapp.activity.v1.UpsertAchievementDefinitionRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 182, // 166: hyapp.activity.v1.UpsertAchievementDefinitionRequest.conditions:type_name -> hyapp.activity.v1.AchievementCondition - 183, // 167: hyapp.activity.v1.UpsertAchievementDefinitionResponse.achievement:type_name -> hyapp.activity.v1.AchievementDefinition - 0, // 168: hyapp.activity.v1.DeleteAchievementDefinitionRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 183, // 169: hyapp.activity.v1.DeleteAchievementDefinitionResponse.achievement:type_name -> hyapp.activity.v1.AchievementDefinition - 198, // 170: hyapp.activity.v1.WheelRuleConfig.tiers:type_name -> hyapp.activity.v1.WheelPrizeTier - 0, // 171: hyapp.activity.v1.WheelDrawMeta.meta:type_name -> hyapp.activity.v1.RequestMeta - 202, // 172: hyapp.activity.v1.WheelDrawResult.rewards:type_name -> hyapp.activity.v1.WheelDrawReward - 200, // 173: hyapp.activity.v1.ExecuteWheelDrawRequest.wheel:type_name -> hyapp.activity.v1.WheelDrawMeta - 201, // 174: hyapp.activity.v1.ExecuteWheelDrawResponse.result:type_name -> hyapp.activity.v1.WheelDrawResult - 0, // 175: hyapp.activity.v1.GetWheelConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 199, // 176: hyapp.activity.v1.GetWheelConfigResponse.config:type_name -> hyapp.activity.v1.WheelRuleConfig - 0, // 177: hyapp.activity.v1.UpsertWheelConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 199, // 178: hyapp.activity.v1.UpsertWheelConfigRequest.config:type_name -> hyapp.activity.v1.WheelRuleConfig - 199, // 179: hyapp.activity.v1.UpsertWheelConfigResponse.config:type_name -> hyapp.activity.v1.WheelRuleConfig - 0, // 180: hyapp.activity.v1.ListWheelDrawsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 201, // 181: hyapp.activity.v1.ListWheelDrawsResponse.draws:type_name -> hyapp.activity.v1.WheelDrawResult - 0, // 182: hyapp.activity.v1.GetWheelDrawSummaryRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 211, // 183: hyapp.activity.v1.GetWheelDrawSummaryResponse.summary:type_name -> hyapp.activity.v1.WheelDrawSummary - 214, // 184: hyapp.activity.v1.WeeklyStarCycle.gifts:type_name -> hyapp.activity.v1.WeeklyStarGift - 215, // 185: hyapp.activity.v1.WeeklyStarCycle.rewards:type_name -> hyapp.activity.v1.WeeklyStarReward - 0, // 186: hyapp.activity.v1.ListWeeklyStarCyclesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 216, // 187: hyapp.activity.v1.ListWeeklyStarCyclesResponse.cycles:type_name -> hyapp.activity.v1.WeeklyStarCycle - 0, // 188: hyapp.activity.v1.GetWeeklyStarCycleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 216, // 189: hyapp.activity.v1.GetWeeklyStarCycleResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 0, // 190: hyapp.activity.v1.UpsertWeeklyStarCycleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 216, // 191: hyapp.activity.v1.UpsertWeeklyStarCycleRequest.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 216, // 192: hyapp.activity.v1.UpsertWeeklyStarCycleResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 0, // 193: hyapp.activity.v1.SetWeeklyStarCycleStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 216, // 194: hyapp.activity.v1.SetWeeklyStarCycleStatusResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 0, // 195: hyapp.activity.v1.ListWeeklyStarLeaderboardRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 216, // 196: hyapp.activity.v1.ListWeeklyStarLeaderboardResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 217, // 197: hyapp.activity.v1.ListWeeklyStarLeaderboardResponse.entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry - 0, // 198: hyapp.activity.v1.ListWeeklyStarSettlementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 218, // 199: hyapp.activity.v1.ListWeeklyStarSettlementsResponse.settlements:type_name -> hyapp.activity.v1.WeeklyStarSettlement - 0, // 200: hyapp.activity.v1.GetWeeklyStarCurrentRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 216, // 201: hyapp.activity.v1.GetWeeklyStarCurrentResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 217, // 202: hyapp.activity.v1.GetWeeklyStarCurrentResponse.top_entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry - 217, // 203: hyapp.activity.v1.GetWeeklyStarCurrentResponse.my_entry:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry - 0, // 204: hyapp.activity.v1.ListWeeklyStarHistoryRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 216, // 205: hyapp.activity.v1.WeeklyStarHistoryCycle.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 217, // 206: hyapp.activity.v1.WeeklyStarHistoryCycle.top_entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry - 234, // 207: hyapp.activity.v1.ListWeeklyStarHistoryResponse.cycles:type_name -> hyapp.activity.v1.WeeklyStarHistoryCycle - 236, // 208: hyapp.activity.v1.CPWeeklyRankConfig.rewards:type_name -> hyapp.activity.v1.CPWeeklyRankReward - 238, // 209: hyapp.activity.v1.CPWeeklyRankEntry.user_a:type_name -> hyapp.activity.v1.CPWeeklyRankUser - 238, // 210: hyapp.activity.v1.CPWeeklyRankEntry.user_b:type_name -> hyapp.activity.v1.CPWeeklyRankUser - 0, // 211: hyapp.activity.v1.GetCPWeeklyRankStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 237, // 212: hyapp.activity.v1.GetCPWeeklyRankStatusResponse.config:type_name -> hyapp.activity.v1.CPWeeklyRankConfig - 239, // 213: hyapp.activity.v1.GetCPWeeklyRankStatusResponse.entries:type_name -> hyapp.activity.v1.CPWeeklyRankEntry - 239, // 214: hyapp.activity.v1.GetCPWeeklyRankStatusResponse.previous_entries:type_name -> hyapp.activity.v1.CPWeeklyRankEntry - 0, // 215: hyapp.activity.v1.GetCPWeeklyRankConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 237, // 216: hyapp.activity.v1.GetCPWeeklyRankConfigResponse.config:type_name -> hyapp.activity.v1.CPWeeklyRankConfig - 0, // 217: hyapp.activity.v1.UpdateCPWeeklyRankConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 237, // 218: hyapp.activity.v1.UpdateCPWeeklyRankConfigRequest.config:type_name -> hyapp.activity.v1.CPWeeklyRankConfig - 237, // 219: hyapp.activity.v1.UpdateCPWeeklyRankConfigResponse.config:type_name -> hyapp.activity.v1.CPWeeklyRankConfig - 0, // 220: hyapp.activity.v1.ListCPWeeklyRankSettlementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 240, // 221: hyapp.activity.v1.ListCPWeeklyRankSettlementsResponse.settlements:type_name -> hyapp.activity.v1.CPWeeklyRankSettlement - 249, // 222: hyapp.activity.v1.AgencyOpeningCycle.rewards:type_name -> hyapp.activity.v1.AgencyOpeningReward - 0, // 223: hyapp.activity.v1.ListAgencyOpeningCyclesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 250, // 224: hyapp.activity.v1.ListAgencyOpeningCyclesResponse.cycles:type_name -> hyapp.activity.v1.AgencyOpeningCycle - 0, // 225: hyapp.activity.v1.GetAgencyOpeningCycleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 250, // 226: hyapp.activity.v1.GetAgencyOpeningCycleResponse.cycle:type_name -> hyapp.activity.v1.AgencyOpeningCycle - 0, // 227: hyapp.activity.v1.UpsertAgencyOpeningCycleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 250, // 228: hyapp.activity.v1.UpsertAgencyOpeningCycleRequest.cycle:type_name -> hyapp.activity.v1.AgencyOpeningCycle - 250, // 229: hyapp.activity.v1.UpsertAgencyOpeningCycleResponse.cycle:type_name -> hyapp.activity.v1.AgencyOpeningCycle - 0, // 230: hyapp.activity.v1.SetAgencyOpeningCycleStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 250, // 231: hyapp.activity.v1.SetAgencyOpeningCycleStatusResponse.cycle:type_name -> hyapp.activity.v1.AgencyOpeningCycle - 0, // 232: hyapp.activity.v1.ListAgencyOpeningApplicationsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 251, // 233: hyapp.activity.v1.ListAgencyOpeningApplicationsResponse.applications:type_name -> hyapp.activity.v1.AgencyOpeningApplication - 0, // 234: hyapp.activity.v1.ReviewAgencyOpeningApplicationRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 251, // 235: hyapp.activity.v1.ReviewAgencyOpeningApplicationResponse.application:type_name -> hyapp.activity.v1.AgencyOpeningApplication - 0, // 236: hyapp.activity.v1.GetAgencyOpeningStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 250, // 237: hyapp.activity.v1.GetAgencyOpeningStatusResponse.cycle:type_name -> hyapp.activity.v1.AgencyOpeningCycle - 251, // 238: hyapp.activity.v1.GetAgencyOpeningStatusResponse.application:type_name -> hyapp.activity.v1.AgencyOpeningApplication - 252, // 239: hyapp.activity.v1.GetAgencyOpeningStatusResponse.agency:type_name -> hyapp.activity.v1.AgencyOpeningAgencySnapshot - 0, // 240: hyapp.activity.v1.ApplyAgencyOpeningRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 251, // 241: hyapp.activity.v1.ApplyAgencyOpeningResponse.application:type_name -> hyapp.activity.v1.AgencyOpeningApplication - 1, // 242: hyapp.activity.v1.ActivityService.PingActivity:input_type -> hyapp.activity.v1.PingActivityRequest - 3, // 243: hyapp.activity.v1.ActivityService.GetActivityStatus:input_type -> hyapp.activity.v1.GetActivityStatusRequest - 6, // 244: hyapp.activity.v1.MessageInboxService.ListMessageTabs:input_type -> hyapp.activity.v1.ListMessageTabsRequest - 9, // 245: hyapp.activity.v1.MessageInboxService.ListInboxMessages:input_type -> hyapp.activity.v1.ListInboxMessagesRequest - 11, // 246: hyapp.activity.v1.MessageInboxService.MarkInboxMessageRead:input_type -> hyapp.activity.v1.MarkInboxMessageReadRequest - 13, // 247: hyapp.activity.v1.MessageInboxService.MarkInboxSectionRead:input_type -> hyapp.activity.v1.MarkInboxSectionReadRequest - 15, // 248: hyapp.activity.v1.MessageInboxService.DeleteInboxMessage:input_type -> hyapp.activity.v1.DeleteInboxMessageRequest - 17, // 249: hyapp.activity.v1.MessageInboxService.CreateInboxMessage:input_type -> hyapp.activity.v1.CreateInboxMessageRequest - 19, // 250: hyapp.activity.v1.MessageInboxService.CreateFanoutJob:input_type -> hyapp.activity.v1.CreateFanoutJobRequest - 22, // 251: hyapp.activity.v1.MessageActionConfirmService.AcceptActionConfirm:input_type -> hyapp.activity.v1.AcceptActionConfirmRequest - 24, // 252: hyapp.activity.v1.MessageActionConfirmService.RejectActionConfirm:input_type -> hyapp.activity.v1.RejectActionConfirmRequest - 26, // 253: hyapp.activity.v1.MessageActionConfirmService.BatchGetActionConfirmStatus:input_type -> hyapp.activity.v1.BatchGetActionConfirmStatusRequest - 28, // 254: hyapp.activity.v1.MessageActionConfirmService.ListConversationActionConfirms:input_type -> hyapp.activity.v1.ListConversationActionConfirmsRequest - 30, // 255: hyapp.activity.v1.ActivityCronService.ProcessMessageFanoutBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 30, // 256: hyapp.activity.v1.ActivityCronService.ProcessLevelRewardBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 30, // 257: hyapp.activity.v1.ActivityCronService.ProcessAchievementRewardBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 30, // 258: hyapp.activity.v1.ActivityCronService.ProcessRoomTurnoverRewardSettlementBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 30, // 259: hyapp.activity.v1.ActivityCronService.ProcessWeeklyStarSettlementBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 30, // 260: hyapp.activity.v1.ActivityCronService.ProcessCPWeeklyRankSettlementBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 30, // 261: hyapp.activity.v1.ActivityCronService.ProcessAgencyOpeningSettlementBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 34, // 262: hyapp.activity.v1.TaskService.ListUserTasks:input_type -> hyapp.activity.v1.ListUserTasksRequest - 36, // 263: hyapp.activity.v1.TaskService.ClaimTaskReward:input_type -> hyapp.activity.v1.ClaimTaskRewardRequest - 38, // 264: hyapp.activity.v1.TaskService.ConsumeTaskEvent:input_type -> hyapp.activity.v1.ConsumeTaskEventRequest - 156, // 265: hyapp.activity.v1.GrowthLevelService.GetMyLevelOverview:input_type -> hyapp.activity.v1.GetMyLevelOverviewRequest - 158, // 266: hyapp.activity.v1.GrowthLevelService.GetLevelTrack:input_type -> hyapp.activity.v1.GetLevelTrackRequest - 162, // 267: hyapp.activity.v1.GrowthLevelService.BatchGetUserLevelDisplayProfiles:input_type -> hyapp.activity.v1.BatchGetUserLevelDisplayProfilesRequest - 165, // 268: hyapp.activity.v1.GrowthLevelService.ListLevelRewards:input_type -> hyapp.activity.v1.ListLevelRewardsRequest - 167, // 269: hyapp.activity.v1.GrowthLevelService.ConsumeLevelEvent:input_type -> hyapp.activity.v1.ConsumeLevelEventRequest - 169, // 270: hyapp.activity.v1.GrowthLevelService.SetUserLevel:input_type -> hyapp.activity.v1.SetUserLevelRequest - 172, // 271: hyapp.activity.v1.GrowthLevelService.IssueRegistrationLevelBadges:input_type -> hyapp.activity.v1.IssueRegistrationLevelBadgesRequest - 185, // 272: hyapp.activity.v1.AchievementService.ListAchievements:input_type -> hyapp.activity.v1.ListAchievementsRequest - 187, // 273: hyapp.activity.v1.AchievementService.ConsumeAchievementEvent:input_type -> hyapp.activity.v1.ConsumeAchievementEventRequest - 190, // 274: hyapp.activity.v1.AchievementService.ListMyBadges:input_type -> hyapp.activity.v1.ListMyBadgesRequest - 192, // 275: hyapp.activity.v1.AchievementService.SetBadgeDisplay:input_type -> hyapp.activity.v1.SetBadgeDisplayRequest - 203, // 276: hyapp.activity.v1.WheelService.ExecuteWheelDraw:input_type -> hyapp.activity.v1.ExecuteWheelDrawRequest - 128, // 277: hyapp.activity.v1.RoomTurnoverRewardService.GetRoomTurnoverRewardStatus:input_type -> hyapp.activity.v1.GetRoomTurnoverRewardStatusRequest - 231, // 278: hyapp.activity.v1.WeeklyStarService.GetWeeklyStarCurrent:input_type -> hyapp.activity.v1.GetWeeklyStarCurrentRequest - 227, // 279: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarLeaderboard:input_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardRequest - 233, // 280: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarHistory:input_type -> hyapp.activity.v1.ListWeeklyStarHistoryRequest - 241, // 281: hyapp.activity.v1.CPWeeklyRankService.GetCPWeeklyRankStatus:input_type -> hyapp.activity.v1.GetCPWeeklyRankStatusRequest - 265, // 282: hyapp.activity.v1.AgencyOpeningService.GetAgencyOpeningStatus:input_type -> hyapp.activity.v1.GetAgencyOpeningStatusRequest - 267, // 283: hyapp.activity.v1.AgencyOpeningService.ApplyAgencyOpening:input_type -> hyapp.activity.v1.ApplyAgencyOpeningRequest - 261, // 284: hyapp.activity.v1.AgencyOpeningService.ListAgencyOpeningLeaderboard:input_type -> hyapp.activity.v1.ListAgencyOpeningApplicationsRequest - 41, // 285: hyapp.activity.v1.BroadcastService.EnsureBroadcastGroups:input_type -> hyapp.activity.v1.EnsureBroadcastGroupsRequest - 43, // 286: hyapp.activity.v1.BroadcastService.PublishRegionBroadcast:input_type -> hyapp.activity.v1.PublishRegionBroadcastRequest - 44, // 287: hyapp.activity.v1.BroadcastService.PublishGlobalBroadcast:input_type -> hyapp.activity.v1.PublishGlobalBroadcastRequest - 46, // 288: hyapp.activity.v1.BroadcastService.RemoveRegionBroadcastMember:input_type -> hyapp.activity.v1.RemoveRegionBroadcastMemberRequest - 30, // 289: hyapp.activity.v1.BroadcastService.ProcessBroadcastOutboxBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 49, // 290: hyapp.activity.v1.RoomEventConsumerService.ConsumeRoomEvent:input_type -> hyapp.activity.v1.ConsumeRoomEventRequest - 52, // 291: hyapp.activity.v1.AdminTaskService.ListTaskDefinitions:input_type -> hyapp.activity.v1.ListTaskDefinitionsRequest - 54, // 292: hyapp.activity.v1.AdminTaskService.UpsertTaskDefinition:input_type -> hyapp.activity.v1.UpsertTaskDefinitionRequest - 56, // 293: hyapp.activity.v1.AdminTaskService.SetTaskDefinitionStatus:input_type -> hyapp.activity.v1.SetTaskDefinitionStatusRequest - 58, // 294: hyapp.activity.v1.AdminTaskService.PublishTaskRewardPolicy:input_type -> hyapp.activity.v1.PublishTaskRewardPolicyRequest - 63, // 295: hyapp.activity.v1.RegistrationRewardService.GetRegistrationRewardEligibility:input_type -> hyapp.activity.v1.GetRegistrationRewardEligibilityRequest - 65, // 296: hyapp.activity.v1.RegistrationRewardService.IssueRegistrationReward:input_type -> hyapp.activity.v1.IssueRegistrationRewardRequest - 67, // 297: hyapp.activity.v1.AdminRegistrationRewardService.GetRegistrationRewardConfig:input_type -> hyapp.activity.v1.GetRegistrationRewardConfigRequest - 69, // 298: hyapp.activity.v1.AdminRegistrationRewardService.UpdateRegistrationRewardConfig:input_type -> hyapp.activity.v1.UpdateRegistrationRewardConfigRequest - 71, // 299: hyapp.activity.v1.AdminRegistrationRewardService.ListRegistrationRewardClaims:input_type -> hyapp.activity.v1.ListRegistrationRewardClaimsRequest - 77, // 300: hyapp.activity.v1.FirstRechargeRewardService.GetFirstRechargeRewardStatus:input_type -> hyapp.activity.v1.GetFirstRechargeRewardStatusRequest - 79, // 301: hyapp.activity.v1.FirstRechargeRewardService.ConsumeFirstRechargeReward:input_type -> hyapp.activity.v1.ConsumeFirstRechargeRewardRequest - 81, // 302: hyapp.activity.v1.AdminFirstRechargeRewardService.GetFirstRechargeRewardConfig:input_type -> hyapp.activity.v1.GetFirstRechargeRewardConfigRequest - 83, // 303: hyapp.activity.v1.AdminFirstRechargeRewardService.UpdateFirstRechargeRewardConfig:input_type -> hyapp.activity.v1.UpdateFirstRechargeRewardConfigRequest - 85, // 304: hyapp.activity.v1.AdminFirstRechargeRewardService.ListFirstRechargeRewardClaims:input_type -> hyapp.activity.v1.ListFirstRechargeRewardClaimsRequest - 92, // 305: hyapp.activity.v1.CumulativeRechargeRewardService.GetCumulativeRechargeRewardStatus:input_type -> hyapp.activity.v1.GetCumulativeRechargeRewardStatusRequest - 94, // 306: hyapp.activity.v1.CumulativeRechargeRewardService.ConsumeCumulativeRechargeReward:input_type -> hyapp.activity.v1.ConsumeCumulativeRechargeRewardRequest - 107, // 307: hyapp.activity.v1.InviteActivityRewardService.GetInviteActivityRewardStatus:input_type -> hyapp.activity.v1.GetInviteActivityRewardStatusRequest - 109, // 308: hyapp.activity.v1.InviteActivityRewardService.ClaimInviteActivityReward:input_type -> hyapp.activity.v1.ClaimInviteActivityRewardRequest - 112, // 309: hyapp.activity.v1.InviteActivityRewardService.ListInviteActivityLeaderboard:input_type -> hyapp.activity.v1.ListInviteActivityLeaderboardRequest - 96, // 310: hyapp.activity.v1.AdminCumulativeRechargeRewardService.GetCumulativeRechargeRewardConfig:input_type -> hyapp.activity.v1.GetCumulativeRechargeRewardConfigRequest - 98, // 311: hyapp.activity.v1.AdminCumulativeRechargeRewardService.UpdateCumulativeRechargeRewardConfig:input_type -> hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigRequest - 100, // 312: hyapp.activity.v1.AdminCumulativeRechargeRewardService.ListCumulativeRechargeRewardGrants:input_type -> hyapp.activity.v1.ListCumulativeRechargeRewardGrantsRequest - 114, // 313: hyapp.activity.v1.AdminInviteActivityRewardService.GetInviteActivityRewardConfig:input_type -> hyapp.activity.v1.GetInviteActivityRewardConfigRequest - 116, // 314: hyapp.activity.v1.AdminInviteActivityRewardService.UpdateInviteActivityRewardConfig:input_type -> hyapp.activity.v1.UpdateInviteActivityRewardConfigRequest - 118, // 315: hyapp.activity.v1.AdminInviteActivityRewardService.ListInviteActivityRewardClaims:input_type -> hyapp.activity.v1.ListInviteActivityRewardClaimsRequest - 122, // 316: hyapp.activity.v1.AdminInviteActivityRewardService.ListInviteActivityRewardSummaries:input_type -> hyapp.activity.v1.ListInviteActivityRewardSummariesRequest - 130, // 317: hyapp.activity.v1.AdminRoomTurnoverRewardService.GetRoomTurnoverRewardConfig:input_type -> hyapp.activity.v1.GetRoomTurnoverRewardConfigRequest - 132, // 318: hyapp.activity.v1.AdminRoomTurnoverRewardService.UpdateRoomTurnoverRewardConfig:input_type -> hyapp.activity.v1.UpdateRoomTurnoverRewardConfigRequest - 134, // 319: hyapp.activity.v1.AdminRoomTurnoverRewardService.ListRoomTurnoverRewardSettlements:input_type -> hyapp.activity.v1.ListRoomTurnoverRewardSettlementsRequest - 136, // 320: hyapp.activity.v1.AdminRoomTurnoverRewardService.RetryRoomTurnoverRewardSettlement:input_type -> hyapp.activity.v1.RetryRoomTurnoverRewardSettlementRequest - 219, // 321: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarCycles:input_type -> hyapp.activity.v1.ListWeeklyStarCyclesRequest - 223, // 322: hyapp.activity.v1.AdminWeeklyStarService.CreateWeeklyStarCycle:input_type -> hyapp.activity.v1.UpsertWeeklyStarCycleRequest - 221, // 323: hyapp.activity.v1.AdminWeeklyStarService.GetWeeklyStarCycle:input_type -> hyapp.activity.v1.GetWeeklyStarCycleRequest - 223, // 324: hyapp.activity.v1.AdminWeeklyStarService.UpdateWeeklyStarCycle:input_type -> hyapp.activity.v1.UpsertWeeklyStarCycleRequest - 225, // 325: hyapp.activity.v1.AdminWeeklyStarService.SetWeeklyStarCycleStatus:input_type -> hyapp.activity.v1.SetWeeklyStarCycleStatusRequest - 227, // 326: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarLeaderboard:input_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardRequest - 229, // 327: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarSettlements:input_type -> hyapp.activity.v1.ListWeeklyStarSettlementsRequest - 243, // 328: hyapp.activity.v1.AdminCPWeeklyRankService.GetCPWeeklyRankConfig:input_type -> hyapp.activity.v1.GetCPWeeklyRankConfigRequest - 245, // 329: hyapp.activity.v1.AdminCPWeeklyRankService.UpdateCPWeeklyRankConfig:input_type -> hyapp.activity.v1.UpdateCPWeeklyRankConfigRequest - 247, // 330: hyapp.activity.v1.AdminCPWeeklyRankService.ListCPWeeklyRankSettlements:input_type -> hyapp.activity.v1.ListCPWeeklyRankSettlementsRequest - 253, // 331: hyapp.activity.v1.AdminAgencyOpeningService.ListAgencyOpeningCycles:input_type -> hyapp.activity.v1.ListAgencyOpeningCyclesRequest - 257, // 332: hyapp.activity.v1.AdminAgencyOpeningService.CreateAgencyOpeningCycle:input_type -> hyapp.activity.v1.UpsertAgencyOpeningCycleRequest - 255, // 333: hyapp.activity.v1.AdminAgencyOpeningService.GetAgencyOpeningCycle:input_type -> hyapp.activity.v1.GetAgencyOpeningCycleRequest - 257, // 334: hyapp.activity.v1.AdminAgencyOpeningService.UpdateAgencyOpeningCycle:input_type -> hyapp.activity.v1.UpsertAgencyOpeningCycleRequest - 259, // 335: hyapp.activity.v1.AdminAgencyOpeningService.SetAgencyOpeningCycleStatus:input_type -> hyapp.activity.v1.SetAgencyOpeningCycleStatusRequest - 261, // 336: hyapp.activity.v1.AdminAgencyOpeningService.ListAgencyOpeningApplications:input_type -> hyapp.activity.v1.ListAgencyOpeningApplicationsRequest - 263, // 337: hyapp.activity.v1.AdminAgencyOpeningService.ReviewAgencyOpeningApplication:input_type -> hyapp.activity.v1.ReviewAgencyOpeningApplicationRequest - 141, // 338: hyapp.activity.v1.SevenDayCheckInService.GetSevenDayCheckInStatus:input_type -> hyapp.activity.v1.GetSevenDayCheckInStatusRequest - 143, // 339: hyapp.activity.v1.SevenDayCheckInService.SignSevenDayCheckIn:input_type -> hyapp.activity.v1.SignSevenDayCheckInRequest - 145, // 340: hyapp.activity.v1.AdminSevenDayCheckInService.GetSevenDayCheckInConfig:input_type -> hyapp.activity.v1.GetSevenDayCheckInConfigRequest - 147, // 341: hyapp.activity.v1.AdminSevenDayCheckInService.UpdateSevenDayCheckInConfig:input_type -> hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest - 150, // 342: hyapp.activity.v1.AdminSevenDayCheckInService.ListSevenDayCheckInClaims:input_type -> hyapp.activity.v1.ListSevenDayCheckInClaimsRequest - 180, // 343: hyapp.activity.v1.AdminGrowthLevelService.ListLevelConfig:input_type -> hyapp.activity.v1.ListLevelConfigRequest - 174, // 344: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTrack:input_type -> hyapp.activity.v1.UpsertLevelTrackRequest - 176, // 345: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelRule:input_type -> hyapp.activity.v1.UpsertLevelRuleRequest - 178, // 346: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTier:input_type -> hyapp.activity.v1.UpsertLevelTierRequest - 185, // 347: hyapp.activity.v1.AdminAchievementService.ListAchievementDefinitions:input_type -> hyapp.activity.v1.ListAchievementsRequest - 194, // 348: hyapp.activity.v1.AdminAchievementService.UpsertAchievementDefinition:input_type -> hyapp.activity.v1.UpsertAchievementDefinitionRequest - 196, // 349: hyapp.activity.v1.AdminAchievementService.DeleteAchievementDefinition:input_type -> hyapp.activity.v1.DeleteAchievementDefinitionRequest - 205, // 350: hyapp.activity.v1.AdminWheelService.GetWheelConfig:input_type -> hyapp.activity.v1.GetWheelConfigRequest - 207, // 351: hyapp.activity.v1.AdminWheelService.UpsertWheelConfig:input_type -> hyapp.activity.v1.UpsertWheelConfigRequest - 209, // 352: hyapp.activity.v1.AdminWheelService.ListWheelDraws:input_type -> hyapp.activity.v1.ListWheelDrawsRequest - 212, // 353: hyapp.activity.v1.AdminWheelService.GetWheelDrawSummary:input_type -> hyapp.activity.v1.GetWheelDrawSummaryRequest - 2, // 354: hyapp.activity.v1.ActivityService.PingActivity:output_type -> hyapp.activity.v1.PingActivityResponse - 4, // 355: hyapp.activity.v1.ActivityService.GetActivityStatus:output_type -> hyapp.activity.v1.GetActivityStatusResponse - 7, // 356: hyapp.activity.v1.MessageInboxService.ListMessageTabs:output_type -> hyapp.activity.v1.ListMessageTabsResponse - 10, // 357: hyapp.activity.v1.MessageInboxService.ListInboxMessages:output_type -> hyapp.activity.v1.ListInboxMessagesResponse - 12, // 358: hyapp.activity.v1.MessageInboxService.MarkInboxMessageRead:output_type -> hyapp.activity.v1.MarkInboxMessageReadResponse - 14, // 359: hyapp.activity.v1.MessageInboxService.MarkInboxSectionRead:output_type -> hyapp.activity.v1.MarkInboxSectionReadResponse - 16, // 360: hyapp.activity.v1.MessageInboxService.DeleteInboxMessage:output_type -> hyapp.activity.v1.DeleteInboxMessageResponse - 18, // 361: hyapp.activity.v1.MessageInboxService.CreateInboxMessage:output_type -> hyapp.activity.v1.CreateInboxMessageResponse - 20, // 362: hyapp.activity.v1.MessageInboxService.CreateFanoutJob:output_type -> hyapp.activity.v1.CreateFanoutJobResponse - 23, // 363: hyapp.activity.v1.MessageActionConfirmService.AcceptActionConfirm:output_type -> hyapp.activity.v1.AcceptActionConfirmResponse - 25, // 364: hyapp.activity.v1.MessageActionConfirmService.RejectActionConfirm:output_type -> hyapp.activity.v1.RejectActionConfirmResponse - 27, // 365: hyapp.activity.v1.MessageActionConfirmService.BatchGetActionConfirmStatus:output_type -> hyapp.activity.v1.BatchGetActionConfirmStatusResponse - 29, // 366: hyapp.activity.v1.MessageActionConfirmService.ListConversationActionConfirms:output_type -> hyapp.activity.v1.ListConversationActionConfirmsResponse - 31, // 367: hyapp.activity.v1.ActivityCronService.ProcessMessageFanoutBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 31, // 368: hyapp.activity.v1.ActivityCronService.ProcessLevelRewardBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 31, // 369: hyapp.activity.v1.ActivityCronService.ProcessAchievementRewardBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 31, // 370: hyapp.activity.v1.ActivityCronService.ProcessRoomTurnoverRewardSettlementBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 31, // 371: hyapp.activity.v1.ActivityCronService.ProcessWeeklyStarSettlementBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 31, // 372: hyapp.activity.v1.ActivityCronService.ProcessCPWeeklyRankSettlementBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 31, // 373: hyapp.activity.v1.ActivityCronService.ProcessAgencyOpeningSettlementBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 35, // 374: hyapp.activity.v1.TaskService.ListUserTasks:output_type -> hyapp.activity.v1.ListUserTasksResponse - 37, // 375: hyapp.activity.v1.TaskService.ClaimTaskReward:output_type -> hyapp.activity.v1.ClaimTaskRewardResponse - 39, // 376: hyapp.activity.v1.TaskService.ConsumeTaskEvent:output_type -> hyapp.activity.v1.ConsumeTaskEventResponse - 157, // 377: hyapp.activity.v1.GrowthLevelService.GetMyLevelOverview:output_type -> hyapp.activity.v1.GetMyLevelOverviewResponse - 159, // 378: hyapp.activity.v1.GrowthLevelService.GetLevelTrack:output_type -> hyapp.activity.v1.GetLevelTrackResponse - 163, // 379: hyapp.activity.v1.GrowthLevelService.BatchGetUserLevelDisplayProfiles:output_type -> hyapp.activity.v1.BatchGetUserLevelDisplayProfilesResponse - 166, // 380: hyapp.activity.v1.GrowthLevelService.ListLevelRewards:output_type -> hyapp.activity.v1.ListLevelRewardsResponse - 168, // 381: hyapp.activity.v1.GrowthLevelService.ConsumeLevelEvent:output_type -> hyapp.activity.v1.ConsumeLevelEventResponse - 170, // 382: hyapp.activity.v1.GrowthLevelService.SetUserLevel:output_type -> hyapp.activity.v1.SetUserLevelResponse - 173, // 383: hyapp.activity.v1.GrowthLevelService.IssueRegistrationLevelBadges:output_type -> hyapp.activity.v1.IssueRegistrationLevelBadgesResponse - 186, // 384: hyapp.activity.v1.AchievementService.ListAchievements:output_type -> hyapp.activity.v1.ListAchievementsResponse - 188, // 385: hyapp.activity.v1.AchievementService.ConsumeAchievementEvent:output_type -> hyapp.activity.v1.ConsumeAchievementEventResponse - 191, // 386: hyapp.activity.v1.AchievementService.ListMyBadges:output_type -> hyapp.activity.v1.ListMyBadgesResponse - 193, // 387: hyapp.activity.v1.AchievementService.SetBadgeDisplay:output_type -> hyapp.activity.v1.SetBadgeDisplayResponse - 204, // 388: hyapp.activity.v1.WheelService.ExecuteWheelDraw:output_type -> hyapp.activity.v1.ExecuteWheelDrawResponse - 129, // 389: hyapp.activity.v1.RoomTurnoverRewardService.GetRoomTurnoverRewardStatus:output_type -> hyapp.activity.v1.GetRoomTurnoverRewardStatusResponse - 232, // 390: hyapp.activity.v1.WeeklyStarService.GetWeeklyStarCurrent:output_type -> hyapp.activity.v1.GetWeeklyStarCurrentResponse - 228, // 391: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarLeaderboard:output_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardResponse - 235, // 392: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarHistory:output_type -> hyapp.activity.v1.ListWeeklyStarHistoryResponse - 242, // 393: hyapp.activity.v1.CPWeeklyRankService.GetCPWeeklyRankStatus:output_type -> hyapp.activity.v1.GetCPWeeklyRankStatusResponse - 266, // 394: hyapp.activity.v1.AgencyOpeningService.GetAgencyOpeningStatus:output_type -> hyapp.activity.v1.GetAgencyOpeningStatusResponse - 268, // 395: hyapp.activity.v1.AgencyOpeningService.ApplyAgencyOpening:output_type -> hyapp.activity.v1.ApplyAgencyOpeningResponse - 262, // 396: hyapp.activity.v1.AgencyOpeningService.ListAgencyOpeningLeaderboard:output_type -> hyapp.activity.v1.ListAgencyOpeningApplicationsResponse - 42, // 397: hyapp.activity.v1.BroadcastService.EnsureBroadcastGroups:output_type -> hyapp.activity.v1.EnsureBroadcastGroupsResponse - 45, // 398: hyapp.activity.v1.BroadcastService.PublishRegionBroadcast:output_type -> hyapp.activity.v1.PublishBroadcastResponse - 45, // 399: hyapp.activity.v1.BroadcastService.PublishGlobalBroadcast:output_type -> hyapp.activity.v1.PublishBroadcastResponse - 47, // 400: hyapp.activity.v1.BroadcastService.RemoveRegionBroadcastMember:output_type -> hyapp.activity.v1.RemoveRegionBroadcastMemberResponse - 48, // 401: hyapp.activity.v1.BroadcastService.ProcessBroadcastOutboxBatch:output_type -> hyapp.activity.v1.ProcessBroadcastOutboxBatchResponse - 50, // 402: hyapp.activity.v1.RoomEventConsumerService.ConsumeRoomEvent:output_type -> hyapp.activity.v1.ConsumeRoomEventResponse - 53, // 403: hyapp.activity.v1.AdminTaskService.ListTaskDefinitions:output_type -> hyapp.activity.v1.ListTaskDefinitionsResponse - 55, // 404: hyapp.activity.v1.AdminTaskService.UpsertTaskDefinition:output_type -> hyapp.activity.v1.UpsertTaskDefinitionResponse - 57, // 405: hyapp.activity.v1.AdminTaskService.SetTaskDefinitionStatus:output_type -> hyapp.activity.v1.SetTaskDefinitionStatusResponse - 59, // 406: hyapp.activity.v1.AdminTaskService.PublishTaskRewardPolicy:output_type -> hyapp.activity.v1.PublishTaskRewardPolicyResponse - 64, // 407: hyapp.activity.v1.RegistrationRewardService.GetRegistrationRewardEligibility:output_type -> hyapp.activity.v1.GetRegistrationRewardEligibilityResponse - 66, // 408: hyapp.activity.v1.RegistrationRewardService.IssueRegistrationReward:output_type -> hyapp.activity.v1.IssueRegistrationRewardResponse - 68, // 409: hyapp.activity.v1.AdminRegistrationRewardService.GetRegistrationRewardConfig:output_type -> hyapp.activity.v1.GetRegistrationRewardConfigResponse - 70, // 410: hyapp.activity.v1.AdminRegistrationRewardService.UpdateRegistrationRewardConfig:output_type -> hyapp.activity.v1.UpdateRegistrationRewardConfigResponse - 72, // 411: hyapp.activity.v1.AdminRegistrationRewardService.ListRegistrationRewardClaims:output_type -> hyapp.activity.v1.ListRegistrationRewardClaimsResponse - 78, // 412: hyapp.activity.v1.FirstRechargeRewardService.GetFirstRechargeRewardStatus:output_type -> hyapp.activity.v1.GetFirstRechargeRewardStatusResponse - 80, // 413: hyapp.activity.v1.FirstRechargeRewardService.ConsumeFirstRechargeReward:output_type -> hyapp.activity.v1.ConsumeFirstRechargeRewardResponse - 82, // 414: hyapp.activity.v1.AdminFirstRechargeRewardService.GetFirstRechargeRewardConfig:output_type -> hyapp.activity.v1.GetFirstRechargeRewardConfigResponse - 84, // 415: hyapp.activity.v1.AdminFirstRechargeRewardService.UpdateFirstRechargeRewardConfig:output_type -> hyapp.activity.v1.UpdateFirstRechargeRewardConfigResponse - 86, // 416: hyapp.activity.v1.AdminFirstRechargeRewardService.ListFirstRechargeRewardClaims:output_type -> hyapp.activity.v1.ListFirstRechargeRewardClaimsResponse - 93, // 417: hyapp.activity.v1.CumulativeRechargeRewardService.GetCumulativeRechargeRewardStatus:output_type -> hyapp.activity.v1.GetCumulativeRechargeRewardStatusResponse - 95, // 418: hyapp.activity.v1.CumulativeRechargeRewardService.ConsumeCumulativeRechargeReward:output_type -> hyapp.activity.v1.ConsumeCumulativeRechargeRewardResponse - 108, // 419: hyapp.activity.v1.InviteActivityRewardService.GetInviteActivityRewardStatus:output_type -> hyapp.activity.v1.GetInviteActivityRewardStatusResponse - 110, // 420: hyapp.activity.v1.InviteActivityRewardService.ClaimInviteActivityReward:output_type -> hyapp.activity.v1.ClaimInviteActivityRewardResponse - 113, // 421: hyapp.activity.v1.InviteActivityRewardService.ListInviteActivityLeaderboard:output_type -> hyapp.activity.v1.ListInviteActivityLeaderboardResponse - 97, // 422: hyapp.activity.v1.AdminCumulativeRechargeRewardService.GetCumulativeRechargeRewardConfig:output_type -> hyapp.activity.v1.GetCumulativeRechargeRewardConfigResponse - 99, // 423: hyapp.activity.v1.AdminCumulativeRechargeRewardService.UpdateCumulativeRechargeRewardConfig:output_type -> hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigResponse - 101, // 424: hyapp.activity.v1.AdminCumulativeRechargeRewardService.ListCumulativeRechargeRewardGrants:output_type -> hyapp.activity.v1.ListCumulativeRechargeRewardGrantsResponse - 115, // 425: hyapp.activity.v1.AdminInviteActivityRewardService.GetInviteActivityRewardConfig:output_type -> hyapp.activity.v1.GetInviteActivityRewardConfigResponse - 117, // 426: hyapp.activity.v1.AdminInviteActivityRewardService.UpdateInviteActivityRewardConfig:output_type -> hyapp.activity.v1.UpdateInviteActivityRewardConfigResponse - 119, // 427: hyapp.activity.v1.AdminInviteActivityRewardService.ListInviteActivityRewardClaims:output_type -> hyapp.activity.v1.ListInviteActivityRewardClaimsResponse - 123, // 428: hyapp.activity.v1.AdminInviteActivityRewardService.ListInviteActivityRewardSummaries:output_type -> hyapp.activity.v1.ListInviteActivityRewardSummariesResponse - 131, // 429: hyapp.activity.v1.AdminRoomTurnoverRewardService.GetRoomTurnoverRewardConfig:output_type -> hyapp.activity.v1.GetRoomTurnoverRewardConfigResponse - 133, // 430: hyapp.activity.v1.AdminRoomTurnoverRewardService.UpdateRoomTurnoverRewardConfig:output_type -> hyapp.activity.v1.UpdateRoomTurnoverRewardConfigResponse - 135, // 431: hyapp.activity.v1.AdminRoomTurnoverRewardService.ListRoomTurnoverRewardSettlements:output_type -> hyapp.activity.v1.ListRoomTurnoverRewardSettlementsResponse - 137, // 432: hyapp.activity.v1.AdminRoomTurnoverRewardService.RetryRoomTurnoverRewardSettlement:output_type -> hyapp.activity.v1.RetryRoomTurnoverRewardSettlementResponse - 220, // 433: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarCycles:output_type -> hyapp.activity.v1.ListWeeklyStarCyclesResponse - 224, // 434: hyapp.activity.v1.AdminWeeklyStarService.CreateWeeklyStarCycle:output_type -> hyapp.activity.v1.UpsertWeeklyStarCycleResponse - 222, // 435: hyapp.activity.v1.AdminWeeklyStarService.GetWeeklyStarCycle:output_type -> hyapp.activity.v1.GetWeeklyStarCycleResponse - 224, // 436: hyapp.activity.v1.AdminWeeklyStarService.UpdateWeeklyStarCycle:output_type -> hyapp.activity.v1.UpsertWeeklyStarCycleResponse - 226, // 437: hyapp.activity.v1.AdminWeeklyStarService.SetWeeklyStarCycleStatus:output_type -> hyapp.activity.v1.SetWeeklyStarCycleStatusResponse - 228, // 438: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarLeaderboard:output_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardResponse - 230, // 439: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarSettlements:output_type -> hyapp.activity.v1.ListWeeklyStarSettlementsResponse - 244, // 440: hyapp.activity.v1.AdminCPWeeklyRankService.GetCPWeeklyRankConfig:output_type -> hyapp.activity.v1.GetCPWeeklyRankConfigResponse - 246, // 441: hyapp.activity.v1.AdminCPWeeklyRankService.UpdateCPWeeklyRankConfig:output_type -> hyapp.activity.v1.UpdateCPWeeklyRankConfigResponse - 248, // 442: hyapp.activity.v1.AdminCPWeeklyRankService.ListCPWeeklyRankSettlements:output_type -> hyapp.activity.v1.ListCPWeeklyRankSettlementsResponse - 254, // 443: hyapp.activity.v1.AdminAgencyOpeningService.ListAgencyOpeningCycles:output_type -> hyapp.activity.v1.ListAgencyOpeningCyclesResponse - 258, // 444: hyapp.activity.v1.AdminAgencyOpeningService.CreateAgencyOpeningCycle:output_type -> hyapp.activity.v1.UpsertAgencyOpeningCycleResponse - 256, // 445: hyapp.activity.v1.AdminAgencyOpeningService.GetAgencyOpeningCycle:output_type -> hyapp.activity.v1.GetAgencyOpeningCycleResponse - 258, // 446: hyapp.activity.v1.AdminAgencyOpeningService.UpdateAgencyOpeningCycle:output_type -> hyapp.activity.v1.UpsertAgencyOpeningCycleResponse - 260, // 447: hyapp.activity.v1.AdminAgencyOpeningService.SetAgencyOpeningCycleStatus:output_type -> hyapp.activity.v1.SetAgencyOpeningCycleStatusResponse - 262, // 448: hyapp.activity.v1.AdminAgencyOpeningService.ListAgencyOpeningApplications:output_type -> hyapp.activity.v1.ListAgencyOpeningApplicationsResponse - 264, // 449: hyapp.activity.v1.AdminAgencyOpeningService.ReviewAgencyOpeningApplication:output_type -> hyapp.activity.v1.ReviewAgencyOpeningApplicationResponse - 142, // 450: hyapp.activity.v1.SevenDayCheckInService.GetSevenDayCheckInStatus:output_type -> hyapp.activity.v1.GetSevenDayCheckInStatusResponse - 144, // 451: hyapp.activity.v1.SevenDayCheckInService.SignSevenDayCheckIn:output_type -> hyapp.activity.v1.SignSevenDayCheckInResponse - 146, // 452: hyapp.activity.v1.AdminSevenDayCheckInService.GetSevenDayCheckInConfig:output_type -> hyapp.activity.v1.GetSevenDayCheckInConfigResponse - 148, // 453: hyapp.activity.v1.AdminSevenDayCheckInService.UpdateSevenDayCheckInConfig:output_type -> hyapp.activity.v1.UpdateSevenDayCheckInConfigResponse - 151, // 454: hyapp.activity.v1.AdminSevenDayCheckInService.ListSevenDayCheckInClaims:output_type -> hyapp.activity.v1.ListSevenDayCheckInClaimsResponse - 181, // 455: hyapp.activity.v1.AdminGrowthLevelService.ListLevelConfig:output_type -> hyapp.activity.v1.ListLevelConfigResponse - 175, // 456: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTrack:output_type -> hyapp.activity.v1.UpsertLevelTrackResponse - 177, // 457: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelRule:output_type -> hyapp.activity.v1.UpsertLevelRuleResponse - 179, // 458: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTier:output_type -> hyapp.activity.v1.UpsertLevelTierResponse - 186, // 459: hyapp.activity.v1.AdminAchievementService.ListAchievementDefinitions:output_type -> hyapp.activity.v1.ListAchievementsResponse - 195, // 460: hyapp.activity.v1.AdminAchievementService.UpsertAchievementDefinition:output_type -> hyapp.activity.v1.UpsertAchievementDefinitionResponse - 197, // 461: hyapp.activity.v1.AdminAchievementService.DeleteAchievementDefinition:output_type -> hyapp.activity.v1.DeleteAchievementDefinitionResponse - 206, // 462: hyapp.activity.v1.AdminWheelService.GetWheelConfig:output_type -> hyapp.activity.v1.GetWheelConfigResponse - 208, // 463: hyapp.activity.v1.AdminWheelService.UpsertWheelConfig:output_type -> hyapp.activity.v1.UpsertWheelConfigResponse - 210, // 464: hyapp.activity.v1.AdminWheelService.ListWheelDraws:output_type -> hyapp.activity.v1.ListWheelDrawsResponse - 213, // 465: hyapp.activity.v1.AdminWheelService.GetWheelDrawSummary:output_type -> hyapp.activity.v1.GetWheelDrawSummaryResponse - 354, // [354:466] is the sub-list for method output_type - 242, // [242:354] is the sub-list for method input_type - 242, // [242:242] is the sub-list for extension type_name - 242, // [242:242] is the sub-list for extension extendee - 0, // [0:242] is the sub-list for field type_name + 171, // 141: hyapp.activity.v1.AdminUserLevelProfile.tracks:type_name -> hyapp.activity.v1.AdminLevelTrackProfile + 0, // 142: hyapp.activity.v1.BatchGetUserLevelAdminProfilesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 172, // 143: hyapp.activity.v1.BatchGetUserLevelAdminProfilesResponse.profiles:type_name -> hyapp.activity.v1.AdminUserLevelProfile + 0, // 144: hyapp.activity.v1.AdjustTemporaryUserLevelsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 175, // 145: hyapp.activity.v1.AdjustTemporaryUserLevelsRequest.adjustments:type_name -> hyapp.activity.v1.TemporaryLevelAdjustment + 172, // 146: hyapp.activity.v1.AdjustTemporaryUserLevelsResponse.profile:type_name -> hyapp.activity.v1.AdminUserLevelProfile + 0, // 147: hyapp.activity.v1.IssueRegistrationLevelBadgesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 178, // 148: hyapp.activity.v1.IssueRegistrationLevelBadgesResponse.grants:type_name -> hyapp.activity.v1.RegistrationLevelBadgeGrant + 0, // 149: hyapp.activity.v1.UpsertLevelTrackRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 152, // 150: hyapp.activity.v1.UpsertLevelTrackResponse.track:type_name -> hyapp.activity.v1.LevelTrack + 0, // 151: hyapp.activity.v1.UpsertLevelRuleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 153, // 152: hyapp.activity.v1.UpsertLevelRuleResponse.rule:type_name -> hyapp.activity.v1.LevelRule + 0, // 153: hyapp.activity.v1.UpsertLevelTierRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 154, // 154: hyapp.activity.v1.UpsertLevelTierResponse.tier:type_name -> hyapp.activity.v1.LevelTier + 0, // 155: hyapp.activity.v1.ListLevelConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 152, // 156: hyapp.activity.v1.ListLevelConfigResponse.tracks:type_name -> hyapp.activity.v1.LevelTrack + 153, // 157: hyapp.activity.v1.ListLevelConfigResponse.rules:type_name -> hyapp.activity.v1.LevelRule + 154, // 158: hyapp.activity.v1.ListLevelConfigResponse.tiers:type_name -> hyapp.activity.v1.LevelTier + 189, // 159: hyapp.activity.v1.AchievementDefinition.conditions:type_name -> hyapp.activity.v1.AchievementCondition + 190, // 160: hyapp.activity.v1.UserAchievement.definition:type_name -> hyapp.activity.v1.AchievementDefinition + 0, // 161: hyapp.activity.v1.ListAchievementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 191, // 162: hyapp.activity.v1.ListAchievementsResponse.achievements:type_name -> hyapp.activity.v1.UserAchievement + 0, // 163: hyapp.activity.v1.ConsumeAchievementEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 0, // 164: hyapp.activity.v1.ListMyBadgesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 196, // 165: hyapp.activity.v1.ListMyBadgesResponse.strip_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem + 196, // 166: hyapp.activity.v1.ListMyBadgesResponse.profile_tile_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem + 196, // 167: hyapp.activity.v1.ListMyBadgesResponse.honor_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem + 0, // 168: hyapp.activity.v1.SetBadgeDisplayRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 196, // 169: hyapp.activity.v1.SetBadgeDisplayRequest.items:type_name -> hyapp.activity.v1.BadgeDisplayItem + 198, // 170: hyapp.activity.v1.SetBadgeDisplayResponse.profile:type_name -> hyapp.activity.v1.ListMyBadgesResponse + 0, // 171: hyapp.activity.v1.UpsertAchievementDefinitionRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 189, // 172: hyapp.activity.v1.UpsertAchievementDefinitionRequest.conditions:type_name -> hyapp.activity.v1.AchievementCondition + 190, // 173: hyapp.activity.v1.UpsertAchievementDefinitionResponse.achievement:type_name -> hyapp.activity.v1.AchievementDefinition + 0, // 174: hyapp.activity.v1.DeleteAchievementDefinitionRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 190, // 175: hyapp.activity.v1.DeleteAchievementDefinitionResponse.achievement:type_name -> hyapp.activity.v1.AchievementDefinition + 205, // 176: hyapp.activity.v1.WheelRuleConfig.tiers:type_name -> hyapp.activity.v1.WheelPrizeTier + 0, // 177: hyapp.activity.v1.WheelDrawMeta.meta:type_name -> hyapp.activity.v1.RequestMeta + 209, // 178: hyapp.activity.v1.WheelDrawResult.rewards:type_name -> hyapp.activity.v1.WheelDrawReward + 207, // 179: hyapp.activity.v1.ExecuteWheelDrawRequest.wheel:type_name -> hyapp.activity.v1.WheelDrawMeta + 208, // 180: hyapp.activity.v1.ExecuteWheelDrawResponse.result:type_name -> hyapp.activity.v1.WheelDrawResult + 0, // 181: hyapp.activity.v1.GetWheelConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 206, // 182: hyapp.activity.v1.GetWheelConfigResponse.config:type_name -> hyapp.activity.v1.WheelRuleConfig + 0, // 183: hyapp.activity.v1.UpsertWheelConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 206, // 184: hyapp.activity.v1.UpsertWheelConfigRequest.config:type_name -> hyapp.activity.v1.WheelRuleConfig + 206, // 185: hyapp.activity.v1.UpsertWheelConfigResponse.config:type_name -> hyapp.activity.v1.WheelRuleConfig + 0, // 186: hyapp.activity.v1.ListWheelDrawsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 208, // 187: hyapp.activity.v1.ListWheelDrawsResponse.draws:type_name -> hyapp.activity.v1.WheelDrawResult + 0, // 188: hyapp.activity.v1.GetWheelDrawSummaryRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 218, // 189: hyapp.activity.v1.GetWheelDrawSummaryResponse.summary:type_name -> hyapp.activity.v1.WheelDrawSummary + 221, // 190: hyapp.activity.v1.WeeklyStarCycle.gifts:type_name -> hyapp.activity.v1.WeeklyStarGift + 222, // 191: hyapp.activity.v1.WeeklyStarCycle.rewards:type_name -> hyapp.activity.v1.WeeklyStarReward + 0, // 192: hyapp.activity.v1.ListWeeklyStarCyclesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 223, // 193: hyapp.activity.v1.ListWeeklyStarCyclesResponse.cycles:type_name -> hyapp.activity.v1.WeeklyStarCycle + 0, // 194: hyapp.activity.v1.GetWeeklyStarCycleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 223, // 195: hyapp.activity.v1.GetWeeklyStarCycleResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 0, // 196: hyapp.activity.v1.UpsertWeeklyStarCycleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 223, // 197: hyapp.activity.v1.UpsertWeeklyStarCycleRequest.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 223, // 198: hyapp.activity.v1.UpsertWeeklyStarCycleResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 0, // 199: hyapp.activity.v1.SetWeeklyStarCycleStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 223, // 200: hyapp.activity.v1.SetWeeklyStarCycleStatusResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 0, // 201: hyapp.activity.v1.ListWeeklyStarLeaderboardRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 223, // 202: hyapp.activity.v1.ListWeeklyStarLeaderboardResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 224, // 203: hyapp.activity.v1.ListWeeklyStarLeaderboardResponse.entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry + 0, // 204: hyapp.activity.v1.ListWeeklyStarSettlementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 225, // 205: hyapp.activity.v1.ListWeeklyStarSettlementsResponse.settlements:type_name -> hyapp.activity.v1.WeeklyStarSettlement + 0, // 206: hyapp.activity.v1.GetWeeklyStarCurrentRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 223, // 207: hyapp.activity.v1.GetWeeklyStarCurrentResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 224, // 208: hyapp.activity.v1.GetWeeklyStarCurrentResponse.top_entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry + 224, // 209: hyapp.activity.v1.GetWeeklyStarCurrentResponse.my_entry:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry + 0, // 210: hyapp.activity.v1.ListWeeklyStarHistoryRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 223, // 211: hyapp.activity.v1.WeeklyStarHistoryCycle.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 224, // 212: hyapp.activity.v1.WeeklyStarHistoryCycle.top_entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry + 241, // 213: hyapp.activity.v1.ListWeeklyStarHistoryResponse.cycles:type_name -> hyapp.activity.v1.WeeklyStarHistoryCycle + 243, // 214: hyapp.activity.v1.CPWeeklyRankConfig.rewards:type_name -> hyapp.activity.v1.CPWeeklyRankReward + 245, // 215: hyapp.activity.v1.CPWeeklyRankEntry.user_a:type_name -> hyapp.activity.v1.CPWeeklyRankUser + 245, // 216: hyapp.activity.v1.CPWeeklyRankEntry.user_b:type_name -> hyapp.activity.v1.CPWeeklyRankUser + 0, // 217: hyapp.activity.v1.GetCPWeeklyRankStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 244, // 218: hyapp.activity.v1.GetCPWeeklyRankStatusResponse.config:type_name -> hyapp.activity.v1.CPWeeklyRankConfig + 246, // 219: hyapp.activity.v1.GetCPWeeklyRankStatusResponse.entries:type_name -> hyapp.activity.v1.CPWeeklyRankEntry + 246, // 220: hyapp.activity.v1.GetCPWeeklyRankStatusResponse.previous_entries:type_name -> hyapp.activity.v1.CPWeeklyRankEntry + 0, // 221: hyapp.activity.v1.GetCPWeeklyRankConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 244, // 222: hyapp.activity.v1.GetCPWeeklyRankConfigResponse.config:type_name -> hyapp.activity.v1.CPWeeklyRankConfig + 0, // 223: hyapp.activity.v1.UpdateCPWeeklyRankConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 244, // 224: hyapp.activity.v1.UpdateCPWeeklyRankConfigRequest.config:type_name -> hyapp.activity.v1.CPWeeklyRankConfig + 244, // 225: hyapp.activity.v1.UpdateCPWeeklyRankConfigResponse.config:type_name -> hyapp.activity.v1.CPWeeklyRankConfig + 0, // 226: hyapp.activity.v1.ListCPWeeklyRankSettlementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 247, // 227: hyapp.activity.v1.ListCPWeeklyRankSettlementsResponse.settlements:type_name -> hyapp.activity.v1.CPWeeklyRankSettlement + 256, // 228: hyapp.activity.v1.AgencyOpeningCycle.rewards:type_name -> hyapp.activity.v1.AgencyOpeningReward + 0, // 229: hyapp.activity.v1.ListAgencyOpeningCyclesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 257, // 230: hyapp.activity.v1.ListAgencyOpeningCyclesResponse.cycles:type_name -> hyapp.activity.v1.AgencyOpeningCycle + 0, // 231: hyapp.activity.v1.GetAgencyOpeningCycleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 257, // 232: hyapp.activity.v1.GetAgencyOpeningCycleResponse.cycle:type_name -> hyapp.activity.v1.AgencyOpeningCycle + 0, // 233: hyapp.activity.v1.UpsertAgencyOpeningCycleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 257, // 234: hyapp.activity.v1.UpsertAgencyOpeningCycleRequest.cycle:type_name -> hyapp.activity.v1.AgencyOpeningCycle + 257, // 235: hyapp.activity.v1.UpsertAgencyOpeningCycleResponse.cycle:type_name -> hyapp.activity.v1.AgencyOpeningCycle + 0, // 236: hyapp.activity.v1.SetAgencyOpeningCycleStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 257, // 237: hyapp.activity.v1.SetAgencyOpeningCycleStatusResponse.cycle:type_name -> hyapp.activity.v1.AgencyOpeningCycle + 0, // 238: hyapp.activity.v1.ListAgencyOpeningApplicationsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 258, // 239: hyapp.activity.v1.ListAgencyOpeningApplicationsResponse.applications:type_name -> hyapp.activity.v1.AgencyOpeningApplication + 0, // 240: hyapp.activity.v1.ReviewAgencyOpeningApplicationRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 258, // 241: hyapp.activity.v1.ReviewAgencyOpeningApplicationResponse.application:type_name -> hyapp.activity.v1.AgencyOpeningApplication + 0, // 242: hyapp.activity.v1.GetAgencyOpeningStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 257, // 243: hyapp.activity.v1.GetAgencyOpeningStatusResponse.cycle:type_name -> hyapp.activity.v1.AgencyOpeningCycle + 258, // 244: hyapp.activity.v1.GetAgencyOpeningStatusResponse.application:type_name -> hyapp.activity.v1.AgencyOpeningApplication + 259, // 245: hyapp.activity.v1.GetAgencyOpeningStatusResponse.agency:type_name -> hyapp.activity.v1.AgencyOpeningAgencySnapshot + 0, // 246: hyapp.activity.v1.ApplyAgencyOpeningRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 258, // 247: hyapp.activity.v1.ApplyAgencyOpeningResponse.application:type_name -> hyapp.activity.v1.AgencyOpeningApplication + 1, // 248: hyapp.activity.v1.ActivityService.PingActivity:input_type -> hyapp.activity.v1.PingActivityRequest + 3, // 249: hyapp.activity.v1.ActivityService.GetActivityStatus:input_type -> hyapp.activity.v1.GetActivityStatusRequest + 6, // 250: hyapp.activity.v1.MessageInboxService.ListMessageTabs:input_type -> hyapp.activity.v1.ListMessageTabsRequest + 9, // 251: hyapp.activity.v1.MessageInboxService.ListInboxMessages:input_type -> hyapp.activity.v1.ListInboxMessagesRequest + 11, // 252: hyapp.activity.v1.MessageInboxService.MarkInboxMessageRead:input_type -> hyapp.activity.v1.MarkInboxMessageReadRequest + 13, // 253: hyapp.activity.v1.MessageInboxService.MarkInboxSectionRead:input_type -> hyapp.activity.v1.MarkInboxSectionReadRequest + 15, // 254: hyapp.activity.v1.MessageInboxService.DeleteInboxMessage:input_type -> hyapp.activity.v1.DeleteInboxMessageRequest + 17, // 255: hyapp.activity.v1.MessageInboxService.CreateInboxMessage:input_type -> hyapp.activity.v1.CreateInboxMessageRequest + 19, // 256: hyapp.activity.v1.MessageInboxService.CreateFanoutJob:input_type -> hyapp.activity.v1.CreateFanoutJobRequest + 22, // 257: hyapp.activity.v1.MessageActionConfirmService.AcceptActionConfirm:input_type -> hyapp.activity.v1.AcceptActionConfirmRequest + 24, // 258: hyapp.activity.v1.MessageActionConfirmService.RejectActionConfirm:input_type -> hyapp.activity.v1.RejectActionConfirmRequest + 26, // 259: hyapp.activity.v1.MessageActionConfirmService.BatchGetActionConfirmStatus:input_type -> hyapp.activity.v1.BatchGetActionConfirmStatusRequest + 28, // 260: hyapp.activity.v1.MessageActionConfirmService.ListConversationActionConfirms:input_type -> hyapp.activity.v1.ListConversationActionConfirmsRequest + 30, // 261: hyapp.activity.v1.ActivityCronService.ProcessMessageFanoutBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 30, // 262: hyapp.activity.v1.ActivityCronService.ProcessLevelRewardBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 30, // 263: hyapp.activity.v1.ActivityCronService.ProcessTemporaryLevelBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 30, // 264: hyapp.activity.v1.ActivityCronService.ProcessAchievementRewardBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 30, // 265: hyapp.activity.v1.ActivityCronService.ProcessRoomTurnoverRewardSettlementBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 30, // 266: hyapp.activity.v1.ActivityCronService.ProcessWeeklyStarSettlementBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 30, // 267: hyapp.activity.v1.ActivityCronService.ProcessCPWeeklyRankSettlementBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 30, // 268: hyapp.activity.v1.ActivityCronService.ProcessAgencyOpeningSettlementBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 34, // 269: hyapp.activity.v1.TaskService.ListUserTasks:input_type -> hyapp.activity.v1.ListUserTasksRequest + 36, // 270: hyapp.activity.v1.TaskService.ClaimTaskReward:input_type -> hyapp.activity.v1.ClaimTaskRewardRequest + 38, // 271: hyapp.activity.v1.TaskService.ConsumeTaskEvent:input_type -> hyapp.activity.v1.ConsumeTaskEventRequest + 156, // 272: hyapp.activity.v1.GrowthLevelService.GetMyLevelOverview:input_type -> hyapp.activity.v1.GetMyLevelOverviewRequest + 158, // 273: hyapp.activity.v1.GrowthLevelService.GetLevelTrack:input_type -> hyapp.activity.v1.GetLevelTrackRequest + 162, // 274: hyapp.activity.v1.GrowthLevelService.BatchGetUserLevelDisplayProfiles:input_type -> hyapp.activity.v1.BatchGetUserLevelDisplayProfilesRequest + 165, // 275: hyapp.activity.v1.GrowthLevelService.ListLevelRewards:input_type -> hyapp.activity.v1.ListLevelRewardsRequest + 167, // 276: hyapp.activity.v1.GrowthLevelService.ConsumeLevelEvent:input_type -> hyapp.activity.v1.ConsumeLevelEventRequest + 169, // 277: hyapp.activity.v1.GrowthLevelService.SetUserLevel:input_type -> hyapp.activity.v1.SetUserLevelRequest + 179, // 278: hyapp.activity.v1.GrowthLevelService.IssueRegistrationLevelBadges:input_type -> hyapp.activity.v1.IssueRegistrationLevelBadgesRequest + 192, // 279: hyapp.activity.v1.AchievementService.ListAchievements:input_type -> hyapp.activity.v1.ListAchievementsRequest + 194, // 280: hyapp.activity.v1.AchievementService.ConsumeAchievementEvent:input_type -> hyapp.activity.v1.ConsumeAchievementEventRequest + 197, // 281: hyapp.activity.v1.AchievementService.ListMyBadges:input_type -> hyapp.activity.v1.ListMyBadgesRequest + 199, // 282: hyapp.activity.v1.AchievementService.SetBadgeDisplay:input_type -> hyapp.activity.v1.SetBadgeDisplayRequest + 210, // 283: hyapp.activity.v1.WheelService.ExecuteWheelDraw:input_type -> hyapp.activity.v1.ExecuteWheelDrawRequest + 128, // 284: hyapp.activity.v1.RoomTurnoverRewardService.GetRoomTurnoverRewardStatus:input_type -> hyapp.activity.v1.GetRoomTurnoverRewardStatusRequest + 238, // 285: hyapp.activity.v1.WeeklyStarService.GetWeeklyStarCurrent:input_type -> hyapp.activity.v1.GetWeeklyStarCurrentRequest + 234, // 286: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarLeaderboard:input_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardRequest + 240, // 287: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarHistory:input_type -> hyapp.activity.v1.ListWeeklyStarHistoryRequest + 248, // 288: hyapp.activity.v1.CPWeeklyRankService.GetCPWeeklyRankStatus:input_type -> hyapp.activity.v1.GetCPWeeklyRankStatusRequest + 272, // 289: hyapp.activity.v1.AgencyOpeningService.GetAgencyOpeningStatus:input_type -> hyapp.activity.v1.GetAgencyOpeningStatusRequest + 274, // 290: hyapp.activity.v1.AgencyOpeningService.ApplyAgencyOpening:input_type -> hyapp.activity.v1.ApplyAgencyOpeningRequest + 268, // 291: hyapp.activity.v1.AgencyOpeningService.ListAgencyOpeningLeaderboard:input_type -> hyapp.activity.v1.ListAgencyOpeningApplicationsRequest + 41, // 292: hyapp.activity.v1.BroadcastService.EnsureBroadcastGroups:input_type -> hyapp.activity.v1.EnsureBroadcastGroupsRequest + 43, // 293: hyapp.activity.v1.BroadcastService.PublishRegionBroadcast:input_type -> hyapp.activity.v1.PublishRegionBroadcastRequest + 44, // 294: hyapp.activity.v1.BroadcastService.PublishGlobalBroadcast:input_type -> hyapp.activity.v1.PublishGlobalBroadcastRequest + 46, // 295: hyapp.activity.v1.BroadcastService.RemoveRegionBroadcastMember:input_type -> hyapp.activity.v1.RemoveRegionBroadcastMemberRequest + 30, // 296: hyapp.activity.v1.BroadcastService.ProcessBroadcastOutboxBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 49, // 297: hyapp.activity.v1.RoomEventConsumerService.ConsumeRoomEvent:input_type -> hyapp.activity.v1.ConsumeRoomEventRequest + 52, // 298: hyapp.activity.v1.AdminTaskService.ListTaskDefinitions:input_type -> hyapp.activity.v1.ListTaskDefinitionsRequest + 54, // 299: hyapp.activity.v1.AdminTaskService.UpsertTaskDefinition:input_type -> hyapp.activity.v1.UpsertTaskDefinitionRequest + 56, // 300: hyapp.activity.v1.AdminTaskService.SetTaskDefinitionStatus:input_type -> hyapp.activity.v1.SetTaskDefinitionStatusRequest + 58, // 301: hyapp.activity.v1.AdminTaskService.PublishTaskRewardPolicy:input_type -> hyapp.activity.v1.PublishTaskRewardPolicyRequest + 63, // 302: hyapp.activity.v1.RegistrationRewardService.GetRegistrationRewardEligibility:input_type -> hyapp.activity.v1.GetRegistrationRewardEligibilityRequest + 65, // 303: hyapp.activity.v1.RegistrationRewardService.IssueRegistrationReward:input_type -> hyapp.activity.v1.IssueRegistrationRewardRequest + 67, // 304: hyapp.activity.v1.AdminRegistrationRewardService.GetRegistrationRewardConfig:input_type -> hyapp.activity.v1.GetRegistrationRewardConfigRequest + 69, // 305: hyapp.activity.v1.AdminRegistrationRewardService.UpdateRegistrationRewardConfig:input_type -> hyapp.activity.v1.UpdateRegistrationRewardConfigRequest + 71, // 306: hyapp.activity.v1.AdminRegistrationRewardService.ListRegistrationRewardClaims:input_type -> hyapp.activity.v1.ListRegistrationRewardClaimsRequest + 77, // 307: hyapp.activity.v1.FirstRechargeRewardService.GetFirstRechargeRewardStatus:input_type -> hyapp.activity.v1.GetFirstRechargeRewardStatusRequest + 79, // 308: hyapp.activity.v1.FirstRechargeRewardService.ConsumeFirstRechargeReward:input_type -> hyapp.activity.v1.ConsumeFirstRechargeRewardRequest + 81, // 309: hyapp.activity.v1.AdminFirstRechargeRewardService.GetFirstRechargeRewardConfig:input_type -> hyapp.activity.v1.GetFirstRechargeRewardConfigRequest + 83, // 310: hyapp.activity.v1.AdminFirstRechargeRewardService.UpdateFirstRechargeRewardConfig:input_type -> hyapp.activity.v1.UpdateFirstRechargeRewardConfigRequest + 85, // 311: hyapp.activity.v1.AdminFirstRechargeRewardService.ListFirstRechargeRewardClaims:input_type -> hyapp.activity.v1.ListFirstRechargeRewardClaimsRequest + 92, // 312: hyapp.activity.v1.CumulativeRechargeRewardService.GetCumulativeRechargeRewardStatus:input_type -> hyapp.activity.v1.GetCumulativeRechargeRewardStatusRequest + 94, // 313: hyapp.activity.v1.CumulativeRechargeRewardService.ConsumeCumulativeRechargeReward:input_type -> hyapp.activity.v1.ConsumeCumulativeRechargeRewardRequest + 107, // 314: hyapp.activity.v1.InviteActivityRewardService.GetInviteActivityRewardStatus:input_type -> hyapp.activity.v1.GetInviteActivityRewardStatusRequest + 109, // 315: hyapp.activity.v1.InviteActivityRewardService.ClaimInviteActivityReward:input_type -> hyapp.activity.v1.ClaimInviteActivityRewardRequest + 112, // 316: hyapp.activity.v1.InviteActivityRewardService.ListInviteActivityLeaderboard:input_type -> hyapp.activity.v1.ListInviteActivityLeaderboardRequest + 96, // 317: hyapp.activity.v1.AdminCumulativeRechargeRewardService.GetCumulativeRechargeRewardConfig:input_type -> hyapp.activity.v1.GetCumulativeRechargeRewardConfigRequest + 98, // 318: hyapp.activity.v1.AdminCumulativeRechargeRewardService.UpdateCumulativeRechargeRewardConfig:input_type -> hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigRequest + 100, // 319: hyapp.activity.v1.AdminCumulativeRechargeRewardService.ListCumulativeRechargeRewardGrants:input_type -> hyapp.activity.v1.ListCumulativeRechargeRewardGrantsRequest + 114, // 320: hyapp.activity.v1.AdminInviteActivityRewardService.GetInviteActivityRewardConfig:input_type -> hyapp.activity.v1.GetInviteActivityRewardConfigRequest + 116, // 321: hyapp.activity.v1.AdminInviteActivityRewardService.UpdateInviteActivityRewardConfig:input_type -> hyapp.activity.v1.UpdateInviteActivityRewardConfigRequest + 118, // 322: hyapp.activity.v1.AdminInviteActivityRewardService.ListInviteActivityRewardClaims:input_type -> hyapp.activity.v1.ListInviteActivityRewardClaimsRequest + 122, // 323: hyapp.activity.v1.AdminInviteActivityRewardService.ListInviteActivityRewardSummaries:input_type -> hyapp.activity.v1.ListInviteActivityRewardSummariesRequest + 130, // 324: hyapp.activity.v1.AdminRoomTurnoverRewardService.GetRoomTurnoverRewardConfig:input_type -> hyapp.activity.v1.GetRoomTurnoverRewardConfigRequest + 132, // 325: hyapp.activity.v1.AdminRoomTurnoverRewardService.UpdateRoomTurnoverRewardConfig:input_type -> hyapp.activity.v1.UpdateRoomTurnoverRewardConfigRequest + 134, // 326: hyapp.activity.v1.AdminRoomTurnoverRewardService.ListRoomTurnoverRewardSettlements:input_type -> hyapp.activity.v1.ListRoomTurnoverRewardSettlementsRequest + 136, // 327: hyapp.activity.v1.AdminRoomTurnoverRewardService.RetryRoomTurnoverRewardSettlement:input_type -> hyapp.activity.v1.RetryRoomTurnoverRewardSettlementRequest + 226, // 328: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarCycles:input_type -> hyapp.activity.v1.ListWeeklyStarCyclesRequest + 230, // 329: hyapp.activity.v1.AdminWeeklyStarService.CreateWeeklyStarCycle:input_type -> hyapp.activity.v1.UpsertWeeklyStarCycleRequest + 228, // 330: hyapp.activity.v1.AdminWeeklyStarService.GetWeeklyStarCycle:input_type -> hyapp.activity.v1.GetWeeklyStarCycleRequest + 230, // 331: hyapp.activity.v1.AdminWeeklyStarService.UpdateWeeklyStarCycle:input_type -> hyapp.activity.v1.UpsertWeeklyStarCycleRequest + 232, // 332: hyapp.activity.v1.AdminWeeklyStarService.SetWeeklyStarCycleStatus:input_type -> hyapp.activity.v1.SetWeeklyStarCycleStatusRequest + 234, // 333: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarLeaderboard:input_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardRequest + 236, // 334: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarSettlements:input_type -> hyapp.activity.v1.ListWeeklyStarSettlementsRequest + 250, // 335: hyapp.activity.v1.AdminCPWeeklyRankService.GetCPWeeklyRankConfig:input_type -> hyapp.activity.v1.GetCPWeeklyRankConfigRequest + 252, // 336: hyapp.activity.v1.AdminCPWeeklyRankService.UpdateCPWeeklyRankConfig:input_type -> hyapp.activity.v1.UpdateCPWeeklyRankConfigRequest + 254, // 337: hyapp.activity.v1.AdminCPWeeklyRankService.ListCPWeeklyRankSettlements:input_type -> hyapp.activity.v1.ListCPWeeklyRankSettlementsRequest + 260, // 338: hyapp.activity.v1.AdminAgencyOpeningService.ListAgencyOpeningCycles:input_type -> hyapp.activity.v1.ListAgencyOpeningCyclesRequest + 264, // 339: hyapp.activity.v1.AdminAgencyOpeningService.CreateAgencyOpeningCycle:input_type -> hyapp.activity.v1.UpsertAgencyOpeningCycleRequest + 262, // 340: hyapp.activity.v1.AdminAgencyOpeningService.GetAgencyOpeningCycle:input_type -> hyapp.activity.v1.GetAgencyOpeningCycleRequest + 264, // 341: hyapp.activity.v1.AdminAgencyOpeningService.UpdateAgencyOpeningCycle:input_type -> hyapp.activity.v1.UpsertAgencyOpeningCycleRequest + 266, // 342: hyapp.activity.v1.AdminAgencyOpeningService.SetAgencyOpeningCycleStatus:input_type -> hyapp.activity.v1.SetAgencyOpeningCycleStatusRequest + 268, // 343: hyapp.activity.v1.AdminAgencyOpeningService.ListAgencyOpeningApplications:input_type -> hyapp.activity.v1.ListAgencyOpeningApplicationsRequest + 270, // 344: hyapp.activity.v1.AdminAgencyOpeningService.ReviewAgencyOpeningApplication:input_type -> hyapp.activity.v1.ReviewAgencyOpeningApplicationRequest + 141, // 345: hyapp.activity.v1.SevenDayCheckInService.GetSevenDayCheckInStatus:input_type -> hyapp.activity.v1.GetSevenDayCheckInStatusRequest + 143, // 346: hyapp.activity.v1.SevenDayCheckInService.SignSevenDayCheckIn:input_type -> hyapp.activity.v1.SignSevenDayCheckInRequest + 145, // 347: hyapp.activity.v1.AdminSevenDayCheckInService.GetSevenDayCheckInConfig:input_type -> hyapp.activity.v1.GetSevenDayCheckInConfigRequest + 147, // 348: hyapp.activity.v1.AdminSevenDayCheckInService.UpdateSevenDayCheckInConfig:input_type -> hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest + 150, // 349: hyapp.activity.v1.AdminSevenDayCheckInService.ListSevenDayCheckInClaims:input_type -> hyapp.activity.v1.ListSevenDayCheckInClaimsRequest + 187, // 350: hyapp.activity.v1.AdminGrowthLevelService.ListLevelConfig:input_type -> hyapp.activity.v1.ListLevelConfigRequest + 181, // 351: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTrack:input_type -> hyapp.activity.v1.UpsertLevelTrackRequest + 183, // 352: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelRule:input_type -> hyapp.activity.v1.UpsertLevelRuleRequest + 185, // 353: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTier:input_type -> hyapp.activity.v1.UpsertLevelTierRequest + 173, // 354: hyapp.activity.v1.AdminGrowthLevelService.BatchGetUserLevelAdminProfiles:input_type -> hyapp.activity.v1.BatchGetUserLevelAdminProfilesRequest + 176, // 355: hyapp.activity.v1.AdminGrowthLevelService.AdjustTemporaryUserLevels:input_type -> hyapp.activity.v1.AdjustTemporaryUserLevelsRequest + 192, // 356: hyapp.activity.v1.AdminAchievementService.ListAchievementDefinitions:input_type -> hyapp.activity.v1.ListAchievementsRequest + 201, // 357: hyapp.activity.v1.AdminAchievementService.UpsertAchievementDefinition:input_type -> hyapp.activity.v1.UpsertAchievementDefinitionRequest + 203, // 358: hyapp.activity.v1.AdminAchievementService.DeleteAchievementDefinition:input_type -> hyapp.activity.v1.DeleteAchievementDefinitionRequest + 212, // 359: hyapp.activity.v1.AdminWheelService.GetWheelConfig:input_type -> hyapp.activity.v1.GetWheelConfigRequest + 214, // 360: hyapp.activity.v1.AdminWheelService.UpsertWheelConfig:input_type -> hyapp.activity.v1.UpsertWheelConfigRequest + 216, // 361: hyapp.activity.v1.AdminWheelService.ListWheelDraws:input_type -> hyapp.activity.v1.ListWheelDrawsRequest + 219, // 362: hyapp.activity.v1.AdminWheelService.GetWheelDrawSummary:input_type -> hyapp.activity.v1.GetWheelDrawSummaryRequest + 2, // 363: hyapp.activity.v1.ActivityService.PingActivity:output_type -> hyapp.activity.v1.PingActivityResponse + 4, // 364: hyapp.activity.v1.ActivityService.GetActivityStatus:output_type -> hyapp.activity.v1.GetActivityStatusResponse + 7, // 365: hyapp.activity.v1.MessageInboxService.ListMessageTabs:output_type -> hyapp.activity.v1.ListMessageTabsResponse + 10, // 366: hyapp.activity.v1.MessageInboxService.ListInboxMessages:output_type -> hyapp.activity.v1.ListInboxMessagesResponse + 12, // 367: hyapp.activity.v1.MessageInboxService.MarkInboxMessageRead:output_type -> hyapp.activity.v1.MarkInboxMessageReadResponse + 14, // 368: hyapp.activity.v1.MessageInboxService.MarkInboxSectionRead:output_type -> hyapp.activity.v1.MarkInboxSectionReadResponse + 16, // 369: hyapp.activity.v1.MessageInboxService.DeleteInboxMessage:output_type -> hyapp.activity.v1.DeleteInboxMessageResponse + 18, // 370: hyapp.activity.v1.MessageInboxService.CreateInboxMessage:output_type -> hyapp.activity.v1.CreateInboxMessageResponse + 20, // 371: hyapp.activity.v1.MessageInboxService.CreateFanoutJob:output_type -> hyapp.activity.v1.CreateFanoutJobResponse + 23, // 372: hyapp.activity.v1.MessageActionConfirmService.AcceptActionConfirm:output_type -> hyapp.activity.v1.AcceptActionConfirmResponse + 25, // 373: hyapp.activity.v1.MessageActionConfirmService.RejectActionConfirm:output_type -> hyapp.activity.v1.RejectActionConfirmResponse + 27, // 374: hyapp.activity.v1.MessageActionConfirmService.BatchGetActionConfirmStatus:output_type -> hyapp.activity.v1.BatchGetActionConfirmStatusResponse + 29, // 375: hyapp.activity.v1.MessageActionConfirmService.ListConversationActionConfirms:output_type -> hyapp.activity.v1.ListConversationActionConfirmsResponse + 31, // 376: hyapp.activity.v1.ActivityCronService.ProcessMessageFanoutBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 31, // 377: hyapp.activity.v1.ActivityCronService.ProcessLevelRewardBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 31, // 378: hyapp.activity.v1.ActivityCronService.ProcessTemporaryLevelBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 31, // 379: hyapp.activity.v1.ActivityCronService.ProcessAchievementRewardBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 31, // 380: hyapp.activity.v1.ActivityCronService.ProcessRoomTurnoverRewardSettlementBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 31, // 381: hyapp.activity.v1.ActivityCronService.ProcessWeeklyStarSettlementBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 31, // 382: hyapp.activity.v1.ActivityCronService.ProcessCPWeeklyRankSettlementBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 31, // 383: hyapp.activity.v1.ActivityCronService.ProcessAgencyOpeningSettlementBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 35, // 384: hyapp.activity.v1.TaskService.ListUserTasks:output_type -> hyapp.activity.v1.ListUserTasksResponse + 37, // 385: hyapp.activity.v1.TaskService.ClaimTaskReward:output_type -> hyapp.activity.v1.ClaimTaskRewardResponse + 39, // 386: hyapp.activity.v1.TaskService.ConsumeTaskEvent:output_type -> hyapp.activity.v1.ConsumeTaskEventResponse + 157, // 387: hyapp.activity.v1.GrowthLevelService.GetMyLevelOverview:output_type -> hyapp.activity.v1.GetMyLevelOverviewResponse + 159, // 388: hyapp.activity.v1.GrowthLevelService.GetLevelTrack:output_type -> hyapp.activity.v1.GetLevelTrackResponse + 163, // 389: hyapp.activity.v1.GrowthLevelService.BatchGetUserLevelDisplayProfiles:output_type -> hyapp.activity.v1.BatchGetUserLevelDisplayProfilesResponse + 166, // 390: hyapp.activity.v1.GrowthLevelService.ListLevelRewards:output_type -> hyapp.activity.v1.ListLevelRewardsResponse + 168, // 391: hyapp.activity.v1.GrowthLevelService.ConsumeLevelEvent:output_type -> hyapp.activity.v1.ConsumeLevelEventResponse + 170, // 392: hyapp.activity.v1.GrowthLevelService.SetUserLevel:output_type -> hyapp.activity.v1.SetUserLevelResponse + 180, // 393: hyapp.activity.v1.GrowthLevelService.IssueRegistrationLevelBadges:output_type -> hyapp.activity.v1.IssueRegistrationLevelBadgesResponse + 193, // 394: hyapp.activity.v1.AchievementService.ListAchievements:output_type -> hyapp.activity.v1.ListAchievementsResponse + 195, // 395: hyapp.activity.v1.AchievementService.ConsumeAchievementEvent:output_type -> hyapp.activity.v1.ConsumeAchievementEventResponse + 198, // 396: hyapp.activity.v1.AchievementService.ListMyBadges:output_type -> hyapp.activity.v1.ListMyBadgesResponse + 200, // 397: hyapp.activity.v1.AchievementService.SetBadgeDisplay:output_type -> hyapp.activity.v1.SetBadgeDisplayResponse + 211, // 398: hyapp.activity.v1.WheelService.ExecuteWheelDraw:output_type -> hyapp.activity.v1.ExecuteWheelDrawResponse + 129, // 399: hyapp.activity.v1.RoomTurnoverRewardService.GetRoomTurnoverRewardStatus:output_type -> hyapp.activity.v1.GetRoomTurnoverRewardStatusResponse + 239, // 400: hyapp.activity.v1.WeeklyStarService.GetWeeklyStarCurrent:output_type -> hyapp.activity.v1.GetWeeklyStarCurrentResponse + 235, // 401: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarLeaderboard:output_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardResponse + 242, // 402: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarHistory:output_type -> hyapp.activity.v1.ListWeeklyStarHistoryResponse + 249, // 403: hyapp.activity.v1.CPWeeklyRankService.GetCPWeeklyRankStatus:output_type -> hyapp.activity.v1.GetCPWeeklyRankStatusResponse + 273, // 404: hyapp.activity.v1.AgencyOpeningService.GetAgencyOpeningStatus:output_type -> hyapp.activity.v1.GetAgencyOpeningStatusResponse + 275, // 405: hyapp.activity.v1.AgencyOpeningService.ApplyAgencyOpening:output_type -> hyapp.activity.v1.ApplyAgencyOpeningResponse + 269, // 406: hyapp.activity.v1.AgencyOpeningService.ListAgencyOpeningLeaderboard:output_type -> hyapp.activity.v1.ListAgencyOpeningApplicationsResponse + 42, // 407: hyapp.activity.v1.BroadcastService.EnsureBroadcastGroups:output_type -> hyapp.activity.v1.EnsureBroadcastGroupsResponse + 45, // 408: hyapp.activity.v1.BroadcastService.PublishRegionBroadcast:output_type -> hyapp.activity.v1.PublishBroadcastResponse + 45, // 409: hyapp.activity.v1.BroadcastService.PublishGlobalBroadcast:output_type -> hyapp.activity.v1.PublishBroadcastResponse + 47, // 410: hyapp.activity.v1.BroadcastService.RemoveRegionBroadcastMember:output_type -> hyapp.activity.v1.RemoveRegionBroadcastMemberResponse + 48, // 411: hyapp.activity.v1.BroadcastService.ProcessBroadcastOutboxBatch:output_type -> hyapp.activity.v1.ProcessBroadcastOutboxBatchResponse + 50, // 412: hyapp.activity.v1.RoomEventConsumerService.ConsumeRoomEvent:output_type -> hyapp.activity.v1.ConsumeRoomEventResponse + 53, // 413: hyapp.activity.v1.AdminTaskService.ListTaskDefinitions:output_type -> hyapp.activity.v1.ListTaskDefinitionsResponse + 55, // 414: hyapp.activity.v1.AdminTaskService.UpsertTaskDefinition:output_type -> hyapp.activity.v1.UpsertTaskDefinitionResponse + 57, // 415: hyapp.activity.v1.AdminTaskService.SetTaskDefinitionStatus:output_type -> hyapp.activity.v1.SetTaskDefinitionStatusResponse + 59, // 416: hyapp.activity.v1.AdminTaskService.PublishTaskRewardPolicy:output_type -> hyapp.activity.v1.PublishTaskRewardPolicyResponse + 64, // 417: hyapp.activity.v1.RegistrationRewardService.GetRegistrationRewardEligibility:output_type -> hyapp.activity.v1.GetRegistrationRewardEligibilityResponse + 66, // 418: hyapp.activity.v1.RegistrationRewardService.IssueRegistrationReward:output_type -> hyapp.activity.v1.IssueRegistrationRewardResponse + 68, // 419: hyapp.activity.v1.AdminRegistrationRewardService.GetRegistrationRewardConfig:output_type -> hyapp.activity.v1.GetRegistrationRewardConfigResponse + 70, // 420: hyapp.activity.v1.AdminRegistrationRewardService.UpdateRegistrationRewardConfig:output_type -> hyapp.activity.v1.UpdateRegistrationRewardConfigResponse + 72, // 421: hyapp.activity.v1.AdminRegistrationRewardService.ListRegistrationRewardClaims:output_type -> hyapp.activity.v1.ListRegistrationRewardClaimsResponse + 78, // 422: hyapp.activity.v1.FirstRechargeRewardService.GetFirstRechargeRewardStatus:output_type -> hyapp.activity.v1.GetFirstRechargeRewardStatusResponse + 80, // 423: hyapp.activity.v1.FirstRechargeRewardService.ConsumeFirstRechargeReward:output_type -> hyapp.activity.v1.ConsumeFirstRechargeRewardResponse + 82, // 424: hyapp.activity.v1.AdminFirstRechargeRewardService.GetFirstRechargeRewardConfig:output_type -> hyapp.activity.v1.GetFirstRechargeRewardConfigResponse + 84, // 425: hyapp.activity.v1.AdminFirstRechargeRewardService.UpdateFirstRechargeRewardConfig:output_type -> hyapp.activity.v1.UpdateFirstRechargeRewardConfigResponse + 86, // 426: hyapp.activity.v1.AdminFirstRechargeRewardService.ListFirstRechargeRewardClaims:output_type -> hyapp.activity.v1.ListFirstRechargeRewardClaimsResponse + 93, // 427: hyapp.activity.v1.CumulativeRechargeRewardService.GetCumulativeRechargeRewardStatus:output_type -> hyapp.activity.v1.GetCumulativeRechargeRewardStatusResponse + 95, // 428: hyapp.activity.v1.CumulativeRechargeRewardService.ConsumeCumulativeRechargeReward:output_type -> hyapp.activity.v1.ConsumeCumulativeRechargeRewardResponse + 108, // 429: hyapp.activity.v1.InviteActivityRewardService.GetInviteActivityRewardStatus:output_type -> hyapp.activity.v1.GetInviteActivityRewardStatusResponse + 110, // 430: hyapp.activity.v1.InviteActivityRewardService.ClaimInviteActivityReward:output_type -> hyapp.activity.v1.ClaimInviteActivityRewardResponse + 113, // 431: hyapp.activity.v1.InviteActivityRewardService.ListInviteActivityLeaderboard:output_type -> hyapp.activity.v1.ListInviteActivityLeaderboardResponse + 97, // 432: hyapp.activity.v1.AdminCumulativeRechargeRewardService.GetCumulativeRechargeRewardConfig:output_type -> hyapp.activity.v1.GetCumulativeRechargeRewardConfigResponse + 99, // 433: hyapp.activity.v1.AdminCumulativeRechargeRewardService.UpdateCumulativeRechargeRewardConfig:output_type -> hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigResponse + 101, // 434: hyapp.activity.v1.AdminCumulativeRechargeRewardService.ListCumulativeRechargeRewardGrants:output_type -> hyapp.activity.v1.ListCumulativeRechargeRewardGrantsResponse + 115, // 435: hyapp.activity.v1.AdminInviteActivityRewardService.GetInviteActivityRewardConfig:output_type -> hyapp.activity.v1.GetInviteActivityRewardConfigResponse + 117, // 436: hyapp.activity.v1.AdminInviteActivityRewardService.UpdateInviteActivityRewardConfig:output_type -> hyapp.activity.v1.UpdateInviteActivityRewardConfigResponse + 119, // 437: hyapp.activity.v1.AdminInviteActivityRewardService.ListInviteActivityRewardClaims:output_type -> hyapp.activity.v1.ListInviteActivityRewardClaimsResponse + 123, // 438: hyapp.activity.v1.AdminInviteActivityRewardService.ListInviteActivityRewardSummaries:output_type -> hyapp.activity.v1.ListInviteActivityRewardSummariesResponse + 131, // 439: hyapp.activity.v1.AdminRoomTurnoverRewardService.GetRoomTurnoverRewardConfig:output_type -> hyapp.activity.v1.GetRoomTurnoverRewardConfigResponse + 133, // 440: hyapp.activity.v1.AdminRoomTurnoverRewardService.UpdateRoomTurnoverRewardConfig:output_type -> hyapp.activity.v1.UpdateRoomTurnoverRewardConfigResponse + 135, // 441: hyapp.activity.v1.AdminRoomTurnoverRewardService.ListRoomTurnoverRewardSettlements:output_type -> hyapp.activity.v1.ListRoomTurnoverRewardSettlementsResponse + 137, // 442: hyapp.activity.v1.AdminRoomTurnoverRewardService.RetryRoomTurnoverRewardSettlement:output_type -> hyapp.activity.v1.RetryRoomTurnoverRewardSettlementResponse + 227, // 443: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarCycles:output_type -> hyapp.activity.v1.ListWeeklyStarCyclesResponse + 231, // 444: hyapp.activity.v1.AdminWeeklyStarService.CreateWeeklyStarCycle:output_type -> hyapp.activity.v1.UpsertWeeklyStarCycleResponse + 229, // 445: hyapp.activity.v1.AdminWeeklyStarService.GetWeeklyStarCycle:output_type -> hyapp.activity.v1.GetWeeklyStarCycleResponse + 231, // 446: hyapp.activity.v1.AdminWeeklyStarService.UpdateWeeklyStarCycle:output_type -> hyapp.activity.v1.UpsertWeeklyStarCycleResponse + 233, // 447: hyapp.activity.v1.AdminWeeklyStarService.SetWeeklyStarCycleStatus:output_type -> hyapp.activity.v1.SetWeeklyStarCycleStatusResponse + 235, // 448: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarLeaderboard:output_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardResponse + 237, // 449: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarSettlements:output_type -> hyapp.activity.v1.ListWeeklyStarSettlementsResponse + 251, // 450: hyapp.activity.v1.AdminCPWeeklyRankService.GetCPWeeklyRankConfig:output_type -> hyapp.activity.v1.GetCPWeeklyRankConfigResponse + 253, // 451: hyapp.activity.v1.AdminCPWeeklyRankService.UpdateCPWeeklyRankConfig:output_type -> hyapp.activity.v1.UpdateCPWeeklyRankConfigResponse + 255, // 452: hyapp.activity.v1.AdminCPWeeklyRankService.ListCPWeeklyRankSettlements:output_type -> hyapp.activity.v1.ListCPWeeklyRankSettlementsResponse + 261, // 453: hyapp.activity.v1.AdminAgencyOpeningService.ListAgencyOpeningCycles:output_type -> hyapp.activity.v1.ListAgencyOpeningCyclesResponse + 265, // 454: hyapp.activity.v1.AdminAgencyOpeningService.CreateAgencyOpeningCycle:output_type -> hyapp.activity.v1.UpsertAgencyOpeningCycleResponse + 263, // 455: hyapp.activity.v1.AdminAgencyOpeningService.GetAgencyOpeningCycle:output_type -> hyapp.activity.v1.GetAgencyOpeningCycleResponse + 265, // 456: hyapp.activity.v1.AdminAgencyOpeningService.UpdateAgencyOpeningCycle:output_type -> hyapp.activity.v1.UpsertAgencyOpeningCycleResponse + 267, // 457: hyapp.activity.v1.AdminAgencyOpeningService.SetAgencyOpeningCycleStatus:output_type -> hyapp.activity.v1.SetAgencyOpeningCycleStatusResponse + 269, // 458: hyapp.activity.v1.AdminAgencyOpeningService.ListAgencyOpeningApplications:output_type -> hyapp.activity.v1.ListAgencyOpeningApplicationsResponse + 271, // 459: hyapp.activity.v1.AdminAgencyOpeningService.ReviewAgencyOpeningApplication:output_type -> hyapp.activity.v1.ReviewAgencyOpeningApplicationResponse + 142, // 460: hyapp.activity.v1.SevenDayCheckInService.GetSevenDayCheckInStatus:output_type -> hyapp.activity.v1.GetSevenDayCheckInStatusResponse + 144, // 461: hyapp.activity.v1.SevenDayCheckInService.SignSevenDayCheckIn:output_type -> hyapp.activity.v1.SignSevenDayCheckInResponse + 146, // 462: hyapp.activity.v1.AdminSevenDayCheckInService.GetSevenDayCheckInConfig:output_type -> hyapp.activity.v1.GetSevenDayCheckInConfigResponse + 148, // 463: hyapp.activity.v1.AdminSevenDayCheckInService.UpdateSevenDayCheckInConfig:output_type -> hyapp.activity.v1.UpdateSevenDayCheckInConfigResponse + 151, // 464: hyapp.activity.v1.AdminSevenDayCheckInService.ListSevenDayCheckInClaims:output_type -> hyapp.activity.v1.ListSevenDayCheckInClaimsResponse + 188, // 465: hyapp.activity.v1.AdminGrowthLevelService.ListLevelConfig:output_type -> hyapp.activity.v1.ListLevelConfigResponse + 182, // 466: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTrack:output_type -> hyapp.activity.v1.UpsertLevelTrackResponse + 184, // 467: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelRule:output_type -> hyapp.activity.v1.UpsertLevelRuleResponse + 186, // 468: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTier:output_type -> hyapp.activity.v1.UpsertLevelTierResponse + 174, // 469: hyapp.activity.v1.AdminGrowthLevelService.BatchGetUserLevelAdminProfiles:output_type -> hyapp.activity.v1.BatchGetUserLevelAdminProfilesResponse + 177, // 470: hyapp.activity.v1.AdminGrowthLevelService.AdjustTemporaryUserLevels:output_type -> hyapp.activity.v1.AdjustTemporaryUserLevelsResponse + 193, // 471: hyapp.activity.v1.AdminAchievementService.ListAchievementDefinitions:output_type -> hyapp.activity.v1.ListAchievementsResponse + 202, // 472: hyapp.activity.v1.AdminAchievementService.UpsertAchievementDefinition:output_type -> hyapp.activity.v1.UpsertAchievementDefinitionResponse + 204, // 473: hyapp.activity.v1.AdminAchievementService.DeleteAchievementDefinition:output_type -> hyapp.activity.v1.DeleteAchievementDefinitionResponse + 213, // 474: hyapp.activity.v1.AdminWheelService.GetWheelConfig:output_type -> hyapp.activity.v1.GetWheelConfigResponse + 215, // 475: hyapp.activity.v1.AdminWheelService.UpsertWheelConfig:output_type -> hyapp.activity.v1.UpsertWheelConfigResponse + 217, // 476: hyapp.activity.v1.AdminWheelService.ListWheelDraws:output_type -> hyapp.activity.v1.ListWheelDrawsResponse + 220, // 477: hyapp.activity.v1.AdminWheelService.GetWheelDrawSummary:output_type -> hyapp.activity.v1.GetWheelDrawSummaryResponse + 363, // [363:478] is the sub-list for method output_type + 248, // [248:363] is the sub-list for method input_type + 248, // [248:248] is the sub-list for extension type_name + 248, // [248:248] is the sub-list for extension extendee + 0, // [0:248] is the sub-list for field type_name } func init() { file_proto_activity_v1_activity_proto_init() } @@ -26881,7 +27549,7 @@ func file_proto_activity_v1_activity_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_activity_v1_activity_proto_rawDesc, NumEnums: 0, - NumMessages: 269, + NumMessages: 276, NumExtensions: 0, NumServices: 32, }, diff --git a/api/proto/activity/v1/activity.proto b/api/proto/activity/v1/activity.proto index 905bc189..c451e65a 100644 --- a/api/proto/activity/v1/activity.proto +++ b/api/proto/activity/v1/activity.proto @@ -1450,6 +1450,13 @@ message LevelRewardJob { string failure_reason = 11; int64 created_at_ms = 12; int64 updated_at_ms = 13; + string reward_origin = 14; + string temporary_level_id = 15; + int32 grant_generation = 16; + int32 reward_level = 17; + bool permanent = 18; + int64 temporary_expires_at_ms = 19; + string revoke_status = 20; } message ListLevelRewardsRequest { @@ -1511,6 +1518,55 @@ message SetUserLevelResponse { int64 server_time_ms = 8; } +// AdminLevelTrackProfile keeps the permanent account and the currently effective display overlay separate. +message AdminLevelTrackProfile { + string track = 1; + int32 real_level = 2; + int64 real_total_value = 3; + int32 display_level = 4; + int64 display_value = 5; + string temporary_level_id = 6; + int32 temporary_target_level = 7; + int64 started_at_ms = 8; + int64 expires_at_ms = 9; +} + +message AdminUserLevelProfile { + int64 user_id = 1; + repeated AdminLevelTrackProfile tracks = 2; +} + +message BatchGetUserLevelAdminProfilesRequest { + RequestMeta meta = 1; + repeated int64 user_ids = 2; +} + +message BatchGetUserLevelAdminProfilesResponse { + repeated AdminUserLevelProfile profiles = 1; + int64 server_time_ms = 2; +} + +// TemporaryLevelAdjustment is limited to wealth/charm; duration_days must be positive and level is 1..50. +message TemporaryLevelAdjustment { + string track = 1; + int32 level = 2; + int32 duration_days = 3; +} + +message AdjustTemporaryUserLevelsRequest { + RequestMeta meta = 1; + string command_id = 2; + int64 user_id = 3; + repeated TemporaryLevelAdjustment adjustments = 4; + int64 operator_admin_id = 5; + string reason = 6; +} + +message AdjustTemporaryUserLevelsResponse { + AdminUserLevelProfile profile = 1; + int64 server_time_ms = 2; +} + message RegistrationLevelBadgeGrant { string track = 1; int32 level = 2; @@ -2335,6 +2391,7 @@ service MessageActionConfirmService { service ActivityCronService { rpc ProcessMessageFanoutBatch(CronBatchRequest) returns (CronBatchResponse); rpc ProcessLevelRewardBatch(CronBatchRequest) returns (CronBatchResponse); + rpc ProcessTemporaryLevelBatch(CronBatchRequest) returns (CronBatchResponse); rpc ProcessAchievementRewardBatch(CronBatchRequest) returns (CronBatchResponse); rpc ProcessRoomTurnoverRewardSettlementBatch(CronBatchRequest) returns (CronBatchResponse); rpc ProcessWeeklyStarSettlementBatch(CronBatchRequest) returns (CronBatchResponse); @@ -2529,6 +2586,8 @@ service AdminGrowthLevelService { rpc UpsertLevelTrack(UpsertLevelTrackRequest) returns (UpsertLevelTrackResponse); rpc UpsertLevelRule(UpsertLevelRuleRequest) returns (UpsertLevelRuleResponse); rpc UpsertLevelTier(UpsertLevelTierRequest) returns (UpsertLevelTierResponse); + rpc BatchGetUserLevelAdminProfiles(BatchGetUserLevelAdminProfilesRequest) returns (BatchGetUserLevelAdminProfilesResponse); + rpc AdjustTemporaryUserLevels(AdjustTemporaryUserLevelsRequest) returns (AdjustTemporaryUserLevelsResponse); } // AdminAchievementService is the admin entry for achievement definitions. diff --git a/api/proto/activity/v1/activity_grpc.pb.go b/api/proto/activity/v1/activity_grpc.pb.go index eb5e6f8b..930cc6df 100644 --- a/api/proto/activity/v1/activity_grpc.pb.go +++ b/api/proto/activity/v1/activity_grpc.pb.go @@ -720,6 +720,7 @@ var MessageActionConfirmService_ServiceDesc = grpc.ServiceDesc{ const ( ActivityCronService_ProcessMessageFanoutBatch_FullMethodName = "/hyapp.activity.v1.ActivityCronService/ProcessMessageFanoutBatch" ActivityCronService_ProcessLevelRewardBatch_FullMethodName = "/hyapp.activity.v1.ActivityCronService/ProcessLevelRewardBatch" + ActivityCronService_ProcessTemporaryLevelBatch_FullMethodName = "/hyapp.activity.v1.ActivityCronService/ProcessTemporaryLevelBatch" ActivityCronService_ProcessAchievementRewardBatch_FullMethodName = "/hyapp.activity.v1.ActivityCronService/ProcessAchievementRewardBatch" ActivityCronService_ProcessRoomTurnoverRewardSettlementBatch_FullMethodName = "/hyapp.activity.v1.ActivityCronService/ProcessRoomTurnoverRewardSettlementBatch" ActivityCronService_ProcessWeeklyStarSettlementBatch_FullMethodName = "/hyapp.activity.v1.ActivityCronService/ProcessWeeklyStarSettlementBatch" @@ -735,6 +736,7 @@ const ( type ActivityCronServiceClient interface { ProcessMessageFanoutBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) ProcessLevelRewardBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) + ProcessTemporaryLevelBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) ProcessAchievementRewardBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) ProcessRoomTurnoverRewardSettlementBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) ProcessWeeklyStarSettlementBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) @@ -770,6 +772,16 @@ func (c *activityCronServiceClient) ProcessLevelRewardBatch(ctx context.Context, return out, nil } +func (c *activityCronServiceClient) ProcessTemporaryLevelBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CronBatchResponse) + err := c.cc.Invoke(ctx, ActivityCronService_ProcessTemporaryLevelBatch_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *activityCronServiceClient) ProcessAchievementRewardBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(CronBatchResponse) @@ -828,6 +840,7 @@ func (c *activityCronServiceClient) ProcessAgencyOpeningSettlementBatch(ctx cont type ActivityCronServiceServer interface { ProcessMessageFanoutBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) ProcessLevelRewardBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) + ProcessTemporaryLevelBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) ProcessAchievementRewardBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) ProcessRoomTurnoverRewardSettlementBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) ProcessWeeklyStarSettlementBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) @@ -849,6 +862,9 @@ func (UnimplementedActivityCronServiceServer) ProcessMessageFanoutBatch(context. func (UnimplementedActivityCronServiceServer) ProcessLevelRewardBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ProcessLevelRewardBatch not implemented") } +func (UnimplementedActivityCronServiceServer) ProcessTemporaryLevelBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ProcessTemporaryLevelBatch not implemented") +} func (UnimplementedActivityCronServiceServer) ProcessAchievementRewardBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ProcessAchievementRewardBatch not implemented") } @@ -921,6 +937,24 @@ func _ActivityCronService_ProcessLevelRewardBatch_Handler(srv interface{}, ctx c return interceptor(ctx, in, info, handler) } +func _ActivityCronService_ProcessTemporaryLevelBatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CronBatchRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ActivityCronServiceServer).ProcessTemporaryLevelBatch(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ActivityCronService_ProcessTemporaryLevelBatch_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ActivityCronServiceServer).ProcessTemporaryLevelBatch(ctx, req.(*CronBatchRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _ActivityCronService_ProcessAchievementRewardBatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CronBatchRequest) if err := dec(in); err != nil { @@ -1026,6 +1060,10 @@ var ActivityCronService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ProcessLevelRewardBatch", Handler: _ActivityCronService_ProcessLevelRewardBatch_Handler, }, + { + MethodName: "ProcessTemporaryLevelBatch", + Handler: _ActivityCronService_ProcessTemporaryLevelBatch_Handler, + }, { MethodName: "ProcessAchievementRewardBatch", Handler: _ActivityCronService_ProcessAchievementRewardBatch_Handler, @@ -5846,10 +5884,12 @@ var AdminSevenDayCheckInService_ServiceDesc = grpc.ServiceDesc{ } const ( - AdminGrowthLevelService_ListLevelConfig_FullMethodName = "/hyapp.activity.v1.AdminGrowthLevelService/ListLevelConfig" - AdminGrowthLevelService_UpsertLevelTrack_FullMethodName = "/hyapp.activity.v1.AdminGrowthLevelService/UpsertLevelTrack" - AdminGrowthLevelService_UpsertLevelRule_FullMethodName = "/hyapp.activity.v1.AdminGrowthLevelService/UpsertLevelRule" - AdminGrowthLevelService_UpsertLevelTier_FullMethodName = "/hyapp.activity.v1.AdminGrowthLevelService/UpsertLevelTier" + AdminGrowthLevelService_ListLevelConfig_FullMethodName = "/hyapp.activity.v1.AdminGrowthLevelService/ListLevelConfig" + AdminGrowthLevelService_UpsertLevelTrack_FullMethodName = "/hyapp.activity.v1.AdminGrowthLevelService/UpsertLevelTrack" + AdminGrowthLevelService_UpsertLevelRule_FullMethodName = "/hyapp.activity.v1.AdminGrowthLevelService/UpsertLevelRule" + AdminGrowthLevelService_UpsertLevelTier_FullMethodName = "/hyapp.activity.v1.AdminGrowthLevelService/UpsertLevelTier" + AdminGrowthLevelService_BatchGetUserLevelAdminProfiles_FullMethodName = "/hyapp.activity.v1.AdminGrowthLevelService/BatchGetUserLevelAdminProfiles" + AdminGrowthLevelService_AdjustTemporaryUserLevels_FullMethodName = "/hyapp.activity.v1.AdminGrowthLevelService/AdjustTemporaryUserLevels" ) // AdminGrowthLevelServiceClient is the client API for AdminGrowthLevelService service. @@ -5862,6 +5902,8 @@ type AdminGrowthLevelServiceClient interface { UpsertLevelTrack(ctx context.Context, in *UpsertLevelTrackRequest, opts ...grpc.CallOption) (*UpsertLevelTrackResponse, error) UpsertLevelRule(ctx context.Context, in *UpsertLevelRuleRequest, opts ...grpc.CallOption) (*UpsertLevelRuleResponse, error) UpsertLevelTier(ctx context.Context, in *UpsertLevelTierRequest, opts ...grpc.CallOption) (*UpsertLevelTierResponse, error) + BatchGetUserLevelAdminProfiles(ctx context.Context, in *BatchGetUserLevelAdminProfilesRequest, opts ...grpc.CallOption) (*BatchGetUserLevelAdminProfilesResponse, error) + AdjustTemporaryUserLevels(ctx context.Context, in *AdjustTemporaryUserLevelsRequest, opts ...grpc.CallOption) (*AdjustTemporaryUserLevelsResponse, error) } type adminGrowthLevelServiceClient struct { @@ -5912,6 +5954,26 @@ func (c *adminGrowthLevelServiceClient) UpsertLevelTier(ctx context.Context, in return out, nil } +func (c *adminGrowthLevelServiceClient) BatchGetUserLevelAdminProfiles(ctx context.Context, in *BatchGetUserLevelAdminProfilesRequest, opts ...grpc.CallOption) (*BatchGetUserLevelAdminProfilesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(BatchGetUserLevelAdminProfilesResponse) + err := c.cc.Invoke(ctx, AdminGrowthLevelService_BatchGetUserLevelAdminProfiles_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *adminGrowthLevelServiceClient) AdjustTemporaryUserLevels(ctx context.Context, in *AdjustTemporaryUserLevelsRequest, opts ...grpc.CallOption) (*AdjustTemporaryUserLevelsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AdjustTemporaryUserLevelsResponse) + err := c.cc.Invoke(ctx, AdminGrowthLevelService_AdjustTemporaryUserLevels_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // AdminGrowthLevelServiceServer is the server API for AdminGrowthLevelService service. // All implementations must embed UnimplementedAdminGrowthLevelServiceServer // for forward compatibility. @@ -5922,6 +5984,8 @@ type AdminGrowthLevelServiceServer interface { UpsertLevelTrack(context.Context, *UpsertLevelTrackRequest) (*UpsertLevelTrackResponse, error) UpsertLevelRule(context.Context, *UpsertLevelRuleRequest) (*UpsertLevelRuleResponse, error) UpsertLevelTier(context.Context, *UpsertLevelTierRequest) (*UpsertLevelTierResponse, error) + BatchGetUserLevelAdminProfiles(context.Context, *BatchGetUserLevelAdminProfilesRequest) (*BatchGetUserLevelAdminProfilesResponse, error) + AdjustTemporaryUserLevels(context.Context, *AdjustTemporaryUserLevelsRequest) (*AdjustTemporaryUserLevelsResponse, error) mustEmbedUnimplementedAdminGrowthLevelServiceServer() } @@ -5944,6 +6008,12 @@ func (UnimplementedAdminGrowthLevelServiceServer) UpsertLevelRule(context.Contex func (UnimplementedAdminGrowthLevelServiceServer) UpsertLevelTier(context.Context, *UpsertLevelTierRequest) (*UpsertLevelTierResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpsertLevelTier not implemented") } +func (UnimplementedAdminGrowthLevelServiceServer) BatchGetUserLevelAdminProfiles(context.Context, *BatchGetUserLevelAdminProfilesRequest) (*BatchGetUserLevelAdminProfilesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BatchGetUserLevelAdminProfiles not implemented") +} +func (UnimplementedAdminGrowthLevelServiceServer) AdjustTemporaryUserLevels(context.Context, *AdjustTemporaryUserLevelsRequest) (*AdjustTemporaryUserLevelsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AdjustTemporaryUserLevels not implemented") +} func (UnimplementedAdminGrowthLevelServiceServer) mustEmbedUnimplementedAdminGrowthLevelServiceServer() { } func (UnimplementedAdminGrowthLevelServiceServer) testEmbeddedByValue() {} @@ -6038,6 +6108,42 @@ func _AdminGrowthLevelService_UpsertLevelTier_Handler(srv interface{}, ctx conte return interceptor(ctx, in, info, handler) } +func _AdminGrowthLevelService_BatchGetUserLevelAdminProfiles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BatchGetUserLevelAdminProfilesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminGrowthLevelServiceServer).BatchGetUserLevelAdminProfiles(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminGrowthLevelService_BatchGetUserLevelAdminProfiles_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminGrowthLevelServiceServer).BatchGetUserLevelAdminProfiles(ctx, req.(*BatchGetUserLevelAdminProfilesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdminGrowthLevelService_AdjustTemporaryUserLevels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AdjustTemporaryUserLevelsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminGrowthLevelServiceServer).AdjustTemporaryUserLevels(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminGrowthLevelService_AdjustTemporaryUserLevels_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminGrowthLevelServiceServer).AdjustTemporaryUserLevels(ctx, req.(*AdjustTemporaryUserLevelsRequest)) + } + return interceptor(ctx, in, info, handler) +} + // AdminGrowthLevelService_ServiceDesc is the grpc.ServiceDesc for AdminGrowthLevelService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -6061,6 +6167,14 @@ var AdminGrowthLevelService_ServiceDesc = grpc.ServiceDesc{ MethodName: "UpsertLevelTier", Handler: _AdminGrowthLevelService_UpsertLevelTier_Handler, }, + { + MethodName: "BatchGetUserLevelAdminProfiles", + Handler: _AdminGrowthLevelService_BatchGetUserLevelAdminProfiles_Handler, + }, + { + MethodName: "AdjustTemporaryUserLevels", + Handler: _AdminGrowthLevelService_AdjustTemporaryUserLevels_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/activity/v1/activity.proto", diff --git a/api/proto/events/luckygift/v1/events.pb.go b/api/proto/events/luckygift/v1/events.pb.go new file mode 100644 index 00000000..d5a300dc --- /dev/null +++ b/api/proto/events/luckygift/v1/events.pb.go @@ -0,0 +1,512 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.1 +// protoc v7.35.0 +// source: proto/events/luckygift/v1/events.proto + +package luckygifteventsv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// EventEnvelope 是 lucky-gift-service 发布 owner outbox 事实的稳定信封。 +// occurred_at_ms 固定为抽奖事实创建时的 Unix epoch milliseconds;MQ 实际发送时间不改变业务发生时间。 +type EventEnvelope struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EventId string `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"` + EventType string `protobuf:"bytes,2,opt,name=event_type,json=eventType,proto3" json:"event_type,omitempty"` + AppCode string `protobuf:"bytes,3,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + // draw_id 是本次送礼命令的聚合中奖标识;批量子抽明细仍只保存在 lucky owner 数据库,不能放大 MQ 负载。 + DrawId string `protobuf:"bytes,4,opt,name=draw_id,json=drawId,proto3" json:"draw_id,omitempty"` + OccurredAtMs int64 `protobuf:"varint,5,opt,name=occurred_at_ms,json=occurredAtMs,proto3" json:"occurred_at_ms,omitempty"` + Body []byte `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"` +} + +func (x *EventEnvelope) Reset() { + *x = EventEnvelope{} + mi := &file_proto_events_luckygift_v1_events_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EventEnvelope) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EventEnvelope) ProtoMessage() {} + +func (x *EventEnvelope) ProtoReflect() protoreflect.Message { + mi := &file_proto_events_luckygift_v1_events_proto_msgTypes[0] + 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 EventEnvelope.ProtoReflect.Descriptor instead. +func (*EventEnvelope) Descriptor() ([]byte, []int) { + return file_proto_events_luckygift_v1_events_proto_rawDescGZIP(), []int{0} +} + +func (x *EventEnvelope) GetEventId() string { + if x != nil { + return x.EventId + } + return "" +} + +func (x *EventEnvelope) GetEventType() string { + if x != nil { + return x.EventType + } + return "" +} + +func (x *EventEnvelope) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *EventEnvelope) GetDrawId() string { + if x != nil { + return x.DrawId + } + return "" +} + +func (x *EventEnvelope) GetOccurredAtMs() int64 { + if x != nil { + return x.OccurredAtMs + } + return 0 +} + +func (x *EventEnvelope) GetBody() []byte { + if x != nil { + return x.Body + } + return nil +} + +// LuckyGiftDrawn 表达钱包返奖已经 granted 的幸运礼物中奖事实。 +// lucky-gift-service 必须先完成 wallet-service 入账和本地 granted 收敛,再发布本事件; +// room-service 与 activity-service 只从该事实派生房内 IM 和区域飘屏,不能反向改变抽奖或账务状态。 +type LuckyGiftDrawn struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DrawId string `protobuf:"bytes,1,opt,name=draw_id,json=drawId,proto3" json:"draw_id,omitempty"` + CommandId string `protobuf:"bytes,2,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + RoomId string `protobuf:"bytes,3,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + PoolId string `protobuf:"bytes,4,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` + GiftId string `protobuf:"bytes,5,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"` + GiftCount int32 `protobuf:"varint,6,opt,name=gift_count,json=giftCount,proto3" json:"gift_count,omitempty"` + SenderUserId int64 `protobuf:"varint,7,opt,name=sender_user_id,json=senderUserId,proto3" json:"sender_user_id,omitempty"` + TargetUserId int64 `protobuf:"varint,8,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + // 展示资料是送礼入口固化的快照;消费者不应为了飘屏高频同步反查 user-service。 + SenderName string `protobuf:"bytes,9,opt,name=sender_name,json=senderName,proto3" json:"sender_name,omitempty"` + SenderAvatar string `protobuf:"bytes,10,opt,name=sender_avatar,json=senderAvatar,proto3" json:"sender_avatar,omitempty"` + SenderDisplayUserId string `protobuf:"bytes,11,opt,name=sender_display_user_id,json=senderDisplayUserId,proto3" json:"sender_display_user_id,omitempty"` + SenderPrettyDisplayUserId string `protobuf:"bytes,12,opt,name=sender_pretty_display_user_id,json=senderPrettyDisplayUserId,proto3" json:"sender_pretty_display_user_id,omitempty"` + // visible_region_id 是房间可见区域,区域播报不能用客户端 IP 或服务所在地推断。 + VisibleRegionId int64 `protobuf:"varint,13,opt,name=visible_region_id,json=visibleRegionId,proto3" json:"visible_region_id,omitempty"` + CountryId int64 `protobuf:"varint,14,opt,name=country_id,json=countryId,proto3" json:"country_id,omitempty"` + CoinSpent int64 `protobuf:"varint,15,opt,name=coin_spent,json=coinSpent,proto3" json:"coin_spent,omitempty"` + RuleVersion int64 `protobuf:"varint,16,opt,name=rule_version,json=ruleVersion,proto3" json:"rule_version,omitempty"` + ExperiencePool string `protobuf:"bytes,17,opt,name=experience_pool,json=experiencePool,proto3" json:"experience_pool,omitempty"` + SelectedTierId string `protobuf:"bytes,18,opt,name=selected_tier_id,json=selectedTierId,proto3" json:"selected_tier_id,omitempty"` + MultiplierPpm int64 `protobuf:"varint,19,opt,name=multiplier_ppm,json=multiplierPpm,proto3" json:"multiplier_ppm,omitempty"` + BaseRewardCoins int64 `protobuf:"varint,20,opt,name=base_reward_coins,json=baseRewardCoins,proto3" json:"base_reward_coins,omitempty"` + EffectiveRewardCoins int64 `protobuf:"varint,21,opt,name=effective_reward_coins,json=effectiveRewardCoins,proto3" json:"effective_reward_coins,omitempty"` + StageFeedback bool `protobuf:"varint,22,opt,name=stage_feedback,json=stageFeedback,proto3" json:"stage_feedback,omitempty"` + HighMultiplier bool `protobuf:"varint,23,opt,name=high_multiplier,json=highMultiplier,proto3" json:"high_multiplier,omitempty"` + // reward_status 在本事件中只能是 granted;保留字段是为了下游 JSON 与现有 Flutter 协议同名映射。 + RewardStatus string `protobuf:"bytes,24,opt,name=reward_status,json=rewardStatus,proto3" json:"reward_status,omitempty"` + WalletTransactionId string `protobuf:"bytes,25,opt,name=wallet_transaction_id,json=walletTransactionId,proto3" json:"wallet_transaction_id,omitempty"` + // 两个时间均为 UTC Unix epoch milliseconds:抽奖发生时间与钱包返奖确认时间不能混用。 + DrawCreatedAtMs int64 `protobuf:"varint,26,opt,name=draw_created_at_ms,json=drawCreatedAtMs,proto3" json:"draw_created_at_ms,omitempty"` + RewardGrantedAtMs int64 `protobuf:"varint,27,opt,name=reward_granted_at_ms,json=rewardGrantedAtMs,proto3" json:"reward_granted_at_ms,omitempty"` +} + +func (x *LuckyGiftDrawn) Reset() { + *x = LuckyGiftDrawn{} + mi := &file_proto_events_luckygift_v1_events_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LuckyGiftDrawn) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LuckyGiftDrawn) ProtoMessage() {} + +func (x *LuckyGiftDrawn) ProtoReflect() protoreflect.Message { + mi := &file_proto_events_luckygift_v1_events_proto_msgTypes[1] + 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 LuckyGiftDrawn.ProtoReflect.Descriptor instead. +func (*LuckyGiftDrawn) Descriptor() ([]byte, []int) { + return file_proto_events_luckygift_v1_events_proto_rawDescGZIP(), []int{1} +} + +func (x *LuckyGiftDrawn) GetDrawId() string { + if x != nil { + return x.DrawId + } + return "" +} + +func (x *LuckyGiftDrawn) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *LuckyGiftDrawn) GetRoomId() string { + if x != nil { + return x.RoomId + } + return "" +} + +func (x *LuckyGiftDrawn) GetPoolId() string { + if x != nil { + return x.PoolId + } + return "" +} + +func (x *LuckyGiftDrawn) GetGiftId() string { + if x != nil { + return x.GiftId + } + return "" +} + +func (x *LuckyGiftDrawn) GetGiftCount() int32 { + if x != nil { + return x.GiftCount + } + return 0 +} + +func (x *LuckyGiftDrawn) GetSenderUserId() int64 { + if x != nil { + return x.SenderUserId + } + return 0 +} + +func (x *LuckyGiftDrawn) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *LuckyGiftDrawn) GetSenderName() string { + if x != nil { + return x.SenderName + } + return "" +} + +func (x *LuckyGiftDrawn) GetSenderAvatar() string { + if x != nil { + return x.SenderAvatar + } + return "" +} + +func (x *LuckyGiftDrawn) GetSenderDisplayUserId() string { + if x != nil { + return x.SenderDisplayUserId + } + return "" +} + +func (x *LuckyGiftDrawn) GetSenderPrettyDisplayUserId() string { + if x != nil { + return x.SenderPrettyDisplayUserId + } + return "" +} + +func (x *LuckyGiftDrawn) GetVisibleRegionId() int64 { + if x != nil { + return x.VisibleRegionId + } + return 0 +} + +func (x *LuckyGiftDrawn) GetCountryId() int64 { + if x != nil { + return x.CountryId + } + return 0 +} + +func (x *LuckyGiftDrawn) GetCoinSpent() int64 { + if x != nil { + return x.CoinSpent + } + return 0 +} + +func (x *LuckyGiftDrawn) GetRuleVersion() int64 { + if x != nil { + return x.RuleVersion + } + return 0 +} + +func (x *LuckyGiftDrawn) GetExperiencePool() string { + if x != nil { + return x.ExperiencePool + } + return "" +} + +func (x *LuckyGiftDrawn) GetSelectedTierId() string { + if x != nil { + return x.SelectedTierId + } + return "" +} + +func (x *LuckyGiftDrawn) GetMultiplierPpm() int64 { + if x != nil { + return x.MultiplierPpm + } + return 0 +} + +func (x *LuckyGiftDrawn) GetBaseRewardCoins() int64 { + if x != nil { + return x.BaseRewardCoins + } + return 0 +} + +func (x *LuckyGiftDrawn) GetEffectiveRewardCoins() int64 { + if x != nil { + return x.EffectiveRewardCoins + } + return 0 +} + +func (x *LuckyGiftDrawn) GetStageFeedback() bool { + if x != nil { + return x.StageFeedback + } + return false +} + +func (x *LuckyGiftDrawn) GetHighMultiplier() bool { + if x != nil { + return x.HighMultiplier + } + return false +} + +func (x *LuckyGiftDrawn) GetRewardStatus() string { + if x != nil { + return x.RewardStatus + } + return "" +} + +func (x *LuckyGiftDrawn) GetWalletTransactionId() string { + if x != nil { + return x.WalletTransactionId + } + return "" +} + +func (x *LuckyGiftDrawn) GetDrawCreatedAtMs() int64 { + if x != nil { + return x.DrawCreatedAtMs + } + return 0 +} + +func (x *LuckyGiftDrawn) GetRewardGrantedAtMs() int64 { + if x != nil { + return x.RewardGrantedAtMs + } + return 0 +} + +var File_proto_events_luckygift_v1_events_proto protoreflect.FileDescriptor + +var file_proto_events_luckygift_v1_events_proto_rawDesc = []byte{ + 0x0a, 0x26, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x6c, + 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x19, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, + 0x2e, 0x76, 0x31, 0x22, 0xb7, 0x01, 0x0a, 0x0d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x45, 0x6e, 0x76, + 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, + 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, + 0x61, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x61, + 0x77, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x63, 0x63, 0x75, 0x72, 0x72, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6f, 0x63, 0x63, + 0x75, 0x72, 0x72, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, + 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0xab, 0x08, + 0x0a, 0x0e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x6e, + 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x64, 0x72, 0x61, 0x77, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, + 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, + 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, + 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, + 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, + 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, + 0x0a, 0x0b, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x76, + 0x61, 0x74, 0x61, 0x72, 0x12, 0x33, 0x0a, 0x16, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x64, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0b, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x1d, 0x73, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x19, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, + 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, + 0x70, 0x65, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, + 0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x75, 0x6c, + 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x78, 0x70, 0x65, + 0x72, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x50, 0x6f, 0x6f, + 0x6c, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x54, 0x69, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d, + 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x13, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x50, + 0x70, 0x6d, 0x12, 0x2a, 0x0a, 0x11, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, + 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x62, + 0x61, 0x73, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x34, + 0x0a, 0x16, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x72, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, + 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, + 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x65, + 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x16, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74, + 0x61, 0x67, 0x65, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x27, 0x0a, 0x0f, 0x68, + 0x69, 0x67, 0x68, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x18, 0x17, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x68, 0x69, 0x67, 0x68, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, + 0x6c, 0x69, 0x65, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2b, 0x0a, + 0x12, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x5f, 0x6d, 0x73, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x64, 0x72, 0x61, 0x77, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2f, 0x0a, 0x14, 0x72, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x47, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x42, 0x3d, 0x5a, 0x3b, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x6c, 0x75, 0x63, 0x6b, + 0x79, 0x67, 0x69, 0x66, 0x74, 0x2f, 0x76, 0x31, 0x3b, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, + 0x66, 0x74, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_proto_events_luckygift_v1_events_proto_rawDescOnce sync.Once + file_proto_events_luckygift_v1_events_proto_rawDescData = file_proto_events_luckygift_v1_events_proto_rawDesc +) + +func file_proto_events_luckygift_v1_events_proto_rawDescGZIP() []byte { + file_proto_events_luckygift_v1_events_proto_rawDescOnce.Do(func() { + file_proto_events_luckygift_v1_events_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_events_luckygift_v1_events_proto_rawDescData) + }) + return file_proto_events_luckygift_v1_events_proto_rawDescData +} + +var file_proto_events_luckygift_v1_events_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_proto_events_luckygift_v1_events_proto_goTypes = []any{ + (*EventEnvelope)(nil), // 0: hyapp.events.luckygift.v1.EventEnvelope + (*LuckyGiftDrawn)(nil), // 1: hyapp.events.luckygift.v1.LuckyGiftDrawn +} +var file_proto_events_luckygift_v1_events_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_proto_events_luckygift_v1_events_proto_init() } +func file_proto_events_luckygift_v1_events_proto_init() { + if File_proto_events_luckygift_v1_events_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto_events_luckygift_v1_events_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_proto_events_luckygift_v1_events_proto_goTypes, + DependencyIndexes: file_proto_events_luckygift_v1_events_proto_depIdxs, + MessageInfos: file_proto_events_luckygift_v1_events_proto_msgTypes, + }.Build() + File_proto_events_luckygift_v1_events_proto = out.File + file_proto_events_luckygift_v1_events_proto_rawDesc = nil + file_proto_events_luckygift_v1_events_proto_goTypes = nil + file_proto_events_luckygift_v1_events_proto_depIdxs = nil +} diff --git a/api/proto/events/luckygift/v1/events.proto b/api/proto/events/luckygift/v1/events.proto new file mode 100644 index 00000000..62ffe37c --- /dev/null +++ b/api/proto/events/luckygift/v1/events.proto @@ -0,0 +1,54 @@ +syntax = "proto3"; + +package hyapp.events.luckygift.v1; + +option go_package = "hyapp.local/api/proto/events/luckygift/v1;luckygifteventsv1"; + +// EventEnvelope 是 lucky-gift-service 发布 owner outbox 事实的稳定信封。 +// occurred_at_ms 固定为抽奖事实创建时的 Unix epoch milliseconds;MQ 实际发送时间不改变业务发生时间。 +message EventEnvelope { + string event_id = 1; + string event_type = 2; + string app_code = 3; + // draw_id 是本次送礼命令的聚合中奖标识;批量子抽明细仍只保存在 lucky owner 数据库,不能放大 MQ 负载。 + string draw_id = 4; + int64 occurred_at_ms = 5; + bytes body = 6; +} + +// LuckyGiftDrawn 表达钱包返奖已经 granted 的幸运礼物中奖事实。 +// lucky-gift-service 必须先完成 wallet-service 入账和本地 granted 收敛,再发布本事件; +// room-service 与 activity-service 只从该事实派生房内 IM 和区域飘屏,不能反向改变抽奖或账务状态。 +message LuckyGiftDrawn { + string draw_id = 1; + string command_id = 2; + string room_id = 3; + string pool_id = 4; + string gift_id = 5; + int32 gift_count = 6; + int64 sender_user_id = 7; + int64 target_user_id = 8; + // 展示资料是送礼入口固化的快照;消费者不应为了飘屏高频同步反查 user-service。 + string sender_name = 9; + string sender_avatar = 10; + string sender_display_user_id = 11; + string sender_pretty_display_user_id = 12; + // visible_region_id 是房间可见区域,区域播报不能用客户端 IP 或服务所在地推断。 + int64 visible_region_id = 13; + int64 country_id = 14; + int64 coin_spent = 15; + int64 rule_version = 16; + string experience_pool = 17; + string selected_tier_id = 18; + int64 multiplier_ppm = 19; + int64 base_reward_coins = 20; + int64 effective_reward_coins = 21; + bool stage_feedback = 22; + bool high_multiplier = 23; + // reward_status 在本事件中只能是 granted;保留字段是为了下游 JSON 与现有 Flutter 协议同名映射。 + string reward_status = 24; + string wallet_transaction_id = 25; + // 两个时间均为 UTC Unix epoch milliseconds:抽奖发生时间与钱包返奖确认时间不能混用。 + int64 draw_created_at_ms = 26; + int64 reward_granted_at_ms = 27; +} diff --git a/api/proto/events/room/v1/events.pb.go b/api/proto/events/room/v1/events.pb.go index 6aa66187..4dfa0ea1 100644 --- a/api/proto/events/room/v1/events.pb.go +++ b/api/proto/events/room/v1/events.pb.go @@ -479,6 +479,11 @@ type RoomUserJoined struct { Avatar string `protobuf:"bytes,9,opt,name=avatar,proto3" json:"avatar,omitempty"` DisplayUserId string `protobuf:"bytes,10,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"` PrettyDisplayUserId string `protobuf:"bytes,11,opt,name=pretty_display_user_id,json=prettyDisplayUserId,proto3" json:"pretty_display_user_id,omitempty"` + // VIP 字段来自同一次 GetMyVip effective state,首次进房 outbox 与重复进房 direct IM 必须保持同一语义。 + VipProgramType string `protobuf:"bytes,12,opt,name=vip_program_type,json=vipProgramType,proto3" json:"vip_program_type,omitempty"` + EffectiveVipLevel int32 `protobuf:"varint,13,opt,name=effective_vip_level,json=effectiveVipLevel,proto3" json:"effective_vip_level,omitempty"` + EffectiveVipName string `protobuf:"bytes,14,opt,name=effective_vip_name,json=effectiveVipName,proto3" json:"effective_vip_name,omitempty"` + RoomEntryNoticeEnabled bool `protobuf:"varint,15,opt,name=room_entry_notice_enabled,json=roomEntryNoticeEnabled,proto3" json:"room_entry_notice_enabled,omitempty"` } func (x *RoomUserJoined) Reset() { @@ -588,6 +593,34 @@ func (x *RoomUserJoined) GetPrettyDisplayUserId() string { return "" } +func (x *RoomUserJoined) GetVipProgramType() string { + if x != nil { + return x.VipProgramType + } + return "" +} + +func (x *RoomUserJoined) GetEffectiveVipLevel() int32 { + if x != nil { + return x.EffectiveVipLevel + } + return 0 +} + +func (x *RoomUserJoined) GetEffectiveVipName() string { + if x != nil { + return x.EffectiveVipName + } + return "" +} + +func (x *RoomUserJoined) GetRoomEntryNoticeEnabled() bool { + if x != nil { + return x.RoomEntryNoticeEnabled + } + return false +} + // RoomUserLeft 表达用户离房成功。 type RoomUserLeft struct { state protoimpl.MessageState @@ -2078,6 +2111,254 @@ func (x *RoomGiftBatchSent) GetVisibleRegionId() int64 { return 0 } +// RoomLuckyGiftDrawn 是 room-service 从 lucky-gift-service owner 事实派生的真人中奖展示事件。 +// 它只承载腾讯云 IM 房间表现,不是 Room Cell 核心状态,不能推进 room_version、榜单或房间快照。 +type RoomLuckyGiftDrawn struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DrawId string `protobuf:"bytes,1,opt,name=draw_id,json=drawId,proto3" json:"draw_id,omitempty"` + CommandId string `protobuf:"bytes,2,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + PoolId string `protobuf:"bytes,3,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` + GiftId string `protobuf:"bytes,4,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"` + GiftCount int32 `protobuf:"varint,5,opt,name=gift_count,json=giftCount,proto3" json:"gift_count,omitempty"` + SenderUserId int64 `protobuf:"varint,6,opt,name=sender_user_id,json=senderUserId,proto3" json:"sender_user_id,omitempty"` + TargetUserId int64 `protobuf:"varint,7,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + SenderName string `protobuf:"bytes,8,opt,name=sender_name,json=senderName,proto3" json:"sender_name,omitempty"` + SenderAvatar string `protobuf:"bytes,9,opt,name=sender_avatar,json=senderAvatar,proto3" json:"sender_avatar,omitempty"` + SenderDisplayUserId string `protobuf:"bytes,10,opt,name=sender_display_user_id,json=senderDisplayUserId,proto3" json:"sender_display_user_id,omitempty"` + SenderPrettyDisplayUserId string `protobuf:"bytes,11,opt,name=sender_pretty_display_user_id,json=senderPrettyDisplayUserId,proto3" json:"sender_pretty_display_user_id,omitempty"` + VisibleRegionId int64 `protobuf:"varint,12,opt,name=visible_region_id,json=visibleRegionId,proto3" json:"visible_region_id,omitempty"` + CountryId int64 `protobuf:"varint,13,opt,name=country_id,json=countryId,proto3" json:"country_id,omitempty"` + CoinSpent int64 `protobuf:"varint,14,opt,name=coin_spent,json=coinSpent,proto3" json:"coin_spent,omitempty"` + RuleVersion int64 `protobuf:"varint,15,opt,name=rule_version,json=ruleVersion,proto3" json:"rule_version,omitempty"` + ExperiencePool string `protobuf:"bytes,16,opt,name=experience_pool,json=experiencePool,proto3" json:"experience_pool,omitempty"` + SelectedTierId string `protobuf:"bytes,17,opt,name=selected_tier_id,json=selectedTierId,proto3" json:"selected_tier_id,omitempty"` + MultiplierPpm int64 `protobuf:"varint,18,opt,name=multiplier_ppm,json=multiplierPpm,proto3" json:"multiplier_ppm,omitempty"` + BaseRewardCoins int64 `protobuf:"varint,19,opt,name=base_reward_coins,json=baseRewardCoins,proto3" json:"base_reward_coins,omitempty"` + EffectiveRewardCoins int64 `protobuf:"varint,20,opt,name=effective_reward_coins,json=effectiveRewardCoins,proto3" json:"effective_reward_coins,omitempty"` + StageFeedback bool `protobuf:"varint,21,opt,name=stage_feedback,json=stageFeedback,proto3" json:"stage_feedback,omitempty"` + HighMultiplier bool `protobuf:"varint,22,opt,name=high_multiplier,json=highMultiplier,proto3" json:"high_multiplier,omitempty"` + RewardStatus string `protobuf:"bytes,23,opt,name=reward_status,json=rewardStatus,proto3" json:"reward_status,omitempty"` + WalletTransactionId string `protobuf:"bytes,24,opt,name=wallet_transaction_id,json=walletTransactionId,proto3" json:"wallet_transaction_id,omitempty"` + // 时间沿用 lucky owner 的 UTC Unix epoch milliseconds,不以 room-service 消费时间覆盖。 + DrawCreatedAtMs int64 `protobuf:"varint,25,opt,name=draw_created_at_ms,json=drawCreatedAtMs,proto3" json:"draw_created_at_ms,omitempty"` + RewardGrantedAtMs int64 `protobuf:"varint,26,opt,name=reward_granted_at_ms,json=rewardGrantedAtMs,proto3" json:"reward_granted_at_ms,omitempty"` +} + +func (x *RoomLuckyGiftDrawn) Reset() { + *x = RoomLuckyGiftDrawn{} + mi := &file_proto_events_room_v1_events_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoomLuckyGiftDrawn) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoomLuckyGiftDrawn) ProtoMessage() {} + +func (x *RoomLuckyGiftDrawn) ProtoReflect() protoreflect.Message { + mi := &file_proto_events_room_v1_events_proto_msgTypes[20] + 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 RoomLuckyGiftDrawn.ProtoReflect.Descriptor instead. +func (*RoomLuckyGiftDrawn) Descriptor() ([]byte, []int) { + return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{20} +} + +func (x *RoomLuckyGiftDrawn) GetDrawId() string { + if x != nil { + return x.DrawId + } + return "" +} + +func (x *RoomLuckyGiftDrawn) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *RoomLuckyGiftDrawn) GetPoolId() string { + if x != nil { + return x.PoolId + } + return "" +} + +func (x *RoomLuckyGiftDrawn) GetGiftId() string { + if x != nil { + return x.GiftId + } + return "" +} + +func (x *RoomLuckyGiftDrawn) GetGiftCount() int32 { + if x != nil { + return x.GiftCount + } + return 0 +} + +func (x *RoomLuckyGiftDrawn) GetSenderUserId() int64 { + if x != nil { + return x.SenderUserId + } + return 0 +} + +func (x *RoomLuckyGiftDrawn) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *RoomLuckyGiftDrawn) GetSenderName() string { + if x != nil { + return x.SenderName + } + return "" +} + +func (x *RoomLuckyGiftDrawn) GetSenderAvatar() string { + if x != nil { + return x.SenderAvatar + } + return "" +} + +func (x *RoomLuckyGiftDrawn) GetSenderDisplayUserId() string { + if x != nil { + return x.SenderDisplayUserId + } + return "" +} + +func (x *RoomLuckyGiftDrawn) GetSenderPrettyDisplayUserId() string { + if x != nil { + return x.SenderPrettyDisplayUserId + } + return "" +} + +func (x *RoomLuckyGiftDrawn) GetVisibleRegionId() int64 { + if x != nil { + return x.VisibleRegionId + } + return 0 +} + +func (x *RoomLuckyGiftDrawn) GetCountryId() int64 { + if x != nil { + return x.CountryId + } + return 0 +} + +func (x *RoomLuckyGiftDrawn) GetCoinSpent() int64 { + if x != nil { + return x.CoinSpent + } + return 0 +} + +func (x *RoomLuckyGiftDrawn) GetRuleVersion() int64 { + if x != nil { + return x.RuleVersion + } + return 0 +} + +func (x *RoomLuckyGiftDrawn) GetExperiencePool() string { + if x != nil { + return x.ExperiencePool + } + return "" +} + +func (x *RoomLuckyGiftDrawn) GetSelectedTierId() string { + if x != nil { + return x.SelectedTierId + } + return "" +} + +func (x *RoomLuckyGiftDrawn) GetMultiplierPpm() int64 { + if x != nil { + return x.MultiplierPpm + } + return 0 +} + +func (x *RoomLuckyGiftDrawn) GetBaseRewardCoins() int64 { + if x != nil { + return x.BaseRewardCoins + } + return 0 +} + +func (x *RoomLuckyGiftDrawn) GetEffectiveRewardCoins() int64 { + if x != nil { + return x.EffectiveRewardCoins + } + return 0 +} + +func (x *RoomLuckyGiftDrawn) GetStageFeedback() bool { + if x != nil { + return x.StageFeedback + } + return false +} + +func (x *RoomLuckyGiftDrawn) GetHighMultiplier() bool { + if x != nil { + return x.HighMultiplier + } + return false +} + +func (x *RoomLuckyGiftDrawn) GetRewardStatus() string { + if x != nil { + return x.RewardStatus + } + return "" +} + +func (x *RoomLuckyGiftDrawn) GetWalletTransactionId() string { + if x != nil { + return x.WalletTransactionId + } + return "" +} + +func (x *RoomLuckyGiftDrawn) GetDrawCreatedAtMs() int64 { + if x != nil { + return x.DrawCreatedAtMs + } + return 0 +} + +func (x *RoomLuckyGiftDrawn) GetRewardGrantedAtMs() int64 { + if x != nil { + return x.RewardGrantedAtMs + } + return 0 +} + // RoomRobotLuckyGiftDrawn 是机器人房间专用幸运礼物展示事件。 // 它复用客户端 lucky_gift_drawn 表现协议,但不代表真实奖池抽奖和钱包返奖事实。 type RoomRobotLuckyGiftDrawn struct { @@ -2103,7 +2384,7 @@ type RoomRobotLuckyGiftDrawn struct { func (x *RoomRobotLuckyGiftDrawn) Reset() { *x = RoomRobotLuckyGiftDrawn{} - mi := &file_proto_events_room_v1_events_proto_msgTypes[20] + mi := &file_proto_events_room_v1_events_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2115,7 +2396,7 @@ func (x *RoomRobotLuckyGiftDrawn) String() string { func (*RoomRobotLuckyGiftDrawn) ProtoMessage() {} func (x *RoomRobotLuckyGiftDrawn) ProtoReflect() protoreflect.Message { - mi := &file_proto_events_room_v1_events_proto_msgTypes[20] + mi := &file_proto_events_room_v1_events_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2128,7 +2409,7 @@ func (x *RoomRobotLuckyGiftDrawn) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRobotLuckyGiftDrawn.ProtoReflect.Descriptor instead. func (*RoomRobotLuckyGiftDrawn) Descriptor() ([]byte, []int) { - return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{20} + return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{21} } func (x *RoomRobotLuckyGiftDrawn) GetDrawId() string { @@ -2241,7 +2522,7 @@ type RoomHeatChanged struct { func (x *RoomHeatChanged) Reset() { *x = RoomHeatChanged{} - mi := &file_proto_events_room_v1_events_proto_msgTypes[21] + mi := &file_proto_events_room_v1_events_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2253,7 +2534,7 @@ func (x *RoomHeatChanged) String() string { func (*RoomHeatChanged) ProtoMessage() {} func (x *RoomHeatChanged) ProtoReflect() protoreflect.Message { - mi := &file_proto_events_room_v1_events_proto_msgTypes[21] + mi := &file_proto_events_room_v1_events_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2266,7 +2547,7 @@ func (x *RoomHeatChanged) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomHeatChanged.ProtoReflect.Descriptor instead. func (*RoomHeatChanged) Descriptor() ([]byte, []int) { - return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{21} + return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{22} } func (x *RoomHeatChanged) GetDelta() int64 { @@ -2296,7 +2577,7 @@ type RoomRankChanged struct { func (x *RoomRankChanged) Reset() { *x = RoomRankChanged{} - mi := &file_proto_events_room_v1_events_proto_msgTypes[22] + mi := &file_proto_events_room_v1_events_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2308,7 +2589,7 @@ func (x *RoomRankChanged) String() string { func (*RoomRankChanged) ProtoMessage() {} func (x *RoomRankChanged) ProtoReflect() protoreflect.Message { - mi := &file_proto_events_room_v1_events_proto_msgTypes[22] + mi := &file_proto_events_room_v1_events_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2321,7 +2602,7 @@ func (x *RoomRankChanged) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRankChanged.ProtoReflect.Descriptor instead. func (*RoomRankChanged) Descriptor() ([]byte, []int) { - return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{22} + return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{23} } func (x *RoomRankChanged) GetUserId() int64 { @@ -2362,7 +2643,7 @@ type RoomRocketRewardGrant struct { func (x *RoomRocketRewardGrant) Reset() { *x = RoomRocketRewardGrant{} - mi := &file_proto_events_room_v1_events_proto_msgTypes[23] + mi := &file_proto_events_room_v1_events_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2374,7 +2655,7 @@ func (x *RoomRocketRewardGrant) String() string { func (*RoomRocketRewardGrant) ProtoMessage() {} func (x *RoomRocketRewardGrant) ProtoReflect() protoreflect.Message { - mi := &file_proto_events_room_v1_events_proto_msgTypes[23] + mi := &file_proto_events_room_v1_events_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2387,7 +2668,7 @@ func (x *RoomRocketRewardGrant) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRocketRewardGrant.ProtoReflect.Descriptor instead. func (*RoomRocketRewardGrant) Descriptor() ([]byte, []int) { - return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{23} + return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{24} } func (x *RoomRocketRewardGrant) GetRewardRole() string { @@ -2470,7 +2751,7 @@ type RoomRocketFuelChanged struct { func (x *RoomRocketFuelChanged) Reset() { *x = RoomRocketFuelChanged{} - mi := &file_proto_events_room_v1_events_proto_msgTypes[24] + mi := &file_proto_events_room_v1_events_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2482,7 +2763,7 @@ func (x *RoomRocketFuelChanged) String() string { func (*RoomRocketFuelChanged) ProtoMessage() {} func (x *RoomRocketFuelChanged) ProtoReflect() protoreflect.Message { - mi := &file_proto_events_room_v1_events_proto_msgTypes[24] + mi := &file_proto_events_room_v1_events_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2495,7 +2776,7 @@ func (x *RoomRocketFuelChanged) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRocketFuelChanged.ProtoReflect.Descriptor instead. func (*RoomRocketFuelChanged) Descriptor() ([]byte, []int) { - return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{24} + return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{25} } func (x *RoomRocketFuelChanged) GetRocketId() string { @@ -2620,7 +2901,7 @@ type RoomRocketIgnited struct { func (x *RoomRocketIgnited) Reset() { *x = RoomRocketIgnited{} - mi := &file_proto_events_room_v1_events_proto_msgTypes[25] + mi := &file_proto_events_room_v1_events_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2632,7 +2913,7 @@ func (x *RoomRocketIgnited) String() string { func (*RoomRocketIgnited) ProtoMessage() {} func (x *RoomRocketIgnited) ProtoReflect() protoreflect.Message { - mi := &file_proto_events_room_v1_events_proto_msgTypes[25] + mi := &file_proto_events_room_v1_events_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2645,7 +2926,7 @@ func (x *RoomRocketIgnited) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRocketIgnited.ProtoReflect.Descriptor instead. func (*RoomRocketIgnited) Descriptor() ([]byte, []int) { - return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{25} + return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{26} } func (x *RoomRocketIgnited) GetRocketId() string { @@ -2765,7 +3046,7 @@ type RoomRocketLaunched struct { func (x *RoomRocketLaunched) Reset() { *x = RoomRocketLaunched{} - mi := &file_proto_events_room_v1_events_proto_msgTypes[26] + mi := &file_proto_events_room_v1_events_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2777,7 +3058,7 @@ func (x *RoomRocketLaunched) String() string { func (*RoomRocketLaunched) ProtoMessage() {} func (x *RoomRocketLaunched) ProtoReflect() protoreflect.Message { - mi := &file_proto_events_room_v1_events_proto_msgTypes[26] + mi := &file_proto_events_room_v1_events_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2790,7 +3071,7 @@ func (x *RoomRocketLaunched) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRocketLaunched.ProtoReflect.Descriptor instead. func (*RoomRocketLaunched) Descriptor() ([]byte, []int) { - return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{26} + return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{27} } func (x *RoomRocketLaunched) GetRocketId() string { @@ -2869,7 +3150,7 @@ type RoomRocketRewardGranted struct { func (x *RoomRocketRewardGranted) Reset() { *x = RoomRocketRewardGranted{} - mi := &file_proto_events_room_v1_events_proto_msgTypes[27] + mi := &file_proto_events_room_v1_events_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2881,7 +3162,7 @@ func (x *RoomRocketRewardGranted) String() string { func (*RoomRocketRewardGranted) ProtoMessage() {} func (x *RoomRocketRewardGranted) ProtoReflect() protoreflect.Message { - mi := &file_proto_events_room_v1_events_proto_msgTypes[27] + mi := &file_proto_events_room_v1_events_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2894,7 +3175,7 @@ func (x *RoomRocketRewardGranted) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRocketRewardGranted.ProtoReflect.Descriptor instead. func (*RoomRocketRewardGranted) Descriptor() ([]byte, []int) { - return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{27} + return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{28} } func (x *RoomRocketRewardGranted) GetRocketId() string { @@ -2996,7 +3277,7 @@ var file_proto_events_room_v1_events_proto_rawDesc = []byte{ 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, - 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xa6, 0x03, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, + 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xe9, 0x04, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, @@ -3022,470 +3303,547 @@ var file_proto_events_room_v1_events_proto_rawDesc = []byte{ 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x65, 0x74, - 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, - 0x27, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x66, 0x74, 0x12, - 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x48, 0x0a, 0x0a, 0x52, 0x6f, 0x6f, 0x6d, - 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, - 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, - 0x6f, 0x6e, 0x22, 0x9d, 0x05, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, 0x4d, 0x69, 0x63, 0x43, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, - 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x1b, 0x0a, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x08, 0x66, 0x72, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x12, 0x17, 0x0a, 0x07, - 0x74, 0x6f, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x74, - 0x6f, 0x53, 0x65, 0x61, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, - 0x0e, 0x6d, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x69, 0x63, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x73, - 0x74, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, - 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x12, 0x2e, 0x0a, 0x13, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x64, 0x65, 0x61, 0x64, - 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x70, - 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x73, - 0x12, 0x31, 0x0a, 0x15, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x12, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, - 0x65, 0x4d, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x63, 0x5f, 0x6d, 0x75, 0x74, 0x65, 0x64, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6d, 0x69, 0x63, 0x4d, 0x75, 0x74, 0x65, 0x64, - 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x61, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x67, 0x69, 0x66, 0x74, - 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x27, 0x0a, - 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4e, 0x69, - 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x33, 0x0a, 0x16, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x40, 0x0a, 0x1d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x72, 0x65, 0x74, 0x74, - 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x19, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, - 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x22, 0x68, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, - 0x74, 0x4c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, - 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x73, - 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, - 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x22, 0x56, 0x0a, 0x16, - 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, - 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x64, 0x22, 0x7d, 0x0a, 0x13, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x61, 0x73, 0x73, - 0x77, 0x6f, 0x72, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, - 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, - 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x22, 0x76, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x69, 0x6e, - 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, - 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x6f, 0x0a, 0x0d, 0x52, - 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4d, 0x75, 0x74, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, - 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x22, 0x5a, 0x0a, 0x0e, - 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x22, + 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x28, 0x0a, 0x10, 0x76, 0x69, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x76, 0x69, 0x70, 0x50, 0x72, + 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x66, 0x66, + 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x76, 0x69, 0x70, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, + 0x65, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x2c, 0x0a, 0x12, 0x65, 0x66, 0x66, + 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x76, 0x69, 0x70, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, + 0x56, 0x69, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x19, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x5f, 0x65, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x72, 0x6f, 0x6f, 0x6d, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x22, 0x27, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, + 0x66, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x48, 0x0a, 0x0a, 0x52, + 0x6f, 0x6f, 0x6d, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, + 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, + 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x9d, 0x05, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, 0x4d, 0x69, + 0x63, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, + 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x66, 0x72, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x12, + 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x06, 0x74, 0x6f, 0x53, 0x65, 0x61, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x69, 0x63, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, + 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x64, + 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x11, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, + 0x65, 0x4d, 0x73, 0x12, 0x31, 0x0a, 0x15, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x12, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x63, 0x5f, 0x6d, 0x75, + 0x74, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6d, 0x69, 0x63, 0x4d, 0x75, + 0x74, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x61, 0x74, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x67, + 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6e, 0x69, 0x63, 0x6b, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x4e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x33, + 0x0a, 0x16, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x1d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x72, + 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x19, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x68, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x4d, 0x69, 0x63, + 0x53, 0x65, 0x61, 0x74, 0x4c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, + 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, + 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x22, + 0x56, 0x0a, 0x16, 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, + 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, + 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x7d, 0x0a, 0x13, 0x52, 0x6f, 0x6f, 0x6d, 0x50, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x5c, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, - 0x55, 0x73, 0x65, 0x72, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, - 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, + 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x76, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, + 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, + 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x6f, + 0x0a, 0x0d, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4d, 0x75, 0x74, 0x65, 0x64, 0x12, + 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x75, 0x74, + 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x22, + 0x5a, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4b, 0x69, 0x63, 0x6b, 0x65, + 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x5c, 0x0a, 0x10, 0x52, + 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x12, + 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xf1, 0x09, 0x0a, 0x0c, 0x52, 0x6f, + 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, + 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xf1, 0x09, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, 0x47, - 0x69, 0x66, 0x74, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, - 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, - 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, - 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x09, 0x67, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, - 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, - 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, - 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, - 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x67, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, - 0x10, 0x63, 0x70, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x70, 0x52, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x63, 0x6f, - 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x67, 0x69, 0x66, - 0x74, 0x49, 0x63, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x66, 0x74, - 0x5f, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x11, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x66, 0x74, 0x41, 0x6e, 0x69, 0x6d, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x65, 0x66, 0x66, 0x65, 0x63, - 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x67, - 0x69, 0x66, 0x74, 0x45, 0x66, 0x66, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x22, - 0x0a, 0x0d, 0x69, 0x73, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x18, - 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x47, 0x69, - 0x66, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, 0x69, 0x63, 0x5f, - 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x18, 0x15, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x12, 0x73, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, 0x69, 0x63, 0x4c, 0x75, 0x63, 0x6b, 0x79, - 0x47, 0x69, 0x66, 0x74, 0x12, 0x2f, 0x0a, 0x14, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x72, 0x6f, 0x6f, - 0x6d, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x18, 0x16, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x11, 0x72, 0x65, 0x61, 0x6c, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, - 0x74, 0x47, 0x69, 0x66, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, - 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, - 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, - 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x33, 0x0a, 0x16, 0x73, - 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x73, 0x65, 0x6e, - 0x64, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x40, 0x0a, 0x1d, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x74, 0x74, - 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x19, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x50, - 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x6e, - 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x72, - 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x4e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x61, 0x76, 0x61, 0x74, - 0x61, 0x72, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, - 0x65, 0x72, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x37, 0x0a, 0x18, 0x72, 0x65, 0x63, 0x65, - 0x69, 0x76, 0x65, 0x72, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x72, 0x65, 0x63, 0x65, - 0x69, 0x76, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x44, 0x0a, 0x1f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x70, 0x72, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, + 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2c, 0x0a, + 0x12, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, + 0x6e, 0x67, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, + 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, + 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, + 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x1b, 0x0a, + 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x67, 0x69, + 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0d, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x67, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, + 0x12, 0x28, 0x0a, 0x10, 0x63, 0x70, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x70, 0x52, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x69, + 0x66, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, + 0x69, 0x66, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x69, 0x66, 0x74, 0x5f, + 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x67, 0x69, 0x66, 0x74, 0x49, 0x63, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x2c, 0x0a, 0x12, 0x67, + 0x69, 0x66, 0x74, 0x5f, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, + 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x66, 0x74, 0x41, 0x6e, 0x69, + 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x12, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x65, 0x66, + 0x66, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x45, 0x66, 0x66, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x69, 0x73, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x67, 0x69, + 0x66, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x52, 0x6f, 0x62, 0x6f, + 0x74, 0x47, 0x69, 0x66, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, + 0x69, 0x63, 0x5f, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x18, 0x15, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x12, 0x73, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, 0x69, 0x63, 0x4c, 0x75, + 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x12, 0x2f, 0x0a, 0x14, 0x72, 0x65, 0x61, 0x6c, 0x5f, + 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x18, + 0x16, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x72, 0x65, 0x61, 0x6c, 0x52, 0x6f, 0x6f, 0x6d, 0x52, + 0x6f, 0x62, 0x6f, 0x74, 0x47, 0x69, 0x66, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x6e, 0x64, + 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, + 0x65, 0x6e, 0x64, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x33, + 0x0a, 0x16, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, + 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x1d, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x72, 0x65, 0x63, 0x65, - 0x69, 0x76, 0x65, 0x72, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, - 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x22, 0xdc, 0x04, 0x0a, 0x18, 0x52, - 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x75, 0x63, 0x6b, - 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, - 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x64, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x61, 0x77, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, - 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, - 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, - 0x75, 0x6c, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x72, 0x75, 0x6c, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x27, - 0x0a, 0x0f, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x70, 0x6f, 0x6f, - 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x65, - 0x6e, 0x63, 0x65, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x54, 0x69, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x5f, - 0x70, 0x70, 0x6d, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x75, 0x6c, 0x74, 0x69, - 0x70, 0x6c, 0x69, 0x65, 0x72, 0x50, 0x70, 0x6d, 0x12, 0x2a, 0x0a, 0x11, 0x62, 0x61, 0x73, 0x65, - 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0f, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, - 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, - 0x65, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, - 0x6b, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x67, 0x65, 0x46, 0x65, - 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x27, 0x0a, 0x0f, 0x68, 0x69, 0x67, 0x68, 0x5f, 0x6d, - 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x0e, 0x68, 0x69, 0x67, 0x68, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x12, - 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, - 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, - 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x96, 0x04, 0x0a, 0x13, 0x52, 0x6f, - 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x54, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, - 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x4e, 0x69, 0x63, 0x6b, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, - 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, - 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x37, 0x0a, - 0x18, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, - 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x15, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x44, 0x0a, 0x1f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, - 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, - 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x1b, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x47, 0x69, - 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, - 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, - 0x6e, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, - 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x63, 0x65, 0x69, - 0x70, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, - 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x49, 0x64, 0x12, 0x4d, 0x0a, 0x0a, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x67, 0x69, 0x66, - 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x75, 0x63, 0x6b, - 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x09, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, - 0x66, 0x74, 0x22, 0x89, 0x07, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, - 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, - 0x0a, 0x0b, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, - 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x76, - 0x61, 0x74, 0x61, 0x72, 0x12, 0x33, 0x0a, 0x16, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x64, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x1d, 0x73, 0x65, 0x6e, - 0x64, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x19, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, - 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, - 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x67, 0x69, 0x66, - 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, - 0x6f, 0x74, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x28, 0x0a, - 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x6e, - 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, - 0x69, 0x6e, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, 0x6c, 0x6c, 0x69, - 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x63, 0x65, - 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x68, 0x65, - 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x6d, 0x48, 0x65, - 0x61, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x67, - 0x69, 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0d, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x67, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, - 0x65, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x70, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x70, 0x52, - 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, - 0x69, 0x66, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x67, 0x69, 0x66, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x69, 0x66, 0x74, - 0x5f, 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x67, 0x69, 0x66, 0x74, 0x49, 0x63, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x2c, 0x0a, 0x12, - 0x67, 0x69, 0x66, 0x74, 0x5f, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, - 0x72, 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x66, 0x74, 0x41, 0x6e, - 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x69, - 0x66, 0x74, 0x5f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, - 0x12, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x45, 0x66, 0x66, 0x65, 0x63, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x03, 0x52, - 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x43, - 0x0a, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x72, - 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x07, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, - 0x64, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x16, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, - 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x80, - 0x04, 0x0a, 0x17, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x4c, 0x75, 0x63, 0x6b, - 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, - 0x61, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x61, - 0x77, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, - 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, - 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x19, 0x73, 0x65, 0x6e, 0x64, + 0x65, 0x72, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, + 0x72, 0x5f, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x4e, 0x69, 0x63, 0x6b, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x61, + 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x63, + 0x65, 0x69, 0x76, 0x65, 0x72, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x37, 0x0a, 0x18, 0x72, + 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x72, + 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x44, 0x0a, 0x1f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, + 0x5f, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x72, + 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x22, 0xdc, 0x04, + 0x0a, 0x18, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4c, + 0x75, 0x63, 0x6b, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x61, 0x77, 0x49, 0x64, 0x12, 0x1d, 0x0a, + 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, + 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, + 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x21, + 0x0a, 0x0c, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x75, 0x6c, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, + 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, 0x70, 0x65, + 0x72, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, + 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x54, 0x69, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, + 0x65, 0x72, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x75, + 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x50, 0x70, 0x6d, 0x12, 0x2a, 0x0a, 0x11, 0x62, + 0x61, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x65, 0x66, 0x66, 0x65, 0x63, + 0x74, 0x69, 0x76, 0x65, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, + 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x23, 0x0a, + 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x64, + 0x62, 0x61, 0x63, 0x6b, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x67, + 0x65, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x27, 0x0a, 0x0f, 0x68, 0x69, 0x67, + 0x68, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x18, 0x0e, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0e, 0x68, 0x69, 0x67, 0x68, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, + 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x5f, 0x6d, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x96, 0x04, 0x0a, + 0x13, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, + 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x4e, + 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x63, 0x65, 0x69, + 0x76, 0x65, 0x72, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0e, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, + 0x12, 0x37, 0x0a, 0x18, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x15, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x44, 0x0a, 0x1f, 0x72, 0x65, 0x63, + 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x1b, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x50, 0x72, 0x65, 0x74, + 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2a, + 0x0a, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x47, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, + 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, + 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, 0x6c, + 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x65, + 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x4d, 0x0a, 0x0a, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, + 0x67, 0x69, 0x66, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4c, + 0x75, 0x63, 0x6b, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x09, 0x6c, 0x75, 0x63, 0x6b, + 0x79, 0x47, 0x69, 0x66, 0x74, 0x22, 0x89, 0x07, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, + 0x66, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, + 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x76, 0x61, + 0x74, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x33, 0x0a, 0x16, 0x73, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x44, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x1d, + 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x19, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x72, 0x65, 0x74, 0x74, + 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, + 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, - 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, - 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, - 0x25, 0x0a, 0x0e, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x5f, 0x70, 0x70, - 0x6d, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, - 0x69, 0x65, 0x72, 0x50, 0x70, 0x6d, 0x12, 0x2a, 0x0a, 0x11, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x72, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0f, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, - 0x6e, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, - 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x14, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x77, - 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2a, 0x0a, 0x11, - 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, - 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x72, - 0x6f, 0x62, 0x6f, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x52, 0x6f, - 0x62, 0x6f, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, 0x69, 0x63, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, 0x69, - 0x63, 0x22, 0x4a, 0x0a, 0x0f, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x74, 0x43, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x05, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0b, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, 0x74, 0x22, 0x5f, 0x0a, - 0x0f, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x61, 0x6e, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, - 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, - 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x12, - 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x94, - 0x02, 0x0a, 0x15, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77, - 0x61, 0x72, 0x64, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x74, 0x65, - 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, - 0x75, 0x70, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x63, 0x6f, 0x6e, 0x5f, - 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x63, 0x6f, 0x6e, 0x55, - 0x72, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xeb, 0x03, 0x0a, 0x15, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, - 0x63, 0x6b, 0x65, 0x74, 0x46, 0x75, 0x65, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, - 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, - 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, - 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x66, 0x75, 0x65, 0x6c, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x61, 0x64, 0x64, 0x65, 0x64, 0x46, 0x75, 0x65, - 0x6c, 0x12, 0x30, 0x0a, 0x14, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x61, - 0x64, 0x64, 0x65, 0x64, 0x5f, 0x66, 0x75, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x12, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x41, 0x64, 0x64, 0x65, 0x64, 0x46, - 0x75, 0x65, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x5f, - 0x66, 0x75, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6f, 0x76, 0x65, 0x72, - 0x66, 0x6c, 0x6f, 0x77, 0x46, 0x75, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x74, 0x5f, 0x66, 0x75, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, + 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, + 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, + 0x70, 0x65, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, + 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, + 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x6d, + 0x5f, 0x68, 0x65, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x6f, 0x6f, + 0x6d, 0x48, 0x65, 0x61, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x24, + 0x0a, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x67, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x70, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x63, 0x70, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, + 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x67, + 0x69, 0x66, 0x74, 0x5f, 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x10, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x67, 0x69, 0x66, 0x74, 0x49, 0x63, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, + 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x66, + 0x74, 0x41, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x2a, 0x0a, + 0x11, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x18, 0x12, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x45, 0x66, + 0x66, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x13, 0x20, 0x03, + 0x28, 0x03, 0x52, 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x73, 0x12, 0x43, 0x0a, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x18, 0x14, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x73, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, + 0x66, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x07, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x5f, 0x69, 0x64, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, + 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x16, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, + 0x64, 0x22, 0x96, 0x08, 0x0a, 0x12, 0x52, 0x6f, 0x6f, 0x6d, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, + 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x61, 0x77, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x61, 0x77, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, + 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, + 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, + 0x0b, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, + 0x0a, 0x0d, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x76, 0x61, + 0x74, 0x61, 0x72, 0x12, 0x33, 0x0a, 0x16, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x64, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x13, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x1d, 0x73, 0x65, 0x6e, 0x64, + 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, + 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x19, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, + 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, + 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x70, + 0x65, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x53, + 0x70, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x75, 0x6c, 0x65, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x78, 0x70, 0x65, 0x72, + 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x50, 0x6f, 0x6f, 0x6c, + 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x54, 0x69, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x75, + 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x12, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x50, 0x70, + 0x6d, 0x12, 0x2a, 0x0a, 0x11, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x62, 0x61, + 0x73, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x34, 0x0a, + 0x16, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, + 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x65, + 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, + 0x69, 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x65, 0x65, + 0x64, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x15, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74, 0x61, + 0x67, 0x65, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x27, 0x0a, 0x0f, 0x68, 0x69, + 0x67, 0x68, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x18, 0x16, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0e, 0x68, 0x69, 0x67, 0x68, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, + 0x69, 0x65, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x12, + 0x64, 0x72, 0x61, 0x77, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x19, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x64, 0x72, 0x61, 0x77, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2f, 0x0a, 0x14, 0x72, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, + 0x73, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x47, + 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x80, 0x04, 0x0a, 0x17, 0x52, + 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, + 0x74, 0x44, 0x72, 0x61, 0x77, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x61, 0x77, 0x49, 0x64, 0x12, + 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, + 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, + 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, + 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d, + 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x50, + 0x70, 0x6d, 0x12, 0x2a, 0x0a, 0x11, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, + 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x62, + 0x61, 0x73, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x34, + 0x0a, 0x16, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x72, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, + 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, + 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, + 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x12, + 0x1c, 0x0a, 0x09, 0x73, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, 0x69, 0x63, 0x18, 0x0e, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x09, 0x73, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, 0x69, 0x63, 0x22, 0x4a, 0x0a, + 0x0f, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, + 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x05, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x5f, 0x68, 0x65, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, 0x74, 0x22, 0x5f, 0x0a, 0x0f, 0x52, 0x6f, 0x6f, + 0x6d, 0x52, 0x61, 0x6e, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x17, 0x0a, 0x07, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x67, + 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x09, 0x67, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x94, 0x02, 0x0a, 0x15, 0x52, + 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x47, + 0x72, 0x61, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x72, + 0x6f, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x77, 0x61, 0x72, + 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, + 0x0a, 0x0e, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, + 0x65, 0x6d, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, + 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x63, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x19, + 0x0a, 0x08, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x22, 0xeb, 0x03, 0x0a, 0x15, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, + 0x46, 0x75, 0x65, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, + 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, + 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1d, + 0x0a, 0x0a, 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x66, 0x75, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x09, 0x61, 0x64, 0x64, 0x65, 0x64, 0x46, 0x75, 0x65, 0x6c, 0x12, 0x30, 0x0a, + 0x14, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, + 0x5f, 0x66, 0x75, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x65, 0x66, 0x66, + 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x41, 0x64, 0x64, 0x65, 0x64, 0x46, 0x75, 0x65, 0x6c, 0x12, + 0x23, 0x0a, 0x0d, 0x6f, 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x66, 0x75, 0x65, 0x6c, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6f, 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, + 0x46, 0x75, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, + 0x66, 0x75, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x74, 0x46, 0x75, 0x65, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x75, 0x65, 0x6c, 0x5f, + 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0d, 0x66, 0x75, 0x65, 0x6c, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5f, + 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x73, + 0x65, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, + 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, + 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, + 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, + 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, + 0x82, 0x04, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x67, + 0x6e, 0x69, 0x74, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, + 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x74, 0x5f, 0x66, 0x75, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x46, 0x75, 0x65, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x66, - 0x75, 0x65, 0x6c, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x07, 0x20, + 0x75, 0x65, 0x6c, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66, 0x75, 0x65, 0x6c, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, - 0x6c, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x65, - 0x73, 0x65, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x09, 0x72, 0x65, 0x73, 0x65, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, - 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, - 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, - 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, - 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x22, 0x82, 0x04, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, - 0x65, 0x74, 0x49, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x63, + 0x6c, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x69, 0x67, 0x6e, 0x69, 0x74, + 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6c, 0x61, + 0x75, 0x6e, 0x63, 0x68, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x62, 0x72, 0x6f, 0x61, + 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x53, 0x63, 0x6f, 0x70, + 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, + 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x20, 0x0a, + 0x0c, 0x74, 0x6f, 0x70, 0x31, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x70, 0x31, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x26, 0x0a, 0x0f, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, + 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, + 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, + 0x6f, 0x6f, 0x6d, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x6f, + 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x76, 0x65, + 0x72, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x74, + 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x73, 0x65, 0x74, + 0x41, 0x74, 0x4d, 0x73, 0x22, 0xe6, 0x02, 0x0a, 0x12, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, + 0x6b, 0x65, 0x74, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, + 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, + 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1d, + 0x0a, 0x0a, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x09, 0x6e, 0x65, 0x78, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x24, 0x0a, + 0x0e, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x64, 0x41, + 0x74, 0x4d, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x73, 0x65, 0x74, 0x41, + 0x74, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x74, 0x6f, 0x70, 0x31, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x70, 0x31, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, + 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x27, 0x0a, + 0x10, 0x69, 0x6e, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x45, 0x0a, 0x07, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x47, + 0x72, 0x61, 0x6e, 0x74, 0x52, 0x07, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x22, 0x93, 0x01, + 0x0a, 0x17, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, - 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x66, 0x75, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x46, 0x75, 0x65, 0x6c, 0x12, - 0x25, 0x0a, 0x0e, 0x66, 0x75, 0x65, 0x6c, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66, 0x75, 0x65, 0x6c, 0x54, 0x68, 0x72, - 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, - 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x69, - 0x67, 0x6e, 0x69, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x6c, 0x61, - 0x75, 0x6e, 0x63, 0x68, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0a, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x0f, - 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, - 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, - 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, - 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x74, 0x6f, 0x70, 0x31, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x70, 0x31, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x67, - 0x6e, 0x69, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x6f, - 0x6f, 0x6d, 0x5f, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x28, - 0x0a, 0x10, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, - 0x72, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, - 0x43, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x65, - 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, - 0x65, 0x73, 0x65, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xe6, 0x02, 0x0a, 0x12, 0x52, 0x6f, 0x6f, - 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x64, 0x12, - 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, - 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, - 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6e, 0x65, 0x78, 0x74, 0x4c, 0x65, 0x76, 0x65, - 0x6c, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x61, 0x75, 0x6e, 0x63, - 0x68, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x74, - 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, - 0x73, 0x65, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x74, 0x6f, 0x70, 0x31, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, - 0x6f, 0x70, 0x31, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x67, 0x6e, - 0x69, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0d, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x27, 0x0a, 0x10, 0x69, 0x6e, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x52, - 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x45, 0x0a, 0x07, 0x72, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77, - 0x61, 0x72, 0x64, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x07, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x73, 0x22, 0x93, 0x01, 0x0a, 0x17, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, - 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x12, 0x1b, 0x0a, - 0x09, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, - 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, - 0x12, 0x45, 0x0a, 0x07, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, - 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, - 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x07, - 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x42, 0x33, 0x5a, 0x31, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x72, 0x6f, 0x6f, 0x6d, 0x2f, 0x76, 0x31, 0x3b, - 0x72, 0x6f, 0x6f, 0x6d, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x45, 0x0a, 0x07, + 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, + 0x65, 0x77, 0x61, 0x72, 0x64, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x07, 0x72, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x73, 0x42, 0x33, 0x5a, 0x31, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x73, 0x2f, 0x72, 0x6f, 0x6f, 0x6d, 0x2f, 0x76, 0x31, 0x3b, 0x72, 0x6f, 0x6f, 0x6d, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -3500,7 +3858,7 @@ func file_proto_events_room_v1_events_proto_rawDescGZIP() []byte { return file_proto_events_room_v1_events_proto_rawDescData } -var file_proto_events_room_v1_events_proto_msgTypes = make([]protoimpl.MessageInfo, 28) +var file_proto_events_room_v1_events_proto_msgTypes = make([]protoimpl.MessageInfo, 29) var file_proto_events_room_v1_events_proto_goTypes = []any{ (*EventEnvelope)(nil), // 0: hyapp.events.room.v1.EventEnvelope (*RoomCreated)(nil), // 1: hyapp.events.room.v1.RoomCreated @@ -3522,21 +3880,22 @@ var file_proto_events_room_v1_events_proto_goTypes = []any{ (*RoomGiftBatchLuckyResult)(nil), // 17: hyapp.events.room.v1.RoomGiftBatchLuckyResult (*RoomGiftBatchTarget)(nil), // 18: hyapp.events.room.v1.RoomGiftBatchTarget (*RoomGiftBatchSent)(nil), // 19: hyapp.events.room.v1.RoomGiftBatchSent - (*RoomRobotLuckyGiftDrawn)(nil), // 20: hyapp.events.room.v1.RoomRobotLuckyGiftDrawn - (*RoomHeatChanged)(nil), // 21: hyapp.events.room.v1.RoomHeatChanged - (*RoomRankChanged)(nil), // 22: hyapp.events.room.v1.RoomRankChanged - (*RoomRocketRewardGrant)(nil), // 23: hyapp.events.room.v1.RoomRocketRewardGrant - (*RoomRocketFuelChanged)(nil), // 24: hyapp.events.room.v1.RoomRocketFuelChanged - (*RoomRocketIgnited)(nil), // 25: hyapp.events.room.v1.RoomRocketIgnited - (*RoomRocketLaunched)(nil), // 26: hyapp.events.room.v1.RoomRocketLaunched - (*RoomRocketRewardGranted)(nil), // 27: hyapp.events.room.v1.RoomRocketRewardGranted + (*RoomLuckyGiftDrawn)(nil), // 20: hyapp.events.room.v1.RoomLuckyGiftDrawn + (*RoomRobotLuckyGiftDrawn)(nil), // 21: hyapp.events.room.v1.RoomRobotLuckyGiftDrawn + (*RoomHeatChanged)(nil), // 22: hyapp.events.room.v1.RoomHeatChanged + (*RoomRankChanged)(nil), // 23: hyapp.events.room.v1.RoomRankChanged + (*RoomRocketRewardGrant)(nil), // 24: hyapp.events.room.v1.RoomRocketRewardGrant + (*RoomRocketFuelChanged)(nil), // 25: hyapp.events.room.v1.RoomRocketFuelChanged + (*RoomRocketIgnited)(nil), // 26: hyapp.events.room.v1.RoomRocketIgnited + (*RoomRocketLaunched)(nil), // 27: hyapp.events.room.v1.RoomRocketLaunched + (*RoomRocketRewardGranted)(nil), // 28: hyapp.events.room.v1.RoomRocketRewardGranted } var file_proto_events_room_v1_events_proto_depIdxs = []int32{ 4, // 0: hyapp.events.room.v1.RoomUserJoined.entry_vehicle:type_name -> hyapp.events.room.v1.RoomEntryVehicleSnapshot 17, // 1: hyapp.events.room.v1.RoomGiftBatchTarget.lucky_gift:type_name -> hyapp.events.room.v1.RoomGiftBatchLuckyResult 18, // 2: hyapp.events.room.v1.RoomGiftBatchSent.targets:type_name -> hyapp.events.room.v1.RoomGiftBatchTarget - 23, // 3: hyapp.events.room.v1.RoomRocketLaunched.rewards:type_name -> hyapp.events.room.v1.RoomRocketRewardGrant - 23, // 4: hyapp.events.room.v1.RoomRocketRewardGranted.rewards:type_name -> hyapp.events.room.v1.RoomRocketRewardGrant + 24, // 3: hyapp.events.room.v1.RoomRocketLaunched.rewards:type_name -> hyapp.events.room.v1.RoomRocketRewardGrant + 24, // 4: hyapp.events.room.v1.RoomRocketRewardGranted.rewards:type_name -> hyapp.events.room.v1.RoomRocketRewardGrant 5, // [5:5] is the sub-list for method output_type 5, // [5:5] is the sub-list for method input_type 5, // [5:5] is the sub-list for extension type_name @@ -3555,7 +3914,7 @@ func file_proto_events_room_v1_events_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_events_room_v1_events_proto_rawDesc, NumEnums: 0, - NumMessages: 28, + NumMessages: 29, NumExtensions: 0, NumServices: 0, }, diff --git a/api/proto/events/room/v1/events.proto b/api/proto/events/room/v1/events.proto index a58e1fc0..17aea2c5 100644 --- a/api/proto/events/room/v1/events.proto +++ b/api/proto/events/room/v1/events.proto @@ -72,6 +72,11 @@ message RoomUserJoined { string avatar = 9; string display_user_id = 10; string pretty_display_user_id = 11; + // VIP 字段来自同一次 GetMyVip effective state,首次进房 outbox 与重复进房 direct IM 必须保持同一语义。 + string vip_program_type = 12; + int32 effective_vip_level = 13; + string effective_vip_name = 14; + bool room_entry_notice_enabled = 15; } // RoomUserLeft 表达用户离房成功。 @@ -262,6 +267,38 @@ message RoomGiftBatchSent { int64 visible_region_id = 22; } +// RoomLuckyGiftDrawn 是 room-service 从 lucky-gift-service owner 事实派生的真人中奖展示事件。 +// 它只承载腾讯云 IM 房间表现,不是 Room Cell 核心状态,不能推进 room_version、榜单或房间快照。 +message RoomLuckyGiftDrawn { + string draw_id = 1; + string command_id = 2; + string pool_id = 3; + string gift_id = 4; + int32 gift_count = 5; + int64 sender_user_id = 6; + int64 target_user_id = 7; + string sender_name = 8; + string sender_avatar = 9; + string sender_display_user_id = 10; + string sender_pretty_display_user_id = 11; + int64 visible_region_id = 12; + int64 country_id = 13; + int64 coin_spent = 14; + int64 rule_version = 15; + string experience_pool = 16; + string selected_tier_id = 17; + int64 multiplier_ppm = 18; + int64 base_reward_coins = 19; + int64 effective_reward_coins = 20; + bool stage_feedback = 21; + bool high_multiplier = 22; + string reward_status = 23; + string wallet_transaction_id = 24; + // 时间沿用 lucky owner 的 UTC Unix epoch milliseconds,不以 room-service 消费时间覆盖。 + int64 draw_created_at_ms = 25; + int64 reward_granted_at_ms = 26; +} + // RoomRobotLuckyGiftDrawn 是机器人房间专用幸运礼物展示事件。 // 它复用客户端 lucky_gift_drawn 表现协议,但不代表真实奖池抽奖和钱包返奖事实。 message RoomRobotLuckyGiftDrawn { diff --git a/api/proto/luckygift/v1/luckygift.pb.go b/api/proto/luckygift/v1/luckygift.pb.go index 95a41b3b..22c23a70 100644 --- a/api/proto/luckygift/v1/luckygift.pb.go +++ b/api/proto/luckygift/v1/luckygift.pb.go @@ -120,6 +120,11 @@ type LuckyGiftMeta struct { GiftCount int32 `protobuf:"varint,12,opt,name=gift_count,json=giftCount,proto3" json:"gift_count,omitempty"` VisibleRegionId int64 `protobuf:"varint,13,opt,name=visible_region_id,json=visibleRegionId,proto3" json:"visible_region_id,omitempty"` CountryId int64 `protobuf:"varint,14,opt,name=country_id,json=countryId,proto3" json:"country_id,omitempty"` + // sender_* 是 room-service 在送礼入口已经确认的展示快照,只进入中奖事实和 IM,不能参与抽奖或账务判断。 + SenderName string `protobuf:"bytes,15,opt,name=sender_name,json=senderName,proto3" json:"sender_name,omitempty"` + SenderAvatar string `protobuf:"bytes,16,opt,name=sender_avatar,json=senderAvatar,proto3" json:"sender_avatar,omitempty"` + SenderDisplayUserId string `protobuf:"bytes,17,opt,name=sender_display_user_id,json=senderDisplayUserId,proto3" json:"sender_display_user_id,omitempty"` + SenderPrettyDisplayUserId string `protobuf:"bytes,18,opt,name=sender_pretty_display_user_id,json=senderPrettyDisplayUserId,proto3" json:"sender_pretty_display_user_id,omitempty"` } func (x *LuckyGiftMeta) Reset() { @@ -250,6 +255,34 @@ func (x *LuckyGiftMeta) GetCountryId() int64 { return 0 } +func (x *LuckyGiftMeta) GetSenderName() string { + if x != nil { + return x.SenderName + } + return "" +} + +func (x *LuckyGiftMeta) GetSenderAvatar() string { + if x != nil { + return x.SenderAvatar + } + return "" +} + +func (x *LuckyGiftMeta) GetSenderDisplayUserId() string { + if x != nil { + return x.SenderDisplayUserId + } + return "" +} + +func (x *LuckyGiftMeta) GetSenderPrettyDisplayUserId() string { + if x != nil { + return x.SenderPrettyDisplayUserId + } + return "" +} + type LuckyGiftRuleTier struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -728,29 +761,30 @@ type LuckyGiftDrawResult struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - DrawId string `protobuf:"bytes,1,opt,name=draw_id,json=drawId,proto3" json:"draw_id,omitempty"` - CommandId string `protobuf:"bytes,2,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` - GiftId string `protobuf:"bytes,3,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"` - RuleVersion int64 `protobuf:"varint,4,opt,name=rule_version,json=ruleVersion,proto3" json:"rule_version,omitempty"` - ExperiencePool string `protobuf:"bytes,5,opt,name=experience_pool,json=experiencePool,proto3" json:"experience_pool,omitempty"` - SelectedTierId string `protobuf:"bytes,6,opt,name=selected_tier_id,json=selectedTierId,proto3" json:"selected_tier_id,omitempty"` - BaseRewardCoins int64 `protobuf:"varint,7,opt,name=base_reward_coins,json=baseRewardCoins,proto3" json:"base_reward_coins,omitempty"` - EffectiveRewardCoins int64 `protobuf:"varint,8,opt,name=effective_reward_coins,json=effectiveRewardCoins,proto3" json:"effective_reward_coins,omitempty"` - RewardStatus string `protobuf:"bytes,9,opt,name=reward_status,json=rewardStatus,proto3" json:"reward_status,omitempty"` - RtpWindowIndex int64 `protobuf:"varint,10,opt,name=rtp_window_index,json=rtpWindowIndex,proto3" json:"rtp_window_index,omitempty"` - GiftRtpWindowIndex int64 `protobuf:"varint,11,opt,name=gift_rtp_window_index,json=giftRtpWindowIndex,proto3" json:"gift_rtp_window_index,omitempty"` - GlobalBaseRtpPpm int64 `protobuf:"varint,12,opt,name=global_base_rtp_ppm,json=globalBaseRtpPpm,proto3" json:"global_base_rtp_ppm,omitempty"` - GiftBaseRtpPpm int64 `protobuf:"varint,13,opt,name=gift_base_rtp_ppm,json=giftBaseRtpPpm,proto3" json:"gift_base_rtp_ppm,omitempty"` - StageFeedback bool `protobuf:"varint,14,opt,name=stage_feedback,json=stageFeedback,proto3" json:"stage_feedback,omitempty"` - HighMultiplier bool `protobuf:"varint,15,opt,name=high_multiplier,json=highMultiplier,proto3" json:"high_multiplier,omitempty"` - CreatedAtMs int64 `protobuf:"varint,16,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` - PoolId string `protobuf:"bytes,17,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` - MultiplierPpm int64 `protobuf:"varint,18,opt,name=multiplier_ppm,json=multiplierPpm,proto3" json:"multiplier_ppm,omitempty"` - WalletTransactionId string `protobuf:"bytes,19,opt,name=wallet_transaction_id,json=walletTransactionId,proto3" json:"wallet_transaction_id,omitempty"` - CoinBalanceAfter int64 `protobuf:"varint,20,opt,name=coin_balance_after,json=coinBalanceAfter,proto3" json:"coin_balance_after,omitempty"` - UserId int64 `protobuf:"varint,21,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - ExternalUserId string `protobuf:"bytes,22,opt,name=external_user_id,json=externalUserId,proto3" json:"external_user_id,omitempty"` - AppCode string `protobuf:"bytes,23,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + DrawId string `protobuf:"bytes,1,opt,name=draw_id,json=drawId,proto3" json:"draw_id,omitempty"` + CommandId string `protobuf:"bytes,2,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + GiftId string `protobuf:"bytes,3,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"` + RuleVersion int64 `protobuf:"varint,4,opt,name=rule_version,json=ruleVersion,proto3" json:"rule_version,omitempty"` + ExperiencePool string `protobuf:"bytes,5,opt,name=experience_pool,json=experiencePool,proto3" json:"experience_pool,omitempty"` + SelectedTierId string `protobuf:"bytes,6,opt,name=selected_tier_id,json=selectedTierId,proto3" json:"selected_tier_id,omitempty"` + BaseRewardCoins int64 `protobuf:"varint,7,opt,name=base_reward_coins,json=baseRewardCoins,proto3" json:"base_reward_coins,omitempty"` + EffectiveRewardCoins int64 `protobuf:"varint,8,opt,name=effective_reward_coins,json=effectiveRewardCoins,proto3" json:"effective_reward_coins,omitempty"` + RewardStatus string `protobuf:"bytes,9,opt,name=reward_status,json=rewardStatus,proto3" json:"reward_status,omitempty"` + RtpWindowIndex int64 `protobuf:"varint,10,opt,name=rtp_window_index,json=rtpWindowIndex,proto3" json:"rtp_window_index,omitempty"` + GiftRtpWindowIndex int64 `protobuf:"varint,11,opt,name=gift_rtp_window_index,json=giftRtpWindowIndex,proto3" json:"gift_rtp_window_index,omitempty"` + GlobalBaseRtpPpm int64 `protobuf:"varint,12,opt,name=global_base_rtp_ppm,json=globalBaseRtpPpm,proto3" json:"global_base_rtp_ppm,omitempty"` + GiftBaseRtpPpm int64 `protobuf:"varint,13,opt,name=gift_base_rtp_ppm,json=giftBaseRtpPpm,proto3" json:"gift_base_rtp_ppm,omitempty"` + StageFeedback bool `protobuf:"varint,14,opt,name=stage_feedback,json=stageFeedback,proto3" json:"stage_feedback,omitempty"` + HighMultiplier bool `protobuf:"varint,15,opt,name=high_multiplier,json=highMultiplier,proto3" json:"high_multiplier,omitempty"` + CreatedAtMs int64 `protobuf:"varint,16,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + PoolId string `protobuf:"bytes,17,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` + MultiplierPpm int64 `protobuf:"varint,18,opt,name=multiplier_ppm,json=multiplierPpm,proto3" json:"multiplier_ppm,omitempty"` + WalletTransactionId string `protobuf:"bytes,19,opt,name=wallet_transaction_id,json=walletTransactionId,proto3" json:"wallet_transaction_id,omitempty"` + CoinBalanceAfter int64 `protobuf:"varint,20,opt,name=coin_balance_after,json=coinBalanceAfter,proto3" json:"coin_balance_after,omitempty"` + UserId int64 `protobuf:"varint,21,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + ExternalUserId string `protobuf:"bytes,22,opt,name=external_user_id,json=externalUserId,proto3" json:"external_user_id,omitempty"` + AppCode string `protobuf:"bytes,23,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + Hits []*LuckyGiftHit `protobuf:"bytes,24,rep,name=hits,proto3" json:"hits,omitempty"` } func (x *LuckyGiftDrawResult) Reset() { @@ -944,6 +978,13 @@ func (x *LuckyGiftDrawResult) GetAppCode() string { return "" } +func (x *LuckyGiftDrawResult) GetHits() []*LuckyGiftHit { + if x != nil { + return x.Hits + } + return nil +} + type ExecuteLuckyGiftDrawRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2326,6 +2367,85 @@ func (x *ListLuckyGiftPoolBalancesResponse) GetPools() []*LuckyGiftPoolBalance { return nil } +// LuckyGiftHit 只返回批量逐份开奖中实际中奖的精简位置,不复制全部审计明细。 +// 声明追加在文件尾以保持既有 protobuf message index 稳定。 +type LuckyGiftHit struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + GiftIndex int32 `protobuf:"varint,1,opt,name=gift_index,json=giftIndex,proto3" json:"gift_index,omitempty"` + DrawId string `protobuf:"bytes,2,opt,name=draw_id,json=drawId,proto3" json:"draw_id,omitempty"` + SelectedTierId string `protobuf:"bytes,3,opt,name=selected_tier_id,json=selectedTierId,proto3" json:"selected_tier_id,omitempty"` + MultiplierPpm int64 `protobuf:"varint,4,opt,name=multiplier_ppm,json=multiplierPpm,proto3" json:"multiplier_ppm,omitempty"` + RewardCoins int64 `protobuf:"varint,5,opt,name=reward_coins,json=rewardCoins,proto3" json:"reward_coins,omitempty"` +} + +func (x *LuckyGiftHit) Reset() { + *x = LuckyGiftHit{} + mi := &file_proto_luckygift_v1_luckygift_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LuckyGiftHit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LuckyGiftHit) ProtoMessage() {} + +func (x *LuckyGiftHit) ProtoReflect() protoreflect.Message { + mi := &file_proto_luckygift_v1_luckygift_proto_msgTypes[28] + 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 LuckyGiftHit.ProtoReflect.Descriptor instead. +func (*LuckyGiftHit) Descriptor() ([]byte, []int) { + return file_proto_luckygift_v1_luckygift_proto_rawDescGZIP(), []int{28} +} + +func (x *LuckyGiftHit) GetGiftIndex() int32 { + if x != nil { + return x.GiftIndex + } + return 0 +} + +func (x *LuckyGiftHit) GetDrawId() string { + if x != nil { + return x.DrawId + } + return "" +} + +func (x *LuckyGiftHit) GetSelectedTierId() string { + if x != nil { + return x.SelectedTierId + } + return "" +} + +func (x *LuckyGiftHit) GetMultiplierPpm() int64 { + if x != nil { + return x.MultiplierPpm + } + return 0 +} + +func (x *LuckyGiftHit) GetRewardCoins() int64 { + if x != nil { + return x.RewardCoins + } + return 0 +} + var File_proto_luckygift_v1_luckygift_proto protoreflect.FileDescriptor var file_proto_luckygift_v1_luckygift_proto_rawDesc = []byte{ @@ -2343,7 +2463,7 @@ var file_proto_luckygift_v1_luckygift_proto_rawDesc = []byte{ 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x65, 0x6e, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, - 0x22, 0xce, 0x03, 0x0a, 0x0d, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x4d, 0x65, + 0x22, 0x8b, 0x05, 0x0a, 0x0d, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x33, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, @@ -2372,387 +2492,414 @@ var file_proto_luckygift_v1_luckygift_proto_rawDesc = []byte{ 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, - 0x64, 0x22, 0xd3, 0x01, 0x0a, 0x11, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, - 0x75, 0x6c, 0x65, 0x54, 0x69, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x17, 0x0a, - 0x07, 0x74, 0x69, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x74, 0x69, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, - 0x6c, 0x69, 0x65, 0x72, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, - 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x50, 0x70, 0x6d, 0x12, 0x26, 0x0a, - 0x0f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x70, 0x70, 0x6d, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x57, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x50, 0x70, 0x6d, 0x12, 0x26, 0x0a, 0x0f, 0x68, 0x69, 0x67, 0x68, 0x5f, 0x77, 0x61, - 0x74, 0x65, 0x72, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, - 0x68, 0x69, 0x67, 0x68, 0x57, 0x61, 0x74, 0x65, 0x72, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x18, 0x0a, - 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, - 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x67, 0x0a, 0x12, 0x4c, 0x75, 0x63, 0x6b, 0x79, - 0x47, 0x69, 0x66, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, - 0x61, 0x67, 0x65, 0x12, 0x3b, 0x0a, 0x05, 0x74, 0x69, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, - 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, - 0x74, 0x52, 0x75, 0x6c, 0x65, 0x54, 0x69, 0x65, 0x72, 0x52, 0x05, 0x74, 0x69, 0x65, 0x72, 0x73, - 0x22, 0xa1, 0x05, 0x0a, 0x13, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x75, - 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, - 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x72, 0x75, 0x6c, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, - 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x5f, 0x72, 0x74, 0x70, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x74, 0x70, 0x50, 0x70, 0x6d, 0x12, - 0x22, 0x0a, 0x0d, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x70, 0x6d, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x70, 0x6f, 0x6f, 0x6c, 0x52, 0x61, 0x74, 0x65, - 0x50, 0x70, 0x6d, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f, 0x77, 0x61, 0x67, 0x65, 0x72, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x57, 0x61, 0x67, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x62, 0x61, 0x6e, 0x64, 0x5f, 0x70, 0x70, 0x6d, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x42, 0x61, - 0x6e, 0x64, 0x50, 0x70, 0x6d, 0x12, 0x30, 0x0a, 0x14, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x72, - 0x69, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x12, 0x67, 0x69, 0x66, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x52, 0x65, - 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x3d, 0x0a, 0x1b, 0x6e, 0x6f, 0x76, 0x69, 0x63, - 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x65, 0x71, 0x75, 0x69, 0x76, 0x61, 0x6c, 0x65, 0x6e, 0x74, - 0x5f, 0x64, 0x72, 0x61, 0x77, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x6e, 0x6f, - 0x76, 0x69, 0x63, 0x65, 0x4d, 0x61, 0x78, 0x45, 0x71, 0x75, 0x69, 0x76, 0x61, 0x6c, 0x65, 0x6e, - 0x74, 0x44, 0x72, 0x61, 0x77, 0x73, 0x12, 0x3d, 0x0a, 0x1b, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, - 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x65, 0x71, 0x75, 0x69, 0x76, 0x61, 0x6c, 0x65, 0x6e, 0x74, 0x5f, - 0x64, 0x72, 0x61, 0x77, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x6e, 0x6f, 0x72, - 0x6d, 0x61, 0x6c, 0x4d, 0x61, 0x78, 0x45, 0x71, 0x75, 0x69, 0x76, 0x61, 0x6c, 0x65, 0x6e, 0x74, - 0x44, 0x72, 0x61, 0x77, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, - 0x76, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x4d, - 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, - 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, - 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, - 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x4d, 0x73, 0x12, 0x3e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x67, 0x65, 0x73, 0x18, 0x0f, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, - 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, - 0x69, 0x66, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x67, 0x65, 0x73, 0x22, 0xb0, 0x01, 0x0a, 0x15, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c, 0x75, - 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, - 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, - 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x17, - 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x22, 0x8d, 0x02, 0x0a, 0x16, 0x43, 0x68, 0x65, 0x63, - 0x6b, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, - 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, - 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x72, 0x75, 0x6c, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, - 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x74, 0x70, 0x5f, 0x70, 0x70, - 0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, - 0x74, 0x70, 0x50, 0x70, 0x6d, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x65, - 0x6e, 0x63, 0x65, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, - 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x17, - 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x22, 0x8e, 0x07, 0x0a, 0x13, 0x4c, 0x75, 0x63, 0x6b, - 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, - 0x17, 0x0a, 0x07, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x64, 0x72, 0x61, 0x77, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, - 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x75, 0x6c, 0x65, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x65, 0x6e, 0x63, - 0x65, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, - 0x70, 0x65, 0x72, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x28, 0x0a, 0x10, - 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, - 0x54, 0x69, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x72, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0f, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, - 0x6e, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, - 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x14, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x77, - 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x28, 0x0a, - 0x10, 0x72, 0x74, 0x70, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x64, 0x65, - 0x78, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x74, 0x70, 0x57, 0x69, 0x6e, 0x64, - 0x6f, 0x77, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x31, 0x0a, 0x15, 0x67, 0x69, 0x66, 0x74, 0x5f, - 0x72, 0x74, 0x70, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x67, 0x69, 0x66, 0x74, 0x52, 0x74, 0x70, 0x57, - 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2d, 0x0a, 0x13, 0x67, 0x6c, - 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x72, 0x74, 0x70, 0x5f, 0x70, 0x70, - 0x6d, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x42, - 0x61, 0x73, 0x65, 0x52, 0x74, 0x70, 0x50, 0x70, 0x6d, 0x12, 0x29, 0x0a, 0x11, 0x67, 0x69, 0x66, - 0x74, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x72, 0x74, 0x70, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x0d, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x42, 0x61, 0x73, 0x65, 0x52, 0x74, - 0x70, 0x50, 0x70, 0x6d, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x65, - 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74, - 0x61, 0x67, 0x65, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x27, 0x0a, 0x0f, 0x68, - 0x69, 0x67, 0x68, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x18, 0x0f, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x68, 0x69, 0x67, 0x68, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, - 0x6c, 0x69, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, - 0x5f, 0x69, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, - 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x5f, - 0x70, 0x70, 0x6d, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x75, 0x6c, 0x74, 0x69, - 0x70, 0x6c, 0x69, 0x65, 0x72, 0x50, 0x70, 0x6d, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, - 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, - 0x65, 0x72, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x69, 0x6e, 0x42, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x15, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, - 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, - 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x5f, 0x0a, 0x1b, 0x45, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x65, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x0a, 0x6c, 0x75, 0x63, 0x6b, 0x79, - 0x5f, 0x67, 0x69, 0x66, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x09, - 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x22, 0x5f, 0x0a, 0x1c, 0x45, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x65, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, - 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x72, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x66, 0x0a, 0x20, 0x42, 0x61, - 0x74, 0x63, 0x68, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, - 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, - 0x0a, 0x0b, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, - 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, - 0x66, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x0a, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, - 0x74, 0x73, 0x22, 0x66, 0x0a, 0x21, 0x42, 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x65, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, - 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x8d, 0x03, 0x0a, 0x17, 0x45, - 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, - 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x61, - 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0e, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, - 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, - 0x0a, 0x0b, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0a, 0x75, 0x6e, 0x69, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x21, 0x0a, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1c, - 0x0a, 0x0a, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x08, 0x70, 0x61, 0x69, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x23, 0x0a, 0x0d, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x0b, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4a, 0x73, 0x6f, - 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x22, 0xb2, 0x03, 0x0a, 0x18, 0x45, - 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x61, 0x77, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x61, 0x77, 0x49, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, - 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x65, 0x78, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x75, 0x6e, 0x69, 0x74, 0x41, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, - 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, - 0x50, 0x70, 0x6d, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x75, 0x6c, 0x65, - 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, - 0x72, 0x75, 0x6c, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0d, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, - 0x69, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x22, 0x5d, 0x0a, 0x1a, 0x47, 0x65, - 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, - 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xc0, 0x01, 0x0a, 0x1c, 0x55, 0x70, - 0x73, 0x65, 0x72, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, - 0x3f, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, + 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x76, 0x61, + 0x74, 0x61, 0x72, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x33, 0x0a, 0x16, 0x73, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x44, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x1d, + 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x12, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x19, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x72, 0x65, 0x74, 0x74, + 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xd3, + 0x01, 0x0a, 0x11, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x75, 0x6c, 0x65, + 0x54, 0x69, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x69, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x69, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, + 0x72, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x75, 0x6c, + 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x50, 0x70, 0x6d, 0x12, 0x26, 0x0a, 0x0f, 0x62, 0x61, + 0x73, 0x65, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x50, + 0x70, 0x6d, 0x12, 0x26, 0x0a, 0x0f, 0x68, 0x69, 0x67, 0x68, 0x5f, 0x77, 0x61, 0x74, 0x65, 0x72, + 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x68, 0x69, 0x67, + 0x68, 0x57, 0x61, 0x74, 0x65, 0x72, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x22, 0x67, 0x0a, 0x12, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, + 0x74, 0x52, 0x75, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, + 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, + 0x12, 0x3b, 0x0a, 0x05, 0x74, 0x69, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x75, - 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, - 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x60, 0x0a, 0x1d, - 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, - 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, + 0x6c, 0x65, 0x54, 0x69, 0x65, 0x72, 0x52, 0x05, 0x74, 0x69, 0x65, 0x72, 0x73, 0x22, 0xa1, 0x05, + 0x0a, 0x13, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, + 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x75, 0x6c, + 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x72, 0x75, 0x6c, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, + 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x72, 0x74, 0x70, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x74, 0x70, 0x50, 0x70, 0x6d, 0x12, 0x22, 0x0a, 0x0d, + 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x70, 0x6f, 0x6f, 0x6c, 0x52, 0x61, 0x74, 0x65, 0x50, 0x70, 0x6d, + 0x12, 0x36, 0x0a, 0x17, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x77, + 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f, 0x77, 0x61, 0x67, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x15, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x57, 0x69, 0x6e, + 0x64, 0x6f, 0x77, 0x57, 0x61, 0x67, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x5f, 0x62, 0x61, 0x6e, 0x64, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x42, 0x61, 0x6e, 0x64, 0x50, + 0x70, 0x6d, 0x12, 0x30, 0x0a, 0x14, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, + 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x12, 0x67, 0x69, 0x66, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, + 0x65, 0x6e, 0x63, 0x65, 0x12, 0x3d, 0x0a, 0x1b, 0x6e, 0x6f, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6d, + 0x61, 0x78, 0x5f, 0x65, 0x71, 0x75, 0x69, 0x76, 0x61, 0x6c, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x72, + 0x61, 0x77, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x6e, 0x6f, 0x76, 0x69, 0x63, + 0x65, 0x4d, 0x61, 0x78, 0x45, 0x71, 0x75, 0x69, 0x76, 0x61, 0x6c, 0x65, 0x6e, 0x74, 0x44, 0x72, + 0x61, 0x77, 0x73, 0x12, 0x3d, 0x0a, 0x1b, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x5f, 0x6d, 0x61, + 0x78, 0x5f, 0x65, 0x71, 0x75, 0x69, 0x76, 0x61, 0x6c, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x72, 0x61, + 0x77, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, + 0x4d, 0x61, 0x78, 0x45, 0x71, 0x75, 0x69, 0x76, 0x61, 0x6c, 0x65, 0x6e, 0x74, 0x44, 0x72, 0x61, + 0x77, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, + 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x65, + 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x4d, 0x73, 0x12, 0x2d, + 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, + 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, + 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, + 0x73, 0x12, 0x3e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x67, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, + 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, + 0x52, 0x75, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x67, 0x65, + 0x73, 0x22, 0xb0, 0x01, 0x0a, 0x15, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c, 0x75, 0x63, 0x6b, 0x79, + 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, + 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, + 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, + 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, + 0x6f, 0x6c, 0x49, 0x64, 0x22, 0x8d, 0x02, 0x0a, 0x16, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c, 0x75, + 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, + 0x66, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, + 0x67, 0x69, 0x66, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x75, 0x6c, + 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x72, 0x75, 0x6c, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x74, 0x70, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x74, 0x70, 0x50, + 0x70, 0x6d, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x65, 0x6e, 0x63, 0x65, + 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, 0x70, + 0x65, 0x72, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x17, 0x0a, 0x07, 0x70, + 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, + 0x6f, 0x6c, 0x49, 0x64, 0x22, 0xc4, 0x07, 0x0a, 0x13, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, + 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x17, 0x0a, 0x07, + 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, + 0x72, 0x61, 0x77, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, + 0x0c, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x75, 0x6c, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x70, + 0x6f, 0x6f, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, 0x70, 0x65, 0x72, + 0x69, 0x65, 0x6e, 0x63, 0x65, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x54, 0x69, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, + 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, + 0x34, 0x0a, 0x16, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x72, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x14, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x74, + 0x70, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x74, 0x70, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x12, 0x31, 0x0a, 0x15, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x72, 0x74, 0x70, + 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x12, 0x67, 0x69, 0x66, 0x74, 0x52, 0x74, 0x70, 0x57, 0x69, 0x6e, 0x64, + 0x6f, 0x77, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2d, 0x0a, 0x13, 0x67, 0x6c, 0x6f, 0x62, 0x61, + 0x6c, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x72, 0x74, 0x70, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x42, 0x61, 0x73, 0x65, + 0x52, 0x74, 0x70, 0x50, 0x70, 0x6d, 0x12, 0x29, 0x0a, 0x11, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x62, + 0x61, 0x73, 0x65, 0x5f, 0x72, 0x74, 0x70, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x42, 0x61, 0x73, 0x65, 0x52, 0x74, 0x70, 0x50, 0x70, + 0x6d, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x64, 0x62, + 0x61, 0x63, 0x6b, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x67, 0x65, + 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x27, 0x0a, 0x0f, 0x68, 0x69, 0x67, 0x68, + 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x18, 0x0f, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0e, 0x68, 0x69, 0x67, 0x68, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, + 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, + 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x25, + 0x0a, 0x0e, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x5f, 0x70, 0x70, 0x6d, + 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, + 0x65, 0x72, 0x50, 0x70, 0x6d, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, + 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x13, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x6f, 0x69, + 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, + 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x69, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x15, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x28, 0x0a, 0x10, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, + 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, + 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x34, 0x0a, 0x04, 0x68, 0x69, 0x74, 0x73, 0x18, 0x18, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, + 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, + 0x66, 0x74, 0x48, 0x69, 0x74, 0x52, 0x04, 0x68, 0x69, 0x74, 0x73, 0x22, 0x5f, 0x0a, 0x1b, 0x45, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, + 0x72, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x0a, 0x6c, 0x75, + 0x63, 0x6b, 0x79, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x4d, 0x65, 0x74, + 0x61, 0x52, 0x09, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x22, 0x5f, 0x0a, 0x1c, + 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, + 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x06, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x66, 0x0a, + 0x20, 0x42, 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x4c, 0x75, 0x63, + 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x42, 0x0a, 0x0b, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, + 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, + 0x79, 0x47, 0x69, 0x66, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x0a, 0x6c, 0x75, 0x63, 0x6b, 0x79, + 0x47, 0x69, 0x66, 0x74, 0x73, 0x22, 0x66, 0x0a, 0x21, 0x42, 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x65, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, + 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x07, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x8d, 0x03, + 0x0a, 0x17, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, + 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x19, + 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x65, 0x78, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x75, 0x6e, 0x69, 0x74, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, + 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, + 0x79, 0x12, 0x1c, 0x0a, 0x0a, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x61, 0x69, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, + 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x22, 0xb2, 0x03, + 0x0a, 0x18, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, + 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, + 0x61, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x61, + 0x77, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, + 0x10, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, + 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x75, 0x6e, 0x69, + 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x25, 0x0a, 0x0e, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x5f, 0x70, 0x70, + 0x6d, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, + 0x69, 0x65, 0x72, 0x50, 0x70, 0x6d, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, + 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x72, + 0x75, 0x6c, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x72, 0x75, 0x6c, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, + 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, + 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x4d, 0x73, 0x22, 0x69, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, + 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x33, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x75, 0x6c, 0x65, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x52, - 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x22, 0x5d, 0x0a, + 0x1a, 0x47, 0x65, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xc0, 0x01, 0x0a, + 0x1c, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x22, 0x61, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, - 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x41, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, - 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, - 0x66, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x73, 0x22, 0xcc, 0x02, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, - 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, - 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, - 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, - 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, - 0x6d, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, - 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, - 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x17, 0x0a, 0x07, - 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, - 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0e, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x71, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, - 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x05, 0x64, 0x72, 0x61, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, - 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, - 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x64, 0x72, 0x61, 0x77, - 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0xaf, 0x03, 0x0a, 0x14, 0x4c, 0x75, 0x63, 0x6b, - 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, - 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x44, 0x72, 0x61, 0x77, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x75, 0x6e, - 0x69, 0x71, 0x75, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0b, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, - 0x0c, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x73, - 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x5f, - 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x2c, 0x0a, 0x12, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, - 0x6e, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x52, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x62, 0x61, - 0x73, 0x65, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x75, 0x61, 0x6c, - 0x5f, 0x72, 0x74, 0x70, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x61, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x52, 0x74, 0x70, 0x50, 0x70, 0x6d, 0x12, 0x23, 0x0a, 0x0d, - 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x73, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x44, 0x72, 0x61, 0x77, - 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x72, 0x61, - 0x77, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, - 0x64, 0x44, 0x72, 0x61, 0x77, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, - 0x5f, 0x64, 0x72, 0x61, 0x77, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x61, - 0x69, 0x6c, 0x65, 0x64, 0x44, 0x72, 0x61, 0x77, 0x73, 0x22, 0xb6, 0x02, 0x0a, 0x14, 0x4c, 0x75, - 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, - 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, - 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x66, 0x6c, 0x6f, 0x6f, - 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x46, 0x6c, 0x6f, 0x6f, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, - 0x6c, 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x10, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x69, 0x6e, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x49, 0x6e, 0x12, 0x1b, 0x0a, - 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x08, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4f, 0x75, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x6d, 0x61, - 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0c, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x22, - 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x4d, 0x73, 0x22, 0xa0, 0x02, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, - 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, - 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, - 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, - 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, - 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x17, 0x0a, - 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0e, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6f, 0x6e, 0x6c, - 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x65, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x4c, 0x75, 0x63, 0x6b, - 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, - 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, - 0x61, 0x72, 0x79, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x70, 0x0a, 0x20, - 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, - 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x74, 0x61, 0x12, 0x3f, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, + 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, + 0x74, 0x52, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, + 0x60, 0x0a, 0x1d, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, + 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3f, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, + 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, + 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x22, 0x52, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, + 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x22, 0x63, - 0x0a, 0x21, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x50, - 0x6f, 0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x05, 0x70, 0x6f, 0x6f, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, - 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, - 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x05, 0x70, 0x6f, - 0x6f, 0x6c, 0x73, 0x32, 0xf7, 0x03, 0x0a, 0x10, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x61, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, + 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, + 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, + 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x07, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x22, 0xcc, 0x02, 0x0a, 0x19, 0x4c, 0x69, 0x73, + 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, + 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x67, + 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, + 0x66, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, + 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, + 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, + 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, + 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x65, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6f, + 0x6e, 0x6c, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x65, 0x78, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x71, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x4c, + 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x05, 0x64, 0x72, 0x61, 0x77, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, + 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, + 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x64, + 0x72, 0x61, 0x77, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0xaf, 0x03, 0x0a, 0x14, 0x4c, + 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x44, 0x72, 0x61, 0x77, 0x73, 0x12, 0x21, 0x0a, + 0x0c, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, + 0x12, 0x21, 0x0a, 0x0c, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x73, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x52, 0x6f, + 0x6f, 0x6d, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x73, 0x70, 0x65, + 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, + 0x2c, 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, + 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x2a, 0x0a, + 0x11, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, + 0x6e, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x63, 0x74, + 0x75, 0x61, 0x6c, 0x5f, 0x72, 0x74, 0x70, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x61, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x52, 0x74, 0x70, 0x50, 0x70, 0x6d, 0x12, + 0x23, 0x0a, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x73, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x44, + 0x72, 0x61, 0x77, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x5f, + 0x64, 0x72, 0x61, 0x77, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x67, 0x72, 0x61, + 0x6e, 0x74, 0x65, 0x64, 0x44, 0x72, 0x61, 0x77, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x61, 0x69, + 0x6c, 0x65, 0x64, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x44, 0x72, 0x61, 0x77, 0x73, 0x22, 0xb6, 0x02, 0x0a, + 0x14, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, + 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x66, + 0x6c, 0x6f, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x46, 0x6c, 0x6f, 0x6f, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x61, 0x76, 0x61, 0x69, + 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x10, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x69, + 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x49, 0x6e, + 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4f, 0x75, 0x74, 0x12, 0x22, 0x0a, + 0x0c, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, + 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xa0, 0x02, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x4c, 0x75, 0x63, + 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, + 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, + 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x65, 0x78, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, + 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x65, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x65, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x4c, + 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x07, 0x73, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, + 0x70, 0x0a, 0x20, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, + 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, + 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, + 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, + 0x64, 0x22, 0x63, 0x0a, 0x21, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, + 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x05, 0x70, 0x6f, 0x6f, 0x6c, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, + 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, + 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, + 0x05, 0x70, 0x6f, 0x6f, 0x6c, 0x73, 0x22, 0xba, 0x01, 0x0a, 0x0c, 0x4c, 0x75, 0x63, 0x6b, 0x79, + 0x47, 0x69, 0x66, 0x74, 0x48, 0x69, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, + 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, + 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x61, 0x77, 0x49, 0x64, 0x12, + 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x65, 0x64, 0x54, 0x69, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x75, 0x6c, + 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0d, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x50, 0x70, 0x6d, + 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, + 0x69, 0x6e, 0x73, 0x32, 0xf7, 0x03, 0x0a, 0x10, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x67, 0x0a, 0x0e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, @@ -2851,7 +2998,7 @@ func file_proto_luckygift_v1_luckygift_proto_rawDescGZIP() []byte { return file_proto_luckygift_v1_luckygift_proto_rawDescData } -var file_proto_luckygift_v1_luckygift_proto_msgTypes = make([]protoimpl.MessageInfo, 28) +var file_proto_luckygift_v1_luckygift_proto_msgTypes = make([]protoimpl.MessageInfo, 29) var file_proto_luckygift_v1_luckygift_proto_goTypes = []any{ (*RequestMeta)(nil), // 0: hyapp.luckygift.v1.RequestMeta (*LuckyGiftMeta)(nil), // 1: hyapp.luckygift.v1.LuckyGiftMeta @@ -2881,55 +3028,57 @@ var file_proto_luckygift_v1_luckygift_proto_goTypes = []any{ (*GetLuckyGiftDrawSummaryResponse)(nil), // 25: hyapp.luckygift.v1.GetLuckyGiftDrawSummaryResponse (*ListLuckyGiftPoolBalancesRequest)(nil), // 26: hyapp.luckygift.v1.ListLuckyGiftPoolBalancesRequest (*ListLuckyGiftPoolBalancesResponse)(nil), // 27: hyapp.luckygift.v1.ListLuckyGiftPoolBalancesResponse + (*LuckyGiftHit)(nil), // 28: hyapp.luckygift.v1.LuckyGiftHit } var file_proto_luckygift_v1_luckygift_proto_depIdxs = []int32{ 0, // 0: hyapp.luckygift.v1.LuckyGiftMeta.meta:type_name -> hyapp.luckygift.v1.RequestMeta 2, // 1: hyapp.luckygift.v1.LuckyGiftRuleStage.tiers:type_name -> hyapp.luckygift.v1.LuckyGiftRuleTier 3, // 2: hyapp.luckygift.v1.LuckyGiftRuleConfig.stages:type_name -> hyapp.luckygift.v1.LuckyGiftRuleStage 0, // 3: hyapp.luckygift.v1.CheckLuckyGiftRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta - 1, // 4: hyapp.luckygift.v1.ExecuteLuckyGiftDrawRequest.lucky_gift:type_name -> hyapp.luckygift.v1.LuckyGiftMeta - 7, // 5: hyapp.luckygift.v1.ExecuteLuckyGiftDrawResponse.result:type_name -> hyapp.luckygift.v1.LuckyGiftDrawResult - 1, // 6: hyapp.luckygift.v1.BatchExecuteLuckyGiftDrawRequest.lucky_gifts:type_name -> hyapp.luckygift.v1.LuckyGiftMeta - 7, // 7: hyapp.luckygift.v1.BatchExecuteLuckyGiftDrawResponse.results:type_name -> hyapp.luckygift.v1.LuckyGiftDrawResult - 0, // 8: hyapp.luckygift.v1.ExternalGiftDrawRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta - 0, // 9: hyapp.luckygift.v1.GetLuckyGiftConfigRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta - 4, // 10: hyapp.luckygift.v1.GetLuckyGiftConfigResponse.config:type_name -> hyapp.luckygift.v1.LuckyGiftRuleConfig - 0, // 11: hyapp.luckygift.v1.UpsertLuckyGiftConfigRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta - 4, // 12: hyapp.luckygift.v1.UpsertLuckyGiftConfigRequest.config:type_name -> hyapp.luckygift.v1.LuckyGiftRuleConfig - 4, // 13: hyapp.luckygift.v1.UpsertLuckyGiftConfigResponse.config:type_name -> hyapp.luckygift.v1.LuckyGiftRuleConfig - 0, // 14: hyapp.luckygift.v1.ListLuckyGiftConfigsRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta - 4, // 15: hyapp.luckygift.v1.ListLuckyGiftConfigsResponse.configs:type_name -> hyapp.luckygift.v1.LuckyGiftRuleConfig - 0, // 16: hyapp.luckygift.v1.ListLuckyGiftDrawsRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta - 7, // 17: hyapp.luckygift.v1.ListLuckyGiftDrawsResponse.draws:type_name -> hyapp.luckygift.v1.LuckyGiftDrawResult - 0, // 18: hyapp.luckygift.v1.GetLuckyGiftDrawSummaryRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta - 22, // 19: hyapp.luckygift.v1.GetLuckyGiftDrawSummaryResponse.summary:type_name -> hyapp.luckygift.v1.LuckyGiftDrawSummary - 0, // 20: hyapp.luckygift.v1.ListLuckyGiftPoolBalancesRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta - 23, // 21: hyapp.luckygift.v1.ListLuckyGiftPoolBalancesResponse.pools:type_name -> hyapp.luckygift.v1.LuckyGiftPoolBalance - 5, // 22: hyapp.luckygift.v1.LuckyGiftService.CheckLuckyGift:input_type -> hyapp.luckygift.v1.CheckLuckyGiftRequest - 8, // 23: hyapp.luckygift.v1.LuckyGiftService.ExecuteLuckyGiftDraw:input_type -> hyapp.luckygift.v1.ExecuteLuckyGiftDrawRequest - 10, // 24: hyapp.luckygift.v1.LuckyGiftService.BatchExecuteLuckyGiftDraw:input_type -> hyapp.luckygift.v1.BatchExecuteLuckyGiftDrawRequest - 12, // 25: hyapp.luckygift.v1.LuckyGiftService.ExecuteExternalGiftDraw:input_type -> hyapp.luckygift.v1.ExternalGiftDrawRequest - 14, // 26: hyapp.luckygift.v1.AdminLuckyGiftService.GetLuckyGiftConfig:input_type -> hyapp.luckygift.v1.GetLuckyGiftConfigRequest - 16, // 27: hyapp.luckygift.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:input_type -> hyapp.luckygift.v1.UpsertLuckyGiftConfigRequest - 18, // 28: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:input_type -> hyapp.luckygift.v1.ListLuckyGiftConfigsRequest - 20, // 29: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftDraws:input_type -> hyapp.luckygift.v1.ListLuckyGiftDrawsRequest - 24, // 30: hyapp.luckygift.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:input_type -> hyapp.luckygift.v1.GetLuckyGiftDrawSummaryRequest - 26, // 31: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftPoolBalances:input_type -> hyapp.luckygift.v1.ListLuckyGiftPoolBalancesRequest - 6, // 32: hyapp.luckygift.v1.LuckyGiftService.CheckLuckyGift:output_type -> hyapp.luckygift.v1.CheckLuckyGiftResponse - 9, // 33: hyapp.luckygift.v1.LuckyGiftService.ExecuteLuckyGiftDraw:output_type -> hyapp.luckygift.v1.ExecuteLuckyGiftDrawResponse - 11, // 34: hyapp.luckygift.v1.LuckyGiftService.BatchExecuteLuckyGiftDraw:output_type -> hyapp.luckygift.v1.BatchExecuteLuckyGiftDrawResponse - 13, // 35: hyapp.luckygift.v1.LuckyGiftService.ExecuteExternalGiftDraw:output_type -> hyapp.luckygift.v1.ExternalGiftDrawResponse - 15, // 36: hyapp.luckygift.v1.AdminLuckyGiftService.GetLuckyGiftConfig:output_type -> hyapp.luckygift.v1.GetLuckyGiftConfigResponse - 17, // 37: hyapp.luckygift.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:output_type -> hyapp.luckygift.v1.UpsertLuckyGiftConfigResponse - 19, // 38: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:output_type -> hyapp.luckygift.v1.ListLuckyGiftConfigsResponse - 21, // 39: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftDraws:output_type -> hyapp.luckygift.v1.ListLuckyGiftDrawsResponse - 25, // 40: hyapp.luckygift.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:output_type -> hyapp.luckygift.v1.GetLuckyGiftDrawSummaryResponse - 27, // 41: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftPoolBalances:output_type -> hyapp.luckygift.v1.ListLuckyGiftPoolBalancesResponse - 32, // [32:42] is the sub-list for method output_type - 22, // [22:32] 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 + 28, // 4: hyapp.luckygift.v1.LuckyGiftDrawResult.hits:type_name -> hyapp.luckygift.v1.LuckyGiftHit + 1, // 5: hyapp.luckygift.v1.ExecuteLuckyGiftDrawRequest.lucky_gift:type_name -> hyapp.luckygift.v1.LuckyGiftMeta + 7, // 6: hyapp.luckygift.v1.ExecuteLuckyGiftDrawResponse.result:type_name -> hyapp.luckygift.v1.LuckyGiftDrawResult + 1, // 7: hyapp.luckygift.v1.BatchExecuteLuckyGiftDrawRequest.lucky_gifts:type_name -> hyapp.luckygift.v1.LuckyGiftMeta + 7, // 8: hyapp.luckygift.v1.BatchExecuteLuckyGiftDrawResponse.results:type_name -> hyapp.luckygift.v1.LuckyGiftDrawResult + 0, // 9: hyapp.luckygift.v1.ExternalGiftDrawRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta + 0, // 10: hyapp.luckygift.v1.GetLuckyGiftConfigRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta + 4, // 11: hyapp.luckygift.v1.GetLuckyGiftConfigResponse.config:type_name -> hyapp.luckygift.v1.LuckyGiftRuleConfig + 0, // 12: hyapp.luckygift.v1.UpsertLuckyGiftConfigRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta + 4, // 13: hyapp.luckygift.v1.UpsertLuckyGiftConfigRequest.config:type_name -> hyapp.luckygift.v1.LuckyGiftRuleConfig + 4, // 14: hyapp.luckygift.v1.UpsertLuckyGiftConfigResponse.config:type_name -> hyapp.luckygift.v1.LuckyGiftRuleConfig + 0, // 15: hyapp.luckygift.v1.ListLuckyGiftConfigsRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta + 4, // 16: hyapp.luckygift.v1.ListLuckyGiftConfigsResponse.configs:type_name -> hyapp.luckygift.v1.LuckyGiftRuleConfig + 0, // 17: hyapp.luckygift.v1.ListLuckyGiftDrawsRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta + 7, // 18: hyapp.luckygift.v1.ListLuckyGiftDrawsResponse.draws:type_name -> hyapp.luckygift.v1.LuckyGiftDrawResult + 0, // 19: hyapp.luckygift.v1.GetLuckyGiftDrawSummaryRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta + 22, // 20: hyapp.luckygift.v1.GetLuckyGiftDrawSummaryResponse.summary:type_name -> hyapp.luckygift.v1.LuckyGiftDrawSummary + 0, // 21: hyapp.luckygift.v1.ListLuckyGiftPoolBalancesRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta + 23, // 22: hyapp.luckygift.v1.ListLuckyGiftPoolBalancesResponse.pools:type_name -> hyapp.luckygift.v1.LuckyGiftPoolBalance + 5, // 23: hyapp.luckygift.v1.LuckyGiftService.CheckLuckyGift:input_type -> hyapp.luckygift.v1.CheckLuckyGiftRequest + 8, // 24: hyapp.luckygift.v1.LuckyGiftService.ExecuteLuckyGiftDraw:input_type -> hyapp.luckygift.v1.ExecuteLuckyGiftDrawRequest + 10, // 25: hyapp.luckygift.v1.LuckyGiftService.BatchExecuteLuckyGiftDraw:input_type -> hyapp.luckygift.v1.BatchExecuteLuckyGiftDrawRequest + 12, // 26: hyapp.luckygift.v1.LuckyGiftService.ExecuteExternalGiftDraw:input_type -> hyapp.luckygift.v1.ExternalGiftDrawRequest + 14, // 27: hyapp.luckygift.v1.AdminLuckyGiftService.GetLuckyGiftConfig:input_type -> hyapp.luckygift.v1.GetLuckyGiftConfigRequest + 16, // 28: hyapp.luckygift.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:input_type -> hyapp.luckygift.v1.UpsertLuckyGiftConfigRequest + 18, // 29: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:input_type -> hyapp.luckygift.v1.ListLuckyGiftConfigsRequest + 20, // 30: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftDraws:input_type -> hyapp.luckygift.v1.ListLuckyGiftDrawsRequest + 24, // 31: hyapp.luckygift.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:input_type -> hyapp.luckygift.v1.GetLuckyGiftDrawSummaryRequest + 26, // 32: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftPoolBalances:input_type -> hyapp.luckygift.v1.ListLuckyGiftPoolBalancesRequest + 6, // 33: hyapp.luckygift.v1.LuckyGiftService.CheckLuckyGift:output_type -> hyapp.luckygift.v1.CheckLuckyGiftResponse + 9, // 34: hyapp.luckygift.v1.LuckyGiftService.ExecuteLuckyGiftDraw:output_type -> hyapp.luckygift.v1.ExecuteLuckyGiftDrawResponse + 11, // 35: hyapp.luckygift.v1.LuckyGiftService.BatchExecuteLuckyGiftDraw:output_type -> hyapp.luckygift.v1.BatchExecuteLuckyGiftDrawResponse + 13, // 36: hyapp.luckygift.v1.LuckyGiftService.ExecuteExternalGiftDraw:output_type -> hyapp.luckygift.v1.ExternalGiftDrawResponse + 15, // 37: hyapp.luckygift.v1.AdminLuckyGiftService.GetLuckyGiftConfig:output_type -> hyapp.luckygift.v1.GetLuckyGiftConfigResponse + 17, // 38: hyapp.luckygift.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:output_type -> hyapp.luckygift.v1.UpsertLuckyGiftConfigResponse + 19, // 39: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:output_type -> hyapp.luckygift.v1.ListLuckyGiftConfigsResponse + 21, // 40: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftDraws:output_type -> hyapp.luckygift.v1.ListLuckyGiftDrawsResponse + 25, // 41: hyapp.luckygift.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:output_type -> hyapp.luckygift.v1.GetLuckyGiftDrawSummaryResponse + 27, // 42: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftPoolBalances:output_type -> hyapp.luckygift.v1.ListLuckyGiftPoolBalancesResponse + 33, // [33:43] is the sub-list for method output_type + 23, // [23:33] 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_proto_luckygift_v1_luckygift_proto_init() } @@ -2943,7 +3092,7 @@ func file_proto_luckygift_v1_luckygift_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_luckygift_v1_luckygift_proto_rawDesc, NumEnums: 0, - NumMessages: 28, + NumMessages: 29, NumExtensions: 0, NumServices: 2, }, diff --git a/api/proto/luckygift/v1/luckygift.proto b/api/proto/luckygift/v1/luckygift.proto index 03e56bf4..98ca11b5 100644 --- a/api/proto/luckygift/v1/luckygift.proto +++ b/api/proto/luckygift/v1/luckygift.proto @@ -31,6 +31,11 @@ message LuckyGiftMeta { int32 gift_count = 12; int64 visible_region_id = 13; int64 country_id = 14; + // sender_* 是 room-service 在送礼入口已经确认的展示快照,只进入中奖事实和 IM,不能参与抽奖或账务判断。 + string sender_name = 15; + string sender_avatar = 16; + string sender_display_user_id = 17; + string sender_pretty_display_user_id = 18; } message LuckyGiftRuleTier { @@ -108,6 +113,7 @@ message LuckyGiftDrawResult { int64 user_id = 21; string external_user_id = 22; string app_code = 23; + repeated LuckyGiftHit hits = 24; } message ExecuteLuckyGiftDrawRequest { @@ -267,3 +273,13 @@ service AdminLuckyGiftService { rpc GetLuckyGiftDrawSummary(GetLuckyGiftDrawSummaryRequest) returns (GetLuckyGiftDrawSummaryResponse); rpc ListLuckyGiftPoolBalances(ListLuckyGiftPoolBalancesRequest) returns (ListLuckyGiftPoolBalancesResponse); } + +// LuckyGiftHit 只返回批量逐份开奖中实际中奖的精简位置,不复制全部审计明细。 +// 声明追加在文件尾以保持既有 protobuf message index 稳定。 +message LuckyGiftHit { + int32 gift_index = 1; + string draw_id = 2; + string selected_tier_id = 3; + int64 multiplier_ppm = 4; + int64 reward_coins = 5; +} diff --git a/api/proto/room/v1/room.pb.go b/api/proto/room/v1/room.pb.go index ad8868c7..fb31c987 100644 --- a/api/proto/room/v1/room.pb.go +++ b/api/proto/room/v1/room.pb.go @@ -676,6 +676,8 @@ type LuckyGiftDrawResult struct { CoinBalanceAfter int64 `protobuf:"varint,17,opt,name=coin_balance_after,json=coinBalanceAfter,proto3" json:"coin_balance_after,omitempty"` // target_user_id 是本次幸运礼物抽奖对应的收礼用户;多目标送礼用它关联结果。 TargetUserId int64 `protobuf:"varint,18,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + // hits 只包含 gift_count 微批中实际中奖的位置;旧客户端忽略该追加字段。 + Hits []*SendGiftLuckyHit `protobuf:"bytes,19,rep,name=hits,proto3" json:"hits,omitempty"` } func (x *LuckyGiftDrawResult) Reset() { @@ -834,6 +836,13 @@ func (x *LuckyGiftDrawResult) GetTargetUserId() int64 { return 0 } +func (x *LuckyGiftDrawResult) GetHits() []*SendGiftLuckyHit { + if x != nil { + return x.Hits + } + return nil +} + // RoomRocketRewardItem 是后台配置给客户端展示的火箭奖励候选项。 type RoomRocketRewardItem struct { state protoimpl.MessageState @@ -5770,6 +5779,9 @@ type JoinRoomRequest struct { ActorDisplayProfile *RoomUserDisplayProfile `protobuf:"bytes,8,opt,name=actor_display_profile,json=actorDisplayProfile,proto3" json:"actor_display_profile,omitempty"` // response_mode=lite 只裁剪响应快照,不改变 Room Cell 命令提交、outbox 或 IM 事件语义。 ResponseMode string `protobuf:"bytes,9,opt,name=response_mode,json=responseMode,proto3" json:"response_mode,omitempty"` + // presence_recovery 只用于客户端仍在房间但业务 presence 已超时清理的恢复。 + // 恢复成功只补回 Room Cell 在线状态,不发布 RoomUserJoined IM/outbox,避免重复入场表现和统计。 + PresenceRecovery bool `protobuf:"varint,10,opt,name=presence_recovery,json=presenceRecovery,proto3" json:"presence_recovery,omitempty"` } func (x *JoinRoomRequest) Reset() { @@ -5865,20 +5877,99 @@ func (x *JoinRoomRequest) GetResponseMode() string { return "" } +func (x *JoinRoomRequest) GetPresenceRecovery() bool { + if x != nil { + return x.PresenceRecovery + } + return false +} + +// RoomEffectiveVipSnapshot 是 room-service 在进房时从 wallet-service 固化的轻量 VIP 展示事实。 +// 权益执行仍以后端 effective_benefits 为准,客户端不能用该快照自行声明房间权限。 +type RoomEffectiveVipSnapshot struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProgramType string `protobuf:"bytes,1,opt,name=program_type,json=programType,proto3" json:"program_type,omitempty"` + Level int32 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + RoomEntryNoticeEnabled bool `protobuf:"varint,4,opt,name=room_entry_notice_enabled,json=roomEntryNoticeEnabled,proto3" json:"room_entry_notice_enabled,omitempty"` +} + +func (x *RoomEffectiveVipSnapshot) Reset() { + *x = RoomEffectiveVipSnapshot{} + mi := &file_proto_room_v1_room_proto_msgTypes[69] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoomEffectiveVipSnapshot) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoomEffectiveVipSnapshot) ProtoMessage() {} + +func (x *RoomEffectiveVipSnapshot) ProtoReflect() protoreflect.Message { + mi := &file_proto_room_v1_room_proto_msgTypes[69] + 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 RoomEffectiveVipSnapshot.ProtoReflect.Descriptor instead. +func (*RoomEffectiveVipSnapshot) Descriptor() ([]byte, []int) { + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{69} +} + +func (x *RoomEffectiveVipSnapshot) GetProgramType() string { + if x != nil { + return x.ProgramType + } + return "" +} + +func (x *RoomEffectiveVipSnapshot) GetLevel() int32 { + if x != nil { + return x.Level + } + return 0 +} + +func (x *RoomEffectiveVipSnapshot) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *RoomEffectiveVipSnapshot) GetRoomEntryNoticeEnabled() bool { + if x != nil { + return x.RoomEntryNoticeEnabled + } + return false +} + // JoinRoomResponse 返回加入后的房间快照。 type JoinRoomResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` - User *RoomUser `protobuf:"bytes,2,opt,name=user,proto3" json:"user,omitempty"` - Room *RoomSnapshot `protobuf:"bytes,3,opt,name=room,proto3" json:"room,omitempty"` + Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + User *RoomUser `protobuf:"bytes,2,opt,name=user,proto3" json:"user,omitempty"` + Room *RoomSnapshot `protobuf:"bytes,3,opt,name=room,proto3" json:"room,omitempty"` + EffectiveVip *RoomEffectiveVipSnapshot `protobuf:"bytes,4,opt,name=effective_vip,json=effectiveVip,proto3" json:"effective_vip,omitempty"` } func (x *JoinRoomResponse) Reset() { *x = JoinRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[69] + mi := &file_proto_room_v1_room_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5890,7 +5981,7 @@ func (x *JoinRoomResponse) String() string { func (*JoinRoomResponse) ProtoMessage() {} func (x *JoinRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[69] + mi := &file_proto_room_v1_room_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5903,7 +5994,7 @@ func (x *JoinRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use JoinRoomResponse.ProtoReflect.Descriptor instead. func (*JoinRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{69} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{70} } func (x *JoinRoomResponse) GetResult() *CommandResult { @@ -5927,6 +6018,13 @@ func (x *JoinRoomResponse) GetRoom() *RoomSnapshot { return nil } +func (x *JoinRoomResponse) GetEffectiveVip() *RoomEffectiveVipSnapshot { + if x != nil { + return x.EffectiveVip + } + return nil +} + // RoomHeartbeatRequest 显式刷新已经存在的房间业务 presence。 // 它不能替代 JoinRoom;用户不在房间时必须拒绝,避免心跳把已离开的用户重新加入房间。 type RoomHeartbeatRequest struct { @@ -5939,7 +6037,7 @@ type RoomHeartbeatRequest struct { func (x *RoomHeartbeatRequest) Reset() { *x = RoomHeartbeatRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[70] + mi := &file_proto_room_v1_room_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5951,7 +6049,7 @@ func (x *RoomHeartbeatRequest) String() string { func (*RoomHeartbeatRequest) ProtoMessage() {} func (x *RoomHeartbeatRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[70] + mi := &file_proto_room_v1_room_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5964,7 +6062,7 @@ func (x *RoomHeartbeatRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomHeartbeatRequest.ProtoReflect.Descriptor instead. func (*RoomHeartbeatRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{70} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{71} } func (x *RoomHeartbeatRequest) GetMeta() *RequestMeta { @@ -5987,7 +6085,7 @@ type RoomHeartbeatResponse struct { func (x *RoomHeartbeatResponse) Reset() { *x = RoomHeartbeatResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[71] + mi := &file_proto_room_v1_room_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5999,7 +6097,7 @@ func (x *RoomHeartbeatResponse) String() string { func (*RoomHeartbeatResponse) ProtoMessage() {} func (x *RoomHeartbeatResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[71] + mi := &file_proto_room_v1_room_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6012,7 +6110,7 @@ func (x *RoomHeartbeatResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomHeartbeatResponse.ProtoReflect.Descriptor instead. func (*RoomHeartbeatResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{71} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{72} } func (x *RoomHeartbeatResponse) GetResult() *CommandResult { @@ -6047,7 +6145,7 @@ type LeaveRoomRequest struct { func (x *LeaveRoomRequest) Reset() { *x = LeaveRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[72] + mi := &file_proto_room_v1_room_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6059,7 +6157,7 @@ func (x *LeaveRoomRequest) String() string { func (*LeaveRoomRequest) ProtoMessage() {} func (x *LeaveRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[72] + mi := &file_proto_room_v1_room_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6072,7 +6170,7 @@ func (x *LeaveRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LeaveRoomRequest.ProtoReflect.Descriptor instead. func (*LeaveRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{72} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{73} } func (x *LeaveRoomRequest) GetMeta() *RequestMeta { @@ -6094,7 +6192,7 @@ type LeaveRoomResponse struct { func (x *LeaveRoomResponse) Reset() { *x = LeaveRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[73] + mi := &file_proto_room_v1_room_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6106,7 +6204,7 @@ func (x *LeaveRoomResponse) String() string { func (*LeaveRoomResponse) ProtoMessage() {} func (x *LeaveRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[73] + mi := &file_proto_room_v1_room_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6119,7 +6217,7 @@ func (x *LeaveRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LeaveRoomResponse.ProtoReflect.Descriptor instead. func (*LeaveRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{73} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{74} } func (x *LeaveRoomResponse) GetResult() *CommandResult { @@ -6148,7 +6246,7 @@ type CloseRoomRequest struct { func (x *CloseRoomRequest) Reset() { *x = CloseRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[74] + mi := &file_proto_room_v1_room_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6160,7 +6258,7 @@ func (x *CloseRoomRequest) String() string { func (*CloseRoomRequest) ProtoMessage() {} func (x *CloseRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[74] + mi := &file_proto_room_v1_room_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6173,7 +6271,7 @@ func (x *CloseRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CloseRoomRequest.ProtoReflect.Descriptor instead. func (*CloseRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{74} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{75} } func (x *CloseRoomRequest) GetMeta() *RequestMeta { @@ -6202,7 +6300,7 @@ type CloseRoomResponse struct { func (x *CloseRoomResponse) Reset() { *x = CloseRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[75] + mi := &file_proto_room_v1_room_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6214,7 +6312,7 @@ func (x *CloseRoomResponse) String() string { func (*CloseRoomResponse) ProtoMessage() {} func (x *CloseRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[75] + mi := &file_proto_room_v1_room_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6227,7 +6325,7 @@ func (x *CloseRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CloseRoomResponse.ProtoReflect.Descriptor instead. func (*CloseRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{75} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{76} } func (x *CloseRoomResponse) GetResult() *CommandResult { @@ -6273,7 +6371,7 @@ type AdminRoomListItem struct { func (x *AdminRoomListItem) Reset() { *x = AdminRoomListItem{} - mi := &file_proto_room_v1_room_proto_msgTypes[76] + mi := &file_proto_room_v1_room_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6285,7 +6383,7 @@ func (x *AdminRoomListItem) String() string { func (*AdminRoomListItem) ProtoMessage() {} func (x *AdminRoomListItem) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[76] + mi := &file_proto_room_v1_room_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6298,7 +6396,7 @@ func (x *AdminRoomListItem) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminRoomListItem.ProtoReflect.Descriptor instead. func (*AdminRoomListItem) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{76} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{77} } func (x *AdminRoomListItem) GetRoomId() string { @@ -6452,7 +6550,7 @@ type AdminListRoomsRequest struct { func (x *AdminListRoomsRequest) Reset() { *x = AdminListRoomsRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[77] + mi := &file_proto_room_v1_room_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6464,7 +6562,7 @@ func (x *AdminListRoomsRequest) String() string { func (*AdminListRoomsRequest) ProtoMessage() {} func (x *AdminListRoomsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[77] + mi := &file_proto_room_v1_room_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6477,7 +6575,7 @@ func (x *AdminListRoomsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminListRoomsRequest.ProtoReflect.Descriptor instead. func (*AdminListRoomsRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{77} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{78} } func (x *AdminListRoomsRequest) GetMeta() *RequestMeta { @@ -6555,7 +6653,7 @@ type AdminListRoomsResponse struct { func (x *AdminListRoomsResponse) Reset() { *x = AdminListRoomsResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[78] + mi := &file_proto_room_v1_room_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6567,7 +6665,7 @@ func (x *AdminListRoomsResponse) String() string { func (*AdminListRoomsResponse) ProtoMessage() {} func (x *AdminListRoomsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[78] + mi := &file_proto_room_v1_room_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6580,7 +6678,7 @@ func (x *AdminListRoomsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminListRoomsResponse.ProtoReflect.Descriptor instead. func (*AdminListRoomsResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{78} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{79} } func (x *AdminListRoomsResponse) GetRooms() []*AdminRoomListItem { @@ -6615,7 +6713,7 @@ type AdminGetRoomRequest struct { func (x *AdminGetRoomRequest) Reset() { *x = AdminGetRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[79] + mi := &file_proto_room_v1_room_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6627,7 +6725,7 @@ func (x *AdminGetRoomRequest) String() string { func (*AdminGetRoomRequest) ProtoMessage() {} func (x *AdminGetRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[79] + mi := &file_proto_room_v1_room_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6640,7 +6738,7 @@ func (x *AdminGetRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminGetRoomRequest.ProtoReflect.Descriptor instead. func (*AdminGetRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{79} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{80} } func (x *AdminGetRoomRequest) GetMeta() *RequestMeta { @@ -6668,7 +6766,7 @@ type AdminGetRoomResponse struct { func (x *AdminGetRoomResponse) Reset() { *x = AdminGetRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[80] + mi := &file_proto_room_v1_room_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6680,7 +6778,7 @@ func (x *AdminGetRoomResponse) String() string { func (*AdminGetRoomResponse) ProtoMessage() {} func (x *AdminGetRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[80] + mi := &file_proto_room_v1_room_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6693,7 +6791,7 @@ func (x *AdminGetRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminGetRoomResponse.ProtoReflect.Descriptor instead. func (*AdminGetRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{80} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{81} } func (x *AdminGetRoomResponse) GetRoom() *AdminRoomListItem { @@ -6731,7 +6829,7 @@ type AdminUpdateRoomRequest struct { func (x *AdminUpdateRoomRequest) Reset() { *x = AdminUpdateRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[81] + mi := &file_proto_room_v1_room_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6743,7 +6841,7 @@ func (x *AdminUpdateRoomRequest) String() string { func (*AdminUpdateRoomRequest) ProtoMessage() {} func (x *AdminUpdateRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[81] + mi := &file_proto_room_v1_room_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6756,7 +6854,7 @@ func (x *AdminUpdateRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminUpdateRoomRequest.ProtoReflect.Descriptor instead. func (*AdminUpdateRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{81} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{82} } func (x *AdminUpdateRoomRequest) GetMeta() *RequestMeta { @@ -6847,7 +6945,7 @@ type AdminUpdateRoomResponse struct { func (x *AdminUpdateRoomResponse) Reset() { *x = AdminUpdateRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[82] + mi := &file_proto_room_v1_room_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6859,7 +6957,7 @@ func (x *AdminUpdateRoomResponse) String() string { func (*AdminUpdateRoomResponse) ProtoMessage() {} func (x *AdminUpdateRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[82] + mi := &file_proto_room_v1_room_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6872,7 +6970,7 @@ func (x *AdminUpdateRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminUpdateRoomResponse.ProtoReflect.Descriptor instead. func (*AdminUpdateRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{82} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{83} } func (x *AdminUpdateRoomResponse) GetResult() *CommandResult { @@ -6901,7 +6999,7 @@ type AdminDeleteRoomRequest struct { func (x *AdminDeleteRoomRequest) Reset() { *x = AdminDeleteRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[83] + mi := &file_proto_room_v1_room_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6913,7 +7011,7 @@ func (x *AdminDeleteRoomRequest) String() string { func (*AdminDeleteRoomRequest) ProtoMessage() {} func (x *AdminDeleteRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[83] + mi := &file_proto_room_v1_room_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6926,7 +7024,7 @@ func (x *AdminDeleteRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminDeleteRoomRequest.ProtoReflect.Descriptor instead. func (*AdminDeleteRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{83} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{84} } func (x *AdminDeleteRoomRequest) GetMeta() *RequestMeta { @@ -6961,7 +7059,7 @@ type AdminDeleteRoomResponse struct { func (x *AdminDeleteRoomResponse) Reset() { *x = AdminDeleteRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[84] + mi := &file_proto_room_v1_room_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6973,7 +7071,7 @@ func (x *AdminDeleteRoomResponse) String() string { func (*AdminDeleteRoomResponse) ProtoMessage() {} func (x *AdminDeleteRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[84] + mi := &file_proto_room_v1_room_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6986,7 +7084,7 @@ func (x *AdminDeleteRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminDeleteRoomResponse.ProtoReflect.Descriptor instead. func (*AdminDeleteRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{84} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{85} } func (x *AdminDeleteRoomResponse) GetResult() *CommandResult { @@ -7017,7 +7115,7 @@ type MicUpRequest struct { func (x *MicUpRequest) Reset() { *x = MicUpRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[85] + mi := &file_proto_room_v1_room_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7029,7 +7127,7 @@ func (x *MicUpRequest) String() string { func (*MicUpRequest) ProtoMessage() {} func (x *MicUpRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[85] + mi := &file_proto_room_v1_room_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7042,7 +7140,7 @@ func (x *MicUpRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MicUpRequest.ProtoReflect.Descriptor instead. func (*MicUpRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{85} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{86} } func (x *MicUpRequest) GetMeta() *RequestMeta { @@ -7081,7 +7179,7 @@ type MicUpResponse struct { func (x *MicUpResponse) Reset() { *x = MicUpResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[86] + mi := &file_proto_room_v1_room_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7093,7 +7191,7 @@ func (x *MicUpResponse) String() string { func (*MicUpResponse) ProtoMessage() {} func (x *MicUpResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[86] + mi := &file_proto_room_v1_room_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7106,7 +7204,7 @@ func (x *MicUpResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MicUpResponse.ProtoReflect.Descriptor instead. func (*MicUpResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{86} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{87} } func (x *MicUpResponse) GetResult() *CommandResult { @@ -7160,7 +7258,7 @@ type MicDownRequest struct { func (x *MicDownRequest) Reset() { *x = MicDownRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[87] + mi := &file_proto_room_v1_room_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7172,7 +7270,7 @@ func (x *MicDownRequest) String() string { func (*MicDownRequest) ProtoMessage() {} func (x *MicDownRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[87] + mi := &file_proto_room_v1_room_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7185,7 +7283,7 @@ func (x *MicDownRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MicDownRequest.ProtoReflect.Descriptor instead. func (*MicDownRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{87} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{88} } func (x *MicDownRequest) GetMeta() *RequestMeta { @@ -7229,7 +7327,7 @@ type MicDownResponse struct { func (x *MicDownResponse) Reset() { *x = MicDownResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[88] + mi := &file_proto_room_v1_room_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7241,7 +7339,7 @@ func (x *MicDownResponse) String() string { func (*MicDownResponse) ProtoMessage() {} func (x *MicDownResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[88] + mi := &file_proto_room_v1_room_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7254,7 +7352,7 @@ func (x *MicDownResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MicDownResponse.ProtoReflect.Descriptor instead. func (*MicDownResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{88} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{89} } func (x *MicDownResponse) GetResult() *CommandResult { @@ -7293,7 +7391,7 @@ type ChangeMicSeatRequest struct { func (x *ChangeMicSeatRequest) Reset() { *x = ChangeMicSeatRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[89] + mi := &file_proto_room_v1_room_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7305,7 +7403,7 @@ func (x *ChangeMicSeatRequest) String() string { func (*ChangeMicSeatRequest) ProtoMessage() {} func (x *ChangeMicSeatRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[89] + mi := &file_proto_room_v1_room_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7318,7 +7416,7 @@ func (x *ChangeMicSeatRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeMicSeatRequest.ProtoReflect.Descriptor instead. func (*ChangeMicSeatRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{89} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{90} } func (x *ChangeMicSeatRequest) GetMeta() *RequestMeta { @@ -7361,7 +7459,7 @@ type ChangeMicSeatResponse struct { func (x *ChangeMicSeatResponse) Reset() { *x = ChangeMicSeatResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[90] + mi := &file_proto_room_v1_room_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7373,7 +7471,7 @@ func (x *ChangeMicSeatResponse) String() string { func (*ChangeMicSeatResponse) ProtoMessage() {} func (x *ChangeMicSeatResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[90] + mi := &file_proto_room_v1_room_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7386,7 +7484,7 @@ func (x *ChangeMicSeatResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeMicSeatResponse.ProtoReflect.Descriptor instead. func (*ChangeMicSeatResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{90} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{91} } func (x *ChangeMicSeatResponse) GetResult() *CommandResult { @@ -7423,7 +7521,7 @@ type ConfirmMicPublishingRequest struct { func (x *ConfirmMicPublishingRequest) Reset() { *x = ConfirmMicPublishingRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[91] + mi := &file_proto_room_v1_room_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7435,7 +7533,7 @@ func (x *ConfirmMicPublishingRequest) String() string { func (*ConfirmMicPublishingRequest) ProtoMessage() {} func (x *ConfirmMicPublishingRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[91] + mi := &file_proto_room_v1_room_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7448,7 +7546,7 @@ func (x *ConfirmMicPublishingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfirmMicPublishingRequest.ProtoReflect.Descriptor instead. func (*ConfirmMicPublishingRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{91} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{92} } func (x *ConfirmMicPublishingRequest) GetMeta() *RequestMeta { @@ -7513,7 +7611,7 @@ type ConfirmMicPublishingResponse struct { func (x *ConfirmMicPublishingResponse) Reset() { *x = ConfirmMicPublishingResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[92] + mi := &file_proto_room_v1_room_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7525,7 +7623,7 @@ func (x *ConfirmMicPublishingResponse) String() string { func (*ConfirmMicPublishingResponse) ProtoMessage() {} func (x *ConfirmMicPublishingResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[92] + mi := &file_proto_room_v1_room_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7538,7 +7636,7 @@ func (x *ConfirmMicPublishingResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfirmMicPublishingResponse.ProtoReflect.Descriptor instead. func (*ConfirmMicPublishingResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{92} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{93} } func (x *ConfirmMicPublishingResponse) GetResult() *CommandResult { @@ -7577,7 +7675,7 @@ type MicHeartbeatRequest struct { func (x *MicHeartbeatRequest) Reset() { *x = MicHeartbeatRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[93] + mi := &file_proto_room_v1_room_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7589,7 +7687,7 @@ func (x *MicHeartbeatRequest) String() string { func (*MicHeartbeatRequest) ProtoMessage() {} func (x *MicHeartbeatRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[93] + mi := &file_proto_room_v1_room_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7602,7 +7700,7 @@ func (x *MicHeartbeatRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MicHeartbeatRequest.ProtoReflect.Descriptor instead. func (*MicHeartbeatRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{93} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{94} } func (x *MicHeartbeatRequest) GetMeta() *RequestMeta { @@ -7640,7 +7738,7 @@ type MicHeartbeatResponse struct { func (x *MicHeartbeatResponse) Reset() { *x = MicHeartbeatResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[94] + mi := &file_proto_room_v1_room_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7652,7 +7750,7 @@ func (x *MicHeartbeatResponse) String() string { func (*MicHeartbeatResponse) ProtoMessage() {} func (x *MicHeartbeatResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[94] + mi := &file_proto_room_v1_room_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7665,7 +7763,7 @@ func (x *MicHeartbeatResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MicHeartbeatResponse.ProtoReflect.Descriptor instead. func (*MicHeartbeatResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{94} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{95} } func (x *MicHeartbeatResponse) GetResult() *CommandResult { @@ -7712,7 +7810,7 @@ type SetMicMuteRequest struct { func (x *SetMicMuteRequest) Reset() { *x = SetMicMuteRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[95] + mi := &file_proto_room_v1_room_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7724,7 +7822,7 @@ func (x *SetMicMuteRequest) String() string { func (*SetMicMuteRequest) ProtoMessage() {} func (x *SetMicMuteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[95] + mi := &file_proto_room_v1_room_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7737,7 +7835,7 @@ func (x *SetMicMuteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetMicMuteRequest.ProtoReflect.Descriptor instead. func (*SetMicMuteRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{95} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{96} } func (x *SetMicMuteRequest) GetMeta() *RequestMeta { @@ -7781,7 +7879,7 @@ type SetMicMuteResponse struct { func (x *SetMicMuteResponse) Reset() { *x = SetMicMuteResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[96] + mi := &file_proto_room_v1_room_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7793,7 +7891,7 @@ func (x *SetMicMuteResponse) String() string { func (*SetMicMuteResponse) ProtoMessage() {} func (x *SetMicMuteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[96] + mi := &file_proto_room_v1_room_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7806,7 +7904,7 @@ func (x *SetMicMuteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetMicMuteResponse.ProtoReflect.Descriptor instead. func (*SetMicMuteResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{96} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{97} } func (x *SetMicMuteResponse) GetResult() *CommandResult { @@ -7848,7 +7946,7 @@ type ApplyRTCEventRequest struct { func (x *ApplyRTCEventRequest) Reset() { *x = ApplyRTCEventRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[97] + mi := &file_proto_room_v1_room_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7860,7 +7958,7 @@ func (x *ApplyRTCEventRequest) String() string { func (*ApplyRTCEventRequest) ProtoMessage() {} func (x *ApplyRTCEventRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[97] + mi := &file_proto_room_v1_room_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7873,7 +7971,7 @@ func (x *ApplyRTCEventRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyRTCEventRequest.ProtoReflect.Descriptor instead. func (*ApplyRTCEventRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{97} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{98} } func (x *ApplyRTCEventRequest) GetMeta() *RequestMeta { @@ -7938,7 +8036,7 @@ type ApplyRTCEventResponse struct { func (x *ApplyRTCEventResponse) Reset() { *x = ApplyRTCEventResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[98] + mi := &file_proto_room_v1_room_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7950,7 +8048,7 @@ func (x *ApplyRTCEventResponse) String() string { func (*ApplyRTCEventResponse) ProtoMessage() {} func (x *ApplyRTCEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[98] + mi := &file_proto_room_v1_room_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7963,7 +8061,7 @@ func (x *ApplyRTCEventResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyRTCEventResponse.ProtoReflect.Descriptor instead. func (*ApplyRTCEventResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{98} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{99} } func (x *ApplyRTCEventResponse) GetResult() *CommandResult { @@ -8000,7 +8098,7 @@ type SetMicSeatLockRequest struct { func (x *SetMicSeatLockRequest) Reset() { *x = SetMicSeatLockRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[99] + mi := &file_proto_room_v1_room_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8012,7 +8110,7 @@ func (x *SetMicSeatLockRequest) String() string { func (*SetMicSeatLockRequest) ProtoMessage() {} func (x *SetMicSeatLockRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[99] + mi := &file_proto_room_v1_room_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8025,7 +8123,7 @@ func (x *SetMicSeatLockRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetMicSeatLockRequest.ProtoReflect.Descriptor instead. func (*SetMicSeatLockRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{99} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{100} } func (x *SetMicSeatLockRequest) GetMeta() *RequestMeta { @@ -8061,7 +8159,7 @@ type SetMicSeatLockResponse struct { func (x *SetMicSeatLockResponse) Reset() { *x = SetMicSeatLockResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[100] + mi := &file_proto_room_v1_room_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8073,7 +8171,7 @@ func (x *SetMicSeatLockResponse) String() string { func (*SetMicSeatLockResponse) ProtoMessage() {} func (x *SetMicSeatLockResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[100] + mi := &file_proto_room_v1_room_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8086,7 +8184,7 @@ func (x *SetMicSeatLockResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetMicSeatLockResponse.ProtoReflect.Descriptor instead. func (*SetMicSeatLockResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{100} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{101} } func (x *SetMicSeatLockResponse) GetResult() *CommandResult { @@ -8115,7 +8213,7 @@ type SetChatEnabledRequest struct { func (x *SetChatEnabledRequest) Reset() { *x = SetChatEnabledRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[101] + mi := &file_proto_room_v1_room_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8127,7 +8225,7 @@ func (x *SetChatEnabledRequest) String() string { func (*SetChatEnabledRequest) ProtoMessage() {} func (x *SetChatEnabledRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[101] + mi := &file_proto_room_v1_room_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8140,7 +8238,7 @@ func (x *SetChatEnabledRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetChatEnabledRequest.ProtoReflect.Descriptor instead. func (*SetChatEnabledRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{101} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{102} } func (x *SetChatEnabledRequest) GetMeta() *RequestMeta { @@ -8169,7 +8267,7 @@ type SetChatEnabledResponse struct { func (x *SetChatEnabledResponse) Reset() { *x = SetChatEnabledResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[102] + mi := &file_proto_room_v1_room_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8181,7 +8279,7 @@ func (x *SetChatEnabledResponse) String() string { func (*SetChatEnabledResponse) ProtoMessage() {} func (x *SetChatEnabledResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[102] + mi := &file_proto_room_v1_room_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8194,7 +8292,7 @@ func (x *SetChatEnabledResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetChatEnabledResponse.ProtoReflect.Descriptor instead. func (*SetChatEnabledResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{102} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{103} } func (x *SetChatEnabledResponse) GetResult() *CommandResult { @@ -8225,7 +8323,7 @@ type SetRoomPasswordRequest struct { func (x *SetRoomPasswordRequest) Reset() { *x = SetRoomPasswordRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[103] + mi := &file_proto_room_v1_room_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8237,7 +8335,7 @@ func (x *SetRoomPasswordRequest) String() string { func (*SetRoomPasswordRequest) ProtoMessage() {} func (x *SetRoomPasswordRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[103] + mi := &file_proto_room_v1_room_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8250,7 +8348,7 @@ func (x *SetRoomPasswordRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetRoomPasswordRequest.ProtoReflect.Descriptor instead. func (*SetRoomPasswordRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{103} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{104} } func (x *SetRoomPasswordRequest) GetMeta() *RequestMeta { @@ -8286,7 +8384,7 @@ type SetRoomPasswordResponse struct { func (x *SetRoomPasswordResponse) Reset() { *x = SetRoomPasswordResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[104] + mi := &file_proto_room_v1_room_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8298,7 +8396,7 @@ func (x *SetRoomPasswordResponse) String() string { func (*SetRoomPasswordResponse) ProtoMessage() {} func (x *SetRoomPasswordResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[104] + mi := &file_proto_room_v1_room_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8311,7 +8409,7 @@ func (x *SetRoomPasswordResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetRoomPasswordResponse.ProtoReflect.Descriptor instead. func (*SetRoomPasswordResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{104} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{105} } func (x *SetRoomPasswordResponse) GetResult() *CommandResult { @@ -8341,7 +8439,7 @@ type SetRoomAdminRequest struct { func (x *SetRoomAdminRequest) Reset() { *x = SetRoomAdminRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[105] + mi := &file_proto_room_v1_room_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8353,7 +8451,7 @@ func (x *SetRoomAdminRequest) String() string { func (*SetRoomAdminRequest) ProtoMessage() {} func (x *SetRoomAdminRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[105] + mi := &file_proto_room_v1_room_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8366,7 +8464,7 @@ func (x *SetRoomAdminRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetRoomAdminRequest.ProtoReflect.Descriptor instead. func (*SetRoomAdminRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{105} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{106} } func (x *SetRoomAdminRequest) GetMeta() *RequestMeta { @@ -8402,7 +8500,7 @@ type SetRoomAdminResponse struct { func (x *SetRoomAdminResponse) Reset() { *x = SetRoomAdminResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[106] + mi := &file_proto_room_v1_room_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8414,7 +8512,7 @@ func (x *SetRoomAdminResponse) String() string { func (*SetRoomAdminResponse) ProtoMessage() {} func (x *SetRoomAdminResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[106] + mi := &file_proto_room_v1_room_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8427,7 +8525,7 @@ func (x *SetRoomAdminResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetRoomAdminResponse.ProtoReflect.Descriptor instead. func (*SetRoomAdminResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{106} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{107} } func (x *SetRoomAdminResponse) GetResult() *CommandResult { @@ -8457,7 +8555,7 @@ type MuteUserRequest struct { func (x *MuteUserRequest) Reset() { *x = MuteUserRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[107] + mi := &file_proto_room_v1_room_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8469,7 +8567,7 @@ func (x *MuteUserRequest) String() string { func (*MuteUserRequest) ProtoMessage() {} func (x *MuteUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[107] + mi := &file_proto_room_v1_room_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8482,7 +8580,7 @@ func (x *MuteUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MuteUserRequest.ProtoReflect.Descriptor instead. func (*MuteUserRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{107} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{108} } func (x *MuteUserRequest) GetMeta() *RequestMeta { @@ -8518,7 +8616,7 @@ type MuteUserResponse struct { func (x *MuteUserResponse) Reset() { *x = MuteUserResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[108] + mi := &file_proto_room_v1_room_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8530,7 +8628,7 @@ func (x *MuteUserResponse) String() string { func (*MuteUserResponse) ProtoMessage() {} func (x *MuteUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[108] + mi := &file_proto_room_v1_room_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8543,7 +8641,7 @@ func (x *MuteUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MuteUserResponse.ProtoReflect.Descriptor instead. func (*MuteUserResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{108} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{109} } func (x *MuteUserResponse) GetResult() *CommandResult { @@ -8574,7 +8672,7 @@ type KickUserRequest struct { func (x *KickUserRequest) Reset() { *x = KickUserRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[109] + mi := &file_proto_room_v1_room_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8586,7 +8684,7 @@ func (x *KickUserRequest) String() string { func (*KickUserRequest) ProtoMessage() {} func (x *KickUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[109] + mi := &file_proto_room_v1_room_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8599,7 +8697,7 @@ func (x *KickUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use KickUserRequest.ProtoReflect.Descriptor instead. func (*KickUserRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{109} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{110} } func (x *KickUserRequest) GetMeta() *RequestMeta { @@ -8639,7 +8737,7 @@ type KickUserResponse struct { func (x *KickUserResponse) Reset() { *x = KickUserResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[110] + mi := &file_proto_room_v1_room_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8651,7 +8749,7 @@ func (x *KickUserResponse) String() string { func (*KickUserResponse) ProtoMessage() {} func (x *KickUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[110] + mi := &file_proto_room_v1_room_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8664,7 +8762,7 @@ func (x *KickUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use KickUserResponse.ProtoReflect.Descriptor instead. func (*KickUserResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{110} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{111} } func (x *KickUserResponse) GetResult() *CommandResult { @@ -8707,7 +8805,7 @@ type UnbanUserRequest struct { func (x *UnbanUserRequest) Reset() { *x = UnbanUserRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[111] + mi := &file_proto_room_v1_room_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8719,7 +8817,7 @@ func (x *UnbanUserRequest) String() string { func (*UnbanUserRequest) ProtoMessage() {} func (x *UnbanUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[111] + mi := &file_proto_room_v1_room_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8732,7 +8830,7 @@ func (x *UnbanUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UnbanUserRequest.ProtoReflect.Descriptor instead. func (*UnbanUserRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{111} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{112} } func (x *UnbanUserRequest) GetMeta() *RequestMeta { @@ -8761,7 +8859,7 @@ type UnbanUserResponse struct { func (x *UnbanUserResponse) Reset() { *x = UnbanUserResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[112] + mi := &file_proto_room_v1_room_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8773,7 +8871,7 @@ func (x *UnbanUserResponse) String() string { func (*UnbanUserResponse) ProtoMessage() {} func (x *UnbanUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[112] + mi := &file_proto_room_v1_room_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8786,7 +8884,7 @@ func (x *UnbanUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UnbanUserResponse.ProtoReflect.Descriptor instead. func (*UnbanUserResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{112} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{113} } func (x *UnbanUserResponse) GetResult() *CommandResult { @@ -8820,7 +8918,7 @@ type SystemEvictUserRequest struct { func (x *SystemEvictUserRequest) Reset() { *x = SystemEvictUserRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[113] + mi := &file_proto_room_v1_room_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8832,7 +8930,7 @@ func (x *SystemEvictUserRequest) String() string { func (*SystemEvictUserRequest) ProtoMessage() {} func (x *SystemEvictUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[113] + mi := &file_proto_room_v1_room_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8845,7 +8943,7 @@ func (x *SystemEvictUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SystemEvictUserRequest.ProtoReflect.Descriptor instead. func (*SystemEvictUserRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{113} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{114} } func (x *SystemEvictUserRequest) GetMeta() *RequestMeta { @@ -8899,7 +8997,7 @@ type SystemEvictUserResponse struct { func (x *SystemEvictUserResponse) Reset() { *x = SystemEvictUserResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[114] + mi := &file_proto_room_v1_room_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8911,7 +9009,7 @@ func (x *SystemEvictUserResponse) String() string { func (*SystemEvictUserResponse) ProtoMessage() {} func (x *SystemEvictUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[114] + mi := &file_proto_room_v1_room_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8924,7 +9022,7 @@ func (x *SystemEvictUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SystemEvictUserResponse.ProtoReflect.Descriptor instead. func (*SystemEvictUserResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{114} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{115} } func (x *SystemEvictUserResponse) GetHadCurrentRoom() bool { @@ -8986,7 +9084,7 @@ type SendGiftTargetHostScope struct { func (x *SendGiftTargetHostScope) Reset() { *x = SendGiftTargetHostScope{} - mi := &file_proto_room_v1_room_proto_msgTypes[115] + mi := &file_proto_room_v1_room_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8998,7 +9096,7 @@ func (x *SendGiftTargetHostScope) String() string { func (*SendGiftTargetHostScope) ProtoMessage() {} func (x *SendGiftTargetHostScope) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[115] + mi := &file_proto_room_v1_room_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9011,7 +9109,7 @@ func (x *SendGiftTargetHostScope) ProtoReflect() protoreflect.Message { // Deprecated: Use SendGiftTargetHostScope.ProtoReflect.Descriptor instead. func (*SendGiftTargetHostScope) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{115} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{116} } func (x *SendGiftTargetHostScope) GetTargetUserId() int64 { @@ -9058,7 +9156,7 @@ type SendGiftDisplayProfile struct { func (x *SendGiftDisplayProfile) Reset() { *x = SendGiftDisplayProfile{} - mi := &file_proto_room_v1_room_proto_msgTypes[116] + mi := &file_proto_room_v1_room_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9070,7 +9168,7 @@ func (x *SendGiftDisplayProfile) String() string { func (*SendGiftDisplayProfile) ProtoMessage() {} func (x *SendGiftDisplayProfile) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[116] + mi := &file_proto_room_v1_room_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9083,7 +9181,7 @@ func (x *SendGiftDisplayProfile) ProtoReflect() protoreflect.Message { // Deprecated: Use SendGiftDisplayProfile.ProtoReflect.Descriptor instead. func (*SendGiftDisplayProfile) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{116} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{117} } func (x *SendGiftDisplayProfile) GetUserId() int64 { @@ -9143,7 +9241,7 @@ type SendGiftBatchTarget struct { func (x *SendGiftBatchTarget) Reset() { *x = SendGiftBatchTarget{} - mi := &file_proto_room_v1_room_proto_msgTypes[117] + mi := &file_proto_room_v1_room_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9155,7 +9253,7 @@ func (x *SendGiftBatchTarget) String() string { func (*SendGiftBatchTarget) ProtoMessage() {} func (x *SendGiftBatchTarget) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[117] + mi := &file_proto_room_v1_room_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9168,7 +9266,7 @@ func (x *SendGiftBatchTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use SendGiftBatchTarget.ProtoReflect.Descriptor instead. func (*SendGiftBatchTarget) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{117} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{118} } func (x *SendGiftBatchTarget) GetTargetUserId() int64 { @@ -9281,7 +9379,7 @@ type SendGiftBatchDisplay struct { func (x *SendGiftBatchDisplay) Reset() { *x = SendGiftBatchDisplay{} - mi := &file_proto_room_v1_room_proto_msgTypes[118] + mi := &file_proto_room_v1_room_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9293,7 +9391,7 @@ func (x *SendGiftBatchDisplay) String() string { func (*SendGiftBatchDisplay) ProtoMessage() {} func (x *SendGiftBatchDisplay) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[118] + mi := &file_proto_room_v1_room_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9306,7 +9404,7 @@ func (x *SendGiftBatchDisplay) ProtoReflect() protoreflect.Message { // Deprecated: Use SendGiftBatchDisplay.ProtoReflect.Descriptor instead. func (*SendGiftBatchDisplay) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{118} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{119} } func (x *SendGiftBatchDisplay) GetEventId() string { @@ -9500,11 +9598,14 @@ type SendGiftRequest struct { TargetDisplayProfiles []*SendGiftDisplayProfile `protobuf:"bytes,17,rep,name=target_display_profiles,json=targetDisplayProfiles,proto3" json:"target_display_profiles,omitempty"` // display_mode=batch 只由新 batch-send HTTP 入口设置,用来生成单条多人展示 IM;旧 send 入口保持空值。 DisplayMode string `protobuf:"bytes,18,opt,name=display_mode,json=displayMode,proto3" json:"display_mode,omitempty"` + // combo_session_id + batch_seq 只描述 Flutter 微批会话,command_id 仍是跨服务唯一幂等键。 + ComboSessionId string `protobuf:"bytes,19,opt,name=combo_session_id,json=comboSessionId,proto3" json:"combo_session_id,omitempty"` + BatchSeq int64 `protobuf:"varint,20,opt,name=batch_seq,json=batchSeq,proto3" json:"batch_seq,omitempty"` } func (x *SendGiftRequest) Reset() { *x = SendGiftRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[119] + mi := &file_proto_room_v1_room_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9516,7 +9617,7 @@ func (x *SendGiftRequest) String() string { func (*SendGiftRequest) ProtoMessage() {} func (x *SendGiftRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[119] + mi := &file_proto_room_v1_room_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9529,7 +9630,7 @@ func (x *SendGiftRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SendGiftRequest.ProtoReflect.Descriptor instead. func (*SendGiftRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{119} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{120} } func (x *SendGiftRequest) GetMeta() *RequestMeta { @@ -9658,6 +9759,20 @@ func (x *SendGiftRequest) GetDisplayMode() string { return "" } +func (x *SendGiftRequest) GetComboSessionId() string { + if x != nil { + return x.ComboSessionId + } + return "" +} + +func (x *SendGiftRequest) GetBatchSeq() int64 { + if x != nil { + return x.BatchSeq + } + return 0 +} + // SendGiftResponse 返回扣费成功并落到房间后的状态结果。 type SendGiftResponse struct { state protoimpl.MessageState @@ -9680,11 +9795,13 @@ type SendGiftResponse struct { GiftIncomeBalanceAfter int64 `protobuf:"varint,10,opt,name=gift_income_balance_after,json=giftIncomeBalanceAfter,proto3" json:"gift_income_balance_after,omitempty"` // batch_display 是新 batch-send 接口的多人展示快照;旧 send 入口保持空值。 BatchDisplay *SendGiftBatchDisplay `protobuf:"bytes,11,opt,name=batch_display,json=batchDisplay,proto3" json:"batch_display,omitempty"` + // v2 保存并重放首次成功的原始结算结果;追加字段不改变旧 Flutter 对顶层 V1 字段的读取。 + V2 *SendGiftResultV2 `protobuf:"bytes,12,opt,name=v2,proto3" json:"v2,omitempty"` } func (x *SendGiftResponse) Reset() { *x = SendGiftResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[120] + mi := &file_proto_room_v1_room_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9696,7 +9813,7 @@ func (x *SendGiftResponse) String() string { func (*SendGiftResponse) ProtoMessage() {} func (x *SendGiftResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[120] + mi := &file_proto_room_v1_room_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9709,7 +9826,7 @@ func (x *SendGiftResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SendGiftResponse.ProtoReflect.Descriptor instead. func (*SendGiftResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{120} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{121} } func (x *SendGiftResponse) GetResult() *CommandResult { @@ -9789,6 +9906,13 @@ func (x *SendGiftResponse) GetBatchDisplay() *SendGiftBatchDisplay { return nil } +func (x *SendGiftResponse) GetV2() *SendGiftResultV2 { + if x != nil { + return x.V2 + } + return nil +} + // CheckSpeakPermissionRequest 让腾讯云 IM 发言回调或 gateway 在公屏前同步问房间业务态。 type CheckSpeakPermissionRequest struct { state protoimpl.MessageState @@ -9802,7 +9926,7 @@ type CheckSpeakPermissionRequest struct { func (x *CheckSpeakPermissionRequest) Reset() { *x = CheckSpeakPermissionRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[121] + mi := &file_proto_room_v1_room_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9814,7 +9938,7 @@ func (x *CheckSpeakPermissionRequest) String() string { func (*CheckSpeakPermissionRequest) ProtoMessage() {} func (x *CheckSpeakPermissionRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[121] + mi := &file_proto_room_v1_room_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9827,7 +9951,7 @@ func (x *CheckSpeakPermissionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckSpeakPermissionRequest.ProtoReflect.Descriptor instead. func (*CheckSpeakPermissionRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{121} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{122} } func (x *CheckSpeakPermissionRequest) GetRoomId() string { @@ -9864,7 +9988,7 @@ type CheckSpeakPermissionResponse struct { func (x *CheckSpeakPermissionResponse) Reset() { *x = CheckSpeakPermissionResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[122] + mi := &file_proto_room_v1_room_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9876,7 +10000,7 @@ func (x *CheckSpeakPermissionResponse) String() string { func (*CheckSpeakPermissionResponse) ProtoMessage() {} func (x *CheckSpeakPermissionResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[122] + mi := &file_proto_room_v1_room_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9889,7 +10013,7 @@ func (x *CheckSpeakPermissionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckSpeakPermissionResponse.ProtoReflect.Descriptor instead. func (*CheckSpeakPermissionResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{122} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{123} } func (x *CheckSpeakPermissionResponse) GetAllowed() bool { @@ -9928,7 +10052,7 @@ type VerifyRoomPresenceRequest struct { func (x *VerifyRoomPresenceRequest) Reset() { *x = VerifyRoomPresenceRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[123] + mi := &file_proto_room_v1_room_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9940,7 +10064,7 @@ func (x *VerifyRoomPresenceRequest) String() string { func (*VerifyRoomPresenceRequest) ProtoMessage() {} func (x *VerifyRoomPresenceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[123] + mi := &file_proto_room_v1_room_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9953,7 +10077,7 @@ func (x *VerifyRoomPresenceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyRoomPresenceRequest.ProtoReflect.Descriptor instead. func (*VerifyRoomPresenceRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{123} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{124} } func (x *VerifyRoomPresenceRequest) GetRoomId() string { @@ -10004,7 +10128,7 @@ type VerifyRoomPresenceResponse struct { func (x *VerifyRoomPresenceResponse) Reset() { *x = VerifyRoomPresenceResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[124] + mi := &file_proto_room_v1_room_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10016,7 +10140,7 @@ func (x *VerifyRoomPresenceResponse) String() string { func (*VerifyRoomPresenceResponse) ProtoMessage() {} func (x *VerifyRoomPresenceResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[124] + mi := &file_proto_room_v1_room_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10029,7 +10153,7 @@ func (x *VerifyRoomPresenceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyRoomPresenceResponse.ProtoReflect.Descriptor instead. func (*VerifyRoomPresenceResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{124} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{125} } func (x *VerifyRoomPresenceResponse) GetPresent() bool { @@ -10053,8 +10177,109 @@ func (x *VerifyRoomPresenceResponse) GetRoomVersion() int64 { return 0 } -// ListRoomsRequest 查询当前用户所在区域可见的房间列表。 -// visible_region_id 必须来自 gateway 对 user-service 的 GetUser 结果,客户端不能直接提交。 +// ResolveRoomAppCodeRequest 仅供已验签的三方回调按全局 room_id 解析租户。 +// 该入口不接受客户端 app_code,避免固定 URL 参数把 Fami 事件错误路由到 Lalu。 +type ResolveRoomAppCodeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` +} + +func (x *ResolveRoomAppCodeRequest) Reset() { + *x = ResolveRoomAppCodeRequest{} + mi := &file_proto_room_v1_room_proto_msgTypes[126] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResolveRoomAppCodeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResolveRoomAppCodeRequest) ProtoMessage() {} + +func (x *ResolveRoomAppCodeRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_room_v1_room_proto_msgTypes[126] + 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 ResolveRoomAppCodeRequest.ProtoReflect.Descriptor instead. +func (*ResolveRoomAppCodeRequest) Descriptor() ([]byte, []int) { + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{126} +} + +func (x *ResolveRoomAppCodeRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *ResolveRoomAppCodeRequest) GetRoomId() string { + if x != nil { + return x.RoomId + } + return "" +} + +type ResolveRoomAppCodeResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` +} + +func (x *ResolveRoomAppCodeResponse) Reset() { + *x = ResolveRoomAppCodeResponse{} + mi := &file_proto_room_v1_room_proto_msgTypes[127] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResolveRoomAppCodeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResolveRoomAppCodeResponse) ProtoMessage() {} + +func (x *ResolveRoomAppCodeResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_room_v1_room_proto_msgTypes[127] + 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 ResolveRoomAppCodeResponse.ProtoReflect.Descriptor instead. +func (*ResolveRoomAppCodeResponse) Descriptor() ([]byte, []int) { + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{127} +} + +func (x *ResolveRoomAppCodeResponse) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +// ListRoomsRequest 查询首页公共房间列表。 +// visible_region_id 是 gateway 解析的实际查询区域:默认取 viewer 区域,显式筛选时取已校验的 filter_region_id。 +// gateway 同时写 filter_region_id 绑定游标;客户端不能直接构造任一内部字段。 type ListRoomsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -10067,17 +10292,19 @@ type ListRoomsRequest struct { Cursor string `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"` Limit int32 `protobuf:"varint,6,opt,name=limit,proto3" json:"limit,omitempty"` Query string `protobuf:"bytes,7,opt,name=query,proto3" json:"query,omitempty"` - // country_code 是可选国家筛选值;gateway 只能传当前用户区域国家列表内的国家码。 + // country_code 是可选国家筛选值;gateway 必须先确认它属于 active 区域。 CountryCode string `protobuf:"bytes,8,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"` - // viewer_country_code 是当前用户国家,用于区域内国家优先排序,不作为客户端可选筛选条件。 + // viewer_country_code 是当前用户国家,用于 All/区域列表的本国家优先排序。 ViewerCountryCode string `protobuf:"bytes,9,opt,name=viewer_country_code,json=viewerCountryCode,proto3" json:"viewer_country_code,omitempty"` - // all_visible_regions 只能由 gateway 根据后台白名单配置注入;为 true 时列表不按 visible_region_id 过滤。 + // all_visible_regions 只能由 gateway 根据 App 策略或后台白名单注入;为 true 时列表不按 visible_region_id 过滤。 AllVisibleRegions bool `protobuf:"varint,10,opt,name=all_visible_regions,json=allVisibleRegions,proto3" json:"all_visible_regions,omitempty"` + // filter_region_id 是 gateway 校验过的 active 区域筛选;它与 all_visible_regions/country_code 互斥。 + FilterRegionId int64 `protobuf:"varint,11,opt,name=filter_region_id,json=filterRegionId,proto3" json:"filter_region_id,omitempty"` } func (x *ListRoomsRequest) Reset() { *x = ListRoomsRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[125] + mi := &file_proto_room_v1_room_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10089,7 +10316,7 @@ func (x *ListRoomsRequest) String() string { func (*ListRoomsRequest) ProtoMessage() {} func (x *ListRoomsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[125] + mi := &file_proto_room_v1_room_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10102,7 +10329,7 @@ func (x *ListRoomsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomsRequest.ProtoReflect.Descriptor instead. func (*ListRoomsRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{125} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{128} } func (x *ListRoomsRequest) GetMeta() *RequestMeta { @@ -10175,6 +10402,13 @@ func (x *ListRoomsRequest) GetAllVisibleRegions() bool { return false } +func (x *ListRoomsRequest) GetFilterRegionId() int64 { + if x != nil { + return x.FilterRegionId + } + return 0 +} + // ListRoomsByOwnersRequest 查询一组房主名下 active 房间。 // owner_user_ids 必须由 gateway 根据上游业务归属注入,客户端不能直接提交任意房主集合。 type ListRoomsByOwnersRequest struct { @@ -10193,7 +10427,7 @@ type ListRoomsByOwnersRequest struct { func (x *ListRoomsByOwnersRequest) Reset() { *x = ListRoomsByOwnersRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[126] + mi := &file_proto_room_v1_room_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10205,7 +10439,7 @@ func (x *ListRoomsByOwnersRequest) String() string { func (*ListRoomsByOwnersRequest) ProtoMessage() {} func (x *ListRoomsByOwnersRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[126] + mi := &file_proto_room_v1_room_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10218,7 +10452,7 @@ func (x *ListRoomsByOwnersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomsByOwnersRequest.ProtoReflect.Descriptor instead. func (*ListRoomsByOwnersRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{126} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{129} } func (x *ListRoomsByOwnersRequest) GetMeta() *RequestMeta { @@ -10290,7 +10524,7 @@ type ListRoomFeedsRequest struct { func (x *ListRoomFeedsRequest) Reset() { *x = ListRoomFeedsRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[127] + mi := &file_proto_room_v1_room_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10302,7 +10536,7 @@ func (x *ListRoomFeedsRequest) String() string { func (*ListRoomFeedsRequest) ProtoMessage() {} func (x *ListRoomFeedsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[127] + mi := &file_proto_room_v1_room_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10315,7 +10549,7 @@ func (x *ListRoomFeedsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomFeedsRequest.ProtoReflect.Descriptor instead. func (*ListRoomFeedsRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{127} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{130} } func (x *ListRoomFeedsRequest) GetMeta() *RequestMeta { @@ -10388,7 +10622,7 @@ type ListRoomGiftLeaderboardRequest struct { func (x *ListRoomGiftLeaderboardRequest) Reset() { *x = ListRoomGiftLeaderboardRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[128] + mi := &file_proto_room_v1_room_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10400,7 +10634,7 @@ func (x *ListRoomGiftLeaderboardRequest) String() string { func (*ListRoomGiftLeaderboardRequest) ProtoMessage() {} func (x *ListRoomGiftLeaderboardRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[128] + mi := &file_proto_room_v1_room_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10413,7 +10647,7 @@ func (x *ListRoomGiftLeaderboardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomGiftLeaderboardRequest.ProtoReflect.Descriptor instead. func (*ListRoomGiftLeaderboardRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{128} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{131} } func (x *ListRoomGiftLeaderboardRequest) GetMeta() *RequestMeta { @@ -10456,7 +10690,7 @@ type RoomFeedRelatedUser struct { func (x *RoomFeedRelatedUser) Reset() { *x = RoomFeedRelatedUser{} - mi := &file_proto_room_v1_room_proto_msgTypes[129] + mi := &file_proto_room_v1_room_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10468,7 +10702,7 @@ func (x *RoomFeedRelatedUser) String() string { func (*RoomFeedRelatedUser) ProtoMessage() {} func (x *RoomFeedRelatedUser) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[129] + mi := &file_proto_room_v1_room_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10481,7 +10715,7 @@ func (x *RoomFeedRelatedUser) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomFeedRelatedUser.ProtoReflect.Descriptor instead. func (*RoomFeedRelatedUser) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{129} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{132} } func (x *RoomFeedRelatedUser) GetUserId() int64 { @@ -10523,7 +10757,7 @@ type RoomListItem struct { func (x *RoomListItem) Reset() { *x = RoomListItem{} - mi := &file_proto_room_v1_room_proto_msgTypes[130] + mi := &file_proto_room_v1_room_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10535,7 +10769,7 @@ func (x *RoomListItem) String() string { func (*RoomListItem) ProtoMessage() {} func (x *RoomListItem) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[130] + mi := &file_proto_room_v1_room_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10548,7 +10782,7 @@ func (x *RoomListItem) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomListItem.ProtoReflect.Descriptor instead. func (*RoomListItem) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{130} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{133} } func (x *RoomListItem) GetRoomId() string { @@ -10668,7 +10902,7 @@ type ListRoomsResponse struct { func (x *ListRoomsResponse) Reset() { *x = ListRoomsResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[131] + mi := &file_proto_room_v1_room_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10680,7 +10914,7 @@ func (x *ListRoomsResponse) String() string { func (*ListRoomsResponse) ProtoMessage() {} func (x *ListRoomsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[131] + mi := &file_proto_room_v1_room_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10693,7 +10927,7 @@ func (x *ListRoomsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomsResponse.ProtoReflect.Descriptor instead. func (*ListRoomsResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{131} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{134} } func (x *ListRoomsResponse) GetRooms() []*RoomListItem { @@ -10724,7 +10958,7 @@ type RoomGiftLeaderboardItem struct { func (x *RoomGiftLeaderboardItem) Reset() { *x = RoomGiftLeaderboardItem{} - mi := &file_proto_room_v1_room_proto_msgTypes[132] + mi := &file_proto_room_v1_room_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10736,7 +10970,7 @@ func (x *RoomGiftLeaderboardItem) String() string { func (*RoomGiftLeaderboardItem) ProtoMessage() {} func (x *RoomGiftLeaderboardItem) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[132] + mi := &file_proto_room_v1_room_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10749,7 +10983,7 @@ func (x *RoomGiftLeaderboardItem) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomGiftLeaderboardItem.ProtoReflect.Descriptor instead. func (*RoomGiftLeaderboardItem) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{132} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{135} } func (x *RoomGiftLeaderboardItem) GetRank() int64 { @@ -10796,7 +11030,7 @@ type ListRoomGiftLeaderboardResponse struct { func (x *ListRoomGiftLeaderboardResponse) Reset() { *x = ListRoomGiftLeaderboardResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[133] + mi := &file_proto_room_v1_room_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10808,7 +11042,7 @@ func (x *ListRoomGiftLeaderboardResponse) String() string { func (*ListRoomGiftLeaderboardResponse) ProtoMessage() {} func (x *ListRoomGiftLeaderboardResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[133] + mi := &file_proto_room_v1_room_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10821,7 +11055,7 @@ func (x *ListRoomGiftLeaderboardResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomGiftLeaderboardResponse.ProtoReflect.Descriptor instead. func (*ListRoomGiftLeaderboardResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{133} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{136} } func (x *ListRoomGiftLeaderboardResponse) GetItems() []*RoomGiftLeaderboardItem { @@ -10881,7 +11115,7 @@ type ListRoomContributionRankRequest struct { func (x *ListRoomContributionRankRequest) Reset() { *x = ListRoomContributionRankRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[134] + mi := &file_proto_room_v1_room_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10893,7 +11127,7 @@ func (x *ListRoomContributionRankRequest) String() string { func (*ListRoomContributionRankRequest) ProtoMessage() {} func (x *ListRoomContributionRankRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[134] + mi := &file_proto_room_v1_room_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10906,7 +11140,7 @@ func (x *ListRoomContributionRankRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomContributionRankRequest.ProtoReflect.Descriptor instead. func (*ListRoomContributionRankRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{134} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{137} } func (x *ListRoomContributionRankRequest) GetMeta() *RequestMeta { @@ -10961,7 +11195,7 @@ type ListRoomContributionRankResponse struct { func (x *ListRoomContributionRankResponse) Reset() { *x = ListRoomContributionRankResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[135] + mi := &file_proto_room_v1_room_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10973,7 +11207,7 @@ func (x *ListRoomContributionRankResponse) String() string { func (*ListRoomContributionRankResponse) ProtoMessage() {} func (x *ListRoomContributionRankResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[135] + mi := &file_proto_room_v1_room_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10986,7 +11220,7 @@ func (x *ListRoomContributionRankResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomContributionRankResponse.ProtoReflect.Descriptor instead. func (*ListRoomContributionRankResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{135} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{138} } func (x *ListRoomContributionRankResponse) GetRoomId() string { @@ -11051,7 +11285,7 @@ type GetMyRoomRequest struct { func (x *GetMyRoomRequest) Reset() { *x = GetMyRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[136] + mi := &file_proto_room_v1_room_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11063,7 +11297,7 @@ func (x *GetMyRoomRequest) String() string { func (*GetMyRoomRequest) ProtoMessage() {} func (x *GetMyRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[136] + mi := &file_proto_room_v1_room_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11076,7 +11310,7 @@ func (x *GetMyRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMyRoomRequest.ProtoReflect.Descriptor instead. func (*GetMyRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{136} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{139} } func (x *GetMyRoomRequest) GetMeta() *RequestMeta { @@ -11107,7 +11341,7 @@ type GetMyRoomResponse struct { func (x *GetMyRoomResponse) Reset() { *x = GetMyRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[137] + mi := &file_proto_room_v1_room_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11119,7 +11353,7 @@ func (x *GetMyRoomResponse) String() string { func (*GetMyRoomResponse) ProtoMessage() {} func (x *GetMyRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[137] + mi := &file_proto_room_v1_room_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11132,7 +11366,7 @@ func (x *GetMyRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMyRoomResponse.ProtoReflect.Descriptor instead. func (*GetMyRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{137} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{140} } func (x *GetMyRoomResponse) GetHasRoom() bool { @@ -11169,7 +11403,7 @@ type GetCurrentRoomRequest struct { func (x *GetCurrentRoomRequest) Reset() { *x = GetCurrentRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[138] + mi := &file_proto_room_v1_room_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11181,7 +11415,7 @@ func (x *GetCurrentRoomRequest) String() string { func (*GetCurrentRoomRequest) ProtoMessage() {} func (x *GetCurrentRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[138] + mi := &file_proto_room_v1_room_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11194,7 +11428,7 @@ func (x *GetCurrentRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCurrentRoomRequest.ProtoReflect.Descriptor instead. func (*GetCurrentRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{138} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{141} } func (x *GetCurrentRoomRequest) GetMeta() *RequestMeta { @@ -11231,7 +11465,7 @@ type GetCurrentRoomResponse struct { func (x *GetCurrentRoomResponse) Reset() { *x = GetCurrentRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[139] + mi := &file_proto_room_v1_room_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11243,7 +11477,7 @@ func (x *GetCurrentRoomResponse) String() string { func (*GetCurrentRoomResponse) ProtoMessage() {} func (x *GetCurrentRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[139] + mi := &file_proto_room_v1_room_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11256,7 +11490,7 @@ func (x *GetCurrentRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCurrentRoomResponse.ProtoReflect.Descriptor instead. func (*GetCurrentRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{139} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{142} } func (x *GetCurrentRoomResponse) GetHasCurrentRoom() bool { @@ -11336,7 +11570,7 @@ type GetRoomSnapshotRequest struct { func (x *GetRoomSnapshotRequest) Reset() { *x = GetRoomSnapshotRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[140] + mi := &file_proto_room_v1_room_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11348,7 +11582,7 @@ func (x *GetRoomSnapshotRequest) String() string { func (*GetRoomSnapshotRequest) ProtoMessage() {} func (x *GetRoomSnapshotRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[140] + mi := &file_proto_room_v1_room_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11361,7 +11595,7 @@ func (x *GetRoomSnapshotRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRoomSnapshotRequest.ProtoReflect.Descriptor instead. func (*GetRoomSnapshotRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{140} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{143} } func (x *GetRoomSnapshotRequest) GetMeta() *RequestMeta { @@ -11399,7 +11633,7 @@ type GetRoomSnapshotResponse struct { func (x *GetRoomSnapshotResponse) Reset() { *x = GetRoomSnapshotResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[141] + mi := &file_proto_room_v1_room_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11411,7 +11645,7 @@ func (x *GetRoomSnapshotResponse) String() string { func (*GetRoomSnapshotResponse) ProtoMessage() {} func (x *GetRoomSnapshotResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[141] + mi := &file_proto_room_v1_room_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11424,7 +11658,7 @@ func (x *GetRoomSnapshotResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRoomSnapshotResponse.ProtoReflect.Descriptor instead. func (*GetRoomSnapshotResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{141} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{144} } func (x *GetRoomSnapshotResponse) GetRoom() *RoomSnapshot { @@ -11462,7 +11696,7 @@ type GetRoomRocketRequest struct { func (x *GetRoomRocketRequest) Reset() { *x = GetRoomRocketRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[142] + mi := &file_proto_room_v1_room_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11474,7 +11708,7 @@ func (x *GetRoomRocketRequest) String() string { func (*GetRoomRocketRequest) ProtoMessage() {} func (x *GetRoomRocketRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[142] + mi := &file_proto_room_v1_room_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11487,7 +11721,7 @@ func (x *GetRoomRocketRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRoomRocketRequest.ProtoReflect.Descriptor instead. func (*GetRoomRocketRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{142} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{145} } func (x *GetRoomRocketRequest) GetMeta() *RequestMeta { @@ -11522,7 +11756,7 @@ type GetRoomRocketResponse struct { func (x *GetRoomRocketResponse) Reset() { *x = GetRoomRocketResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[143] + mi := &file_proto_room_v1_room_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11534,7 +11768,7 @@ func (x *GetRoomRocketResponse) String() string { func (*GetRoomRocketResponse) ProtoMessage() {} func (x *GetRoomRocketResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[143] + mi := &file_proto_room_v1_room_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11547,7 +11781,7 @@ func (x *GetRoomRocketResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRoomRocketResponse.ProtoReflect.Descriptor instead. func (*GetRoomRocketResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{143} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{146} } func (x *GetRoomRocketResponse) GetRocket() *RoomRocketInfo { @@ -11580,7 +11814,7 @@ type ListRoomOnlineUsersRequest struct { func (x *ListRoomOnlineUsersRequest) Reset() { *x = ListRoomOnlineUsersRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[144] + mi := &file_proto_room_v1_room_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11592,7 +11826,7 @@ func (x *ListRoomOnlineUsersRequest) String() string { func (*ListRoomOnlineUsersRequest) ProtoMessage() {} func (x *ListRoomOnlineUsersRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[144] + mi := &file_proto_room_v1_room_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11605,7 +11839,7 @@ func (x *ListRoomOnlineUsersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomOnlineUsersRequest.ProtoReflect.Descriptor instead. func (*ListRoomOnlineUsersRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{144} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{147} } func (x *ListRoomOnlineUsersRequest) GetMeta() *RequestMeta { @@ -11665,7 +11899,7 @@ type ListRoomOnlineUsersResponse struct { func (x *ListRoomOnlineUsersResponse) Reset() { *x = ListRoomOnlineUsersResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[145] + mi := &file_proto_room_v1_room_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11677,7 +11911,7 @@ func (x *ListRoomOnlineUsersResponse) String() string { func (*ListRoomOnlineUsersResponse) ProtoMessage() {} func (x *ListRoomOnlineUsersResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[145] + mi := &file_proto_room_v1_room_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11690,7 +11924,7 @@ func (x *ListRoomOnlineUsersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomOnlineUsersResponse.ProtoReflect.Descriptor instead. func (*ListRoomOnlineUsersResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{145} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{148} } func (x *ListRoomOnlineUsersResponse) GetUsers() []*RoomUser { @@ -11752,7 +11986,7 @@ type RoomBannedUser struct { func (x *RoomBannedUser) Reset() { *x = RoomBannedUser{} - mi := &file_proto_room_v1_room_proto_msgTypes[146] + mi := &file_proto_room_v1_room_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11764,7 +11998,7 @@ func (x *RoomBannedUser) String() string { func (*RoomBannedUser) ProtoMessage() {} func (x *RoomBannedUser) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[146] + mi := &file_proto_room_v1_room_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11777,7 +12011,7 @@ func (x *RoomBannedUser) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomBannedUser.ProtoReflect.Descriptor instead. func (*RoomBannedUser) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{146} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{149} } func (x *RoomBannedUser) GetUserId() int64 { @@ -11837,7 +12071,7 @@ type ListRoomBannedUsersRequest struct { func (x *ListRoomBannedUsersRequest) Reset() { *x = ListRoomBannedUsersRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[147] + mi := &file_proto_room_v1_room_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11849,7 +12083,7 @@ func (x *ListRoomBannedUsersRequest) String() string { func (*ListRoomBannedUsersRequest) ProtoMessage() {} func (x *ListRoomBannedUsersRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[147] + mi := &file_proto_room_v1_room_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11862,7 +12096,7 @@ func (x *ListRoomBannedUsersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomBannedUsersRequest.ProtoReflect.Descriptor instead. func (*ListRoomBannedUsersRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{147} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{150} } func (x *ListRoomBannedUsersRequest) GetMeta() *RequestMeta { @@ -11914,7 +12148,7 @@ type ListRoomBannedUsersResponse struct { func (x *ListRoomBannedUsersResponse) Reset() { *x = ListRoomBannedUsersResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[148] + mi := &file_proto_room_v1_room_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11926,7 +12160,7 @@ func (x *ListRoomBannedUsersResponse) String() string { func (*ListRoomBannedUsersResponse) ProtoMessage() {} func (x *ListRoomBannedUsersResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[148] + mi := &file_proto_room_v1_room_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11939,7 +12173,7 @@ func (x *ListRoomBannedUsersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomBannedUsersResponse.ProtoReflect.Descriptor instead. func (*ListRoomBannedUsersResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{148} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{151} } func (x *ListRoomBannedUsersResponse) GetItems() []*RoomBannedUser { @@ -11991,7 +12225,7 @@ type FollowRoomRequest struct { func (x *FollowRoomRequest) Reset() { *x = FollowRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[149] + mi := &file_proto_room_v1_room_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12003,7 +12237,7 @@ func (x *FollowRoomRequest) String() string { func (*FollowRoomRequest) ProtoMessage() {} func (x *FollowRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[149] + mi := &file_proto_room_v1_room_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12016,7 +12250,7 @@ func (x *FollowRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FollowRoomRequest.ProtoReflect.Descriptor instead. func (*FollowRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{149} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{152} } func (x *FollowRoomRequest) GetMeta() *RequestMeta { @@ -12053,7 +12287,7 @@ type FollowRoomResponse struct { func (x *FollowRoomResponse) Reset() { *x = FollowRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[150] + mi := &file_proto_room_v1_room_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12065,7 +12299,7 @@ func (x *FollowRoomResponse) String() string { func (*FollowRoomResponse) ProtoMessage() {} func (x *FollowRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[150] + mi := &file_proto_room_v1_room_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12078,7 +12312,7 @@ func (x *FollowRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FollowRoomResponse.ProtoReflect.Descriptor instead. func (*FollowRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{150} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{153} } func (x *FollowRoomResponse) GetRoomId() string { @@ -12122,7 +12356,7 @@ type UnfollowRoomRequest struct { func (x *UnfollowRoomRequest) Reset() { *x = UnfollowRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[151] + mi := &file_proto_room_v1_room_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12134,7 +12368,7 @@ func (x *UnfollowRoomRequest) String() string { func (*UnfollowRoomRequest) ProtoMessage() {} func (x *UnfollowRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[151] + mi := &file_proto_room_v1_room_proto_msgTypes[154] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12147,7 +12381,7 @@ func (x *UnfollowRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UnfollowRoomRequest.ProtoReflect.Descriptor instead. func (*UnfollowRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{151} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{154} } func (x *UnfollowRoomRequest) GetMeta() *RequestMeta { @@ -12183,7 +12417,7 @@ type UnfollowRoomResponse struct { func (x *UnfollowRoomResponse) Reset() { *x = UnfollowRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[152] + mi := &file_proto_room_v1_room_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12195,7 +12429,7 @@ func (x *UnfollowRoomResponse) String() string { func (*UnfollowRoomResponse) ProtoMessage() {} func (x *UnfollowRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[152] + mi := &file_proto_room_v1_room_proto_msgTypes[155] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12208,7 +12442,7 @@ func (x *UnfollowRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UnfollowRoomResponse.ProtoReflect.Descriptor instead. func (*UnfollowRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{152} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{155} } func (x *UnfollowRoomResponse) GetRoomId() string { @@ -12232,6 +12466,287 @@ func (x *UnfollowRoomResponse) GetServerTimeMs() int64 { return 0 } +// SendGiftLuckyHit 是 V2 微批中需要逐次播放的精简中奖命中。 +// 声明追加在文件尾以保持既有 protobuf message index 稳定;字段可在前面的消息中正常引用。 +type SendGiftLuckyHit struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + GiftIndex int32 `protobuf:"varint,1,opt,name=gift_index,json=giftIndex,proto3" json:"gift_index,omitempty"` + DrawId string `protobuf:"bytes,2,opt,name=draw_id,json=drawId,proto3" json:"draw_id,omitempty"` + TargetUserId int64 `protobuf:"varint,3,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + SelectedTierId string `protobuf:"bytes,4,opt,name=selected_tier_id,json=selectedTierId,proto3" json:"selected_tier_id,omitempty"` + MultiplierPpm int64 `protobuf:"varint,5,opt,name=multiplier_ppm,json=multiplierPpm,proto3" json:"multiplier_ppm,omitempty"` + RewardCoins int64 `protobuf:"varint,6,opt,name=reward_coins,json=rewardCoins,proto3" json:"reward_coins,omitempty"` +} + +func (x *SendGiftLuckyHit) Reset() { + *x = SendGiftLuckyHit{} + mi := &file_proto_room_v1_room_proto_msgTypes[156] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SendGiftLuckyHit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendGiftLuckyHit) ProtoMessage() {} + +func (x *SendGiftLuckyHit) ProtoReflect() protoreflect.Message { + mi := &file_proto_room_v1_room_proto_msgTypes[156] + 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 SendGiftLuckyHit.ProtoReflect.Descriptor instead. +func (*SendGiftLuckyHit) Descriptor() ([]byte, []int) { + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{156} +} + +func (x *SendGiftLuckyHit) GetGiftIndex() int32 { + if x != nil { + return x.GiftIndex + } + return 0 +} + +func (x *SendGiftLuckyHit) GetDrawId() string { + if x != nil { + return x.DrawId + } + return "" +} + +func (x *SendGiftLuckyHit) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *SendGiftLuckyHit) GetSelectedTierId() string { + if x != nil { + return x.SelectedTierId + } + return "" +} + +func (x *SendGiftLuckyHit) GetMultiplierPpm() int64 { + if x != nil { + return x.MultiplierPpm + } + return 0 +} + +func (x *SendGiftLuckyHit) GetRewardCoins() int64 { + if x != nil { + return x.RewardCoins + } + return 0 +} + +// SendGiftResultV2 是可持久化、可原样重放的送礼结算结果。 +// 旧客户端继续读取 SendGiftResponse 顶层 V1 字段;V2 客户端只以该对象判断幂等、 +// 提交顺序和实际结算数量,避免网络重试时拿“当前房间快照”冒充原始结算结果。 +type SendGiftResultV2 struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ApiVersion int32 `protobuf:"varint,1,opt,name=api_version,json=apiVersion,proto3" json:"api_version,omitempty"` + CommandId string `protobuf:"bytes,2,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + ComboSessionId string `protobuf:"bytes,3,opt,name=combo_session_id,json=comboSessionId,proto3" json:"combo_session_id,omitempty"` + BatchSeq int64 `protobuf:"varint,4,opt,name=batch_seq,json=batchSeq,proto3" json:"batch_seq,omitempty"` + IdempotentReplay bool `protobuf:"varint,5,opt,name=idempotent_replay,json=idempotentReplay,proto3" json:"idempotent_replay,omitempty"` + CommittedRoomVersion int64 `protobuf:"varint,6,opt,name=committed_room_version,json=committedRoomVersion,proto3" json:"committed_room_version,omitempty"` + BillingReceiptId string `protobuf:"bytes,7,opt,name=billing_receipt_id,json=billingReceiptId,proto3" json:"billing_receipt_id,omitempty"` + CoinBalanceAfter int64 `protobuf:"varint,8,opt,name=coin_balance_after,json=coinBalanceAfter,proto3" json:"coin_balance_after,omitempty"` + GiftIncomeBalanceAfter int64 `protobuf:"varint,9,opt,name=gift_income_balance_after,json=giftIncomeBalanceAfter,proto3" json:"gift_income_balance_after,omitempty"` + SettledGiftCount int32 `protobuf:"varint,10,opt,name=settled_gift_count,json=settledGiftCount,proto3" json:"settled_gift_count,omitempty"` + SettledTargetUserIds []int64 `protobuf:"varint,11,rep,packed,name=settled_target_user_ids,json=settledTargetUserIds,proto3" json:"settled_target_user_ids,omitempty"` + // balance_version 是 sender 被扣费资产在该批次完成后的 wallet 账户版本;幂等重放保持首次值。 + BalanceVersion int64 `protobuf:"varint,12,opt,name=balance_version,json=balanceVersion,proto3" json:"balance_version,omitempty"` + LuckyGift *LuckyGiftDrawResult `protobuf:"bytes,13,opt,name=lucky_gift,json=luckyGift,proto3" json:"lucky_gift,omitempty"` + LuckyGifts []*LuckyGiftDrawResult `protobuf:"bytes,14,rep,name=lucky_gifts,json=luckyGifts,proto3" json:"lucky_gifts,omitempty"` + LuckyHits []*SendGiftLuckyHit `protobuf:"bytes,15,rep,name=lucky_hits,json=luckyHits,proto3" json:"lucky_hits,omitempty"` + BatchDisplay *SendGiftBatchDisplay `protobuf:"bytes,16,opt,name=batch_display,json=batchDisplay,proto3" json:"batch_display,omitempty"` + RoomHeat int64 `protobuf:"varint,17,opt,name=room_heat,json=roomHeat,proto3" json:"room_heat,omitempty"` + GiftRank []*RankItem `protobuf:"bytes,18,rep,name=gift_rank,json=giftRank,proto3" json:"gift_rank,omitempty"` + // operation_status 当前成功响应固定为 committed;该字段让 V2 契约可以明确区分未来的异步受理态。 + OperationStatus string `protobuf:"bytes,19,opt,name=operation_status,json=operationStatus,proto3" json:"operation_status,omitempty"` +} + +func (x *SendGiftResultV2) Reset() { + *x = SendGiftResultV2{} + mi := &file_proto_room_v1_room_proto_msgTypes[157] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SendGiftResultV2) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendGiftResultV2) ProtoMessage() {} + +func (x *SendGiftResultV2) ProtoReflect() protoreflect.Message { + mi := &file_proto_room_v1_room_proto_msgTypes[157] + 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 SendGiftResultV2.ProtoReflect.Descriptor instead. +func (*SendGiftResultV2) Descriptor() ([]byte, []int) { + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{157} +} + +func (x *SendGiftResultV2) GetApiVersion() int32 { + if x != nil { + return x.ApiVersion + } + return 0 +} + +func (x *SendGiftResultV2) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *SendGiftResultV2) GetComboSessionId() string { + if x != nil { + return x.ComboSessionId + } + return "" +} + +func (x *SendGiftResultV2) GetBatchSeq() int64 { + if x != nil { + return x.BatchSeq + } + return 0 +} + +func (x *SendGiftResultV2) GetIdempotentReplay() bool { + if x != nil { + return x.IdempotentReplay + } + return false +} + +func (x *SendGiftResultV2) GetCommittedRoomVersion() int64 { + if x != nil { + return x.CommittedRoomVersion + } + return 0 +} + +func (x *SendGiftResultV2) GetBillingReceiptId() string { + if x != nil { + return x.BillingReceiptId + } + return "" +} + +func (x *SendGiftResultV2) GetCoinBalanceAfter() int64 { + if x != nil { + return x.CoinBalanceAfter + } + return 0 +} + +func (x *SendGiftResultV2) GetGiftIncomeBalanceAfter() int64 { + if x != nil { + return x.GiftIncomeBalanceAfter + } + return 0 +} + +func (x *SendGiftResultV2) GetSettledGiftCount() int32 { + if x != nil { + return x.SettledGiftCount + } + return 0 +} + +func (x *SendGiftResultV2) GetSettledTargetUserIds() []int64 { + if x != nil { + return x.SettledTargetUserIds + } + return nil +} + +func (x *SendGiftResultV2) GetBalanceVersion() int64 { + if x != nil { + return x.BalanceVersion + } + return 0 +} + +func (x *SendGiftResultV2) GetLuckyGift() *LuckyGiftDrawResult { + if x != nil { + return x.LuckyGift + } + return nil +} + +func (x *SendGiftResultV2) GetLuckyGifts() []*LuckyGiftDrawResult { + if x != nil { + return x.LuckyGifts + } + return nil +} + +func (x *SendGiftResultV2) GetLuckyHits() []*SendGiftLuckyHit { + if x != nil { + return x.LuckyHits + } + return nil +} + +func (x *SendGiftResultV2) GetBatchDisplay() *SendGiftBatchDisplay { + if x != nil { + return x.BatchDisplay + } + return nil +} + +func (x *SendGiftResultV2) GetRoomHeat() int64 { + if x != nil { + return x.RoomHeat + } + return 0 +} + +func (x *SendGiftResultV2) GetGiftRank() []*RankItem { + if x != nil { + return x.GiftRank + } + return nil +} + +func (x *SendGiftResultV2) GetOperationStatus() string { + if x != nil { + return x.OperationStatus + } + return "" +} + var File_proto_room_v1_room_proto protoreflect.FileDescriptor var file_proto_room_v1_room_proto_rawDesc = []byte{ @@ -12333,7 +12848,7 @@ var file_proto_room_v1_room_proto_rawDesc = []byte{ 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, - 0xb9, 0x05, 0x0a, 0x13, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, + 0xee, 0x05, 0x0a, 0x13, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, @@ -12376,2074 +12891,2188 @@ var file_proto_room_v1_room_proto_rawDesc = []byte{ 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x69, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xda, 0x01, 0x0a, 0x14, - 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x49, 0x74, 0x65, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, - 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, - 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x21, - 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x63, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, - 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, - 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0xd3, 0x03, 0x0a, 0x0f, 0x52, 0x6f, 0x6f, - 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, - 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, - 0x65, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x75, 0x65, 0x6c, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, - 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66, 0x75, 0x65, 0x6c, - 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x76, - 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, - 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x30, 0x0a, 0x14, 0x6c, - 0x61, 0x75, 0x6e, 0x63, 0x68, 0x5f, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6c, 0x61, 0x75, 0x6e, 0x63, - 0x68, 0x41, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x2c, 0x0a, - 0x12, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, - 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6c, 0x61, 0x75, 0x6e, 0x63, - 0x68, 0x65, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x4b, 0x0a, 0x0f, 0x69, - 0x6e, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x18, 0x07, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, - 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x0d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, - 0x6d, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x12, 0x46, 0x0a, 0x0c, 0x74, 0x6f, 0x70, 0x31, - 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x04, 0x68, + 0x69, 0x74, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, + 0x66, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x48, 0x69, 0x74, 0x52, 0x04, 0x68, 0x69, 0x74, 0x73, + 0x22, 0xda, 0x01, 0x0a, 0x14, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, + 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, + 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x77, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x77, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, + 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x63, 0x6f, 0x6e, 0x55, 0x72, + 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0xd3, 0x03, + 0x0a, 0x0f, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x75, 0x65, 0x6c, 0x5f, + 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0d, 0x66, 0x75, 0x65, 0x6c, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x1b, + 0x0a, 0x09, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x61, + 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, 0x6c, + 0x12, 0x30, 0x0a, 0x14, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x5f, 0x61, 0x6e, 0x69, 0x6d, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, + 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x41, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, + 0x72, 0x6c, 0x12, 0x2c, 0x0a, 0x12, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x69, + 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, + 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x55, 0x72, 0x6c, + 0x12, 0x4b, 0x0a, 0x0f, 0x69, 0x6e, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, + 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x0d, + 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x12, 0x46, 0x0a, + 0x0c, 0x74, 0x6f, 0x70, 0x31, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x18, 0x08, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x0b, 0x74, 0x6f, 0x70, 0x31, 0x52, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x73, 0x12, 0x4c, 0x0a, 0x0f, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, + 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, - 0x74, 0x65, 0x6d, 0x52, 0x0b, 0x74, 0x6f, 0x70, 0x31, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, - 0x12, 0x4c, 0x0a, 0x0f, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, - 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x0e, - 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x22, 0x94, - 0x02, 0x0a, 0x15, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77, - 0x61, 0x72, 0x64, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x74, 0x65, - 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, - 0x75, 0x70, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x63, 0x6f, 0x6e, 0x5f, - 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x63, 0x6f, 0x6e, 0x55, - 0x72, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x69, 0x0a, 0x16, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, - 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x75, 0x65, 0x6c, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x66, 0x75, 0x65, 0x6c, 0x12, 0x22, 0x0a, 0x0d, - 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, - 0x22, 0xe7, 0x02, 0x0a, 0x17, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x50, - 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x12, 0x1b, 0x0a, 0x09, - 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, - 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, - 0x25, 0x0a, 0x0e, 0x66, 0x75, 0x65, 0x6c, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66, 0x75, 0x65, 0x6c, 0x54, 0x68, 0x72, - 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, - 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x69, - 0x67, 0x6e, 0x69, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x6c, 0x61, - 0x75, 0x6e, 0x63, 0x68, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0a, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1e, 0x0a, 0x0b, - 0x72, 0x65, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x09, 0x72, 0x65, 0x73, 0x65, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0c, - 0x74, 0x6f, 0x70, 0x31, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x70, 0x31, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, - 0x0a, 0x0f, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, - 0x09, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x22, 0xaa, 0x05, 0x0a, 0x0f, 0x52, - 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x23, - 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x66, - 0x75, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x74, 0x46, 0x75, 0x65, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x75, 0x65, 0x6c, 0x5f, 0x74, - 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, - 0x66, 0x75, 0x65, 0x6c, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x69, 0x67, - 0x6e, 0x69, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x6c, 0x61, 0x75, - 0x6e, 0x63, 0x68, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0a, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x6c, - 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x64, 0x41, 0x74, 0x4d, + 0x74, 0x65, 0x6d, 0x52, 0x0e, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x73, 0x22, 0x94, 0x02, 0x0a, 0x15, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, + 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x12, 0x1f, 0x0a, + 0x0b, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x17, + 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x77, 0x61, 0x72, + 0x64, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, 0x2a, 0x0a, + 0x11, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, + 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, + 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x69, 0x63, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x61, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x61, 0x6e, 0x74, + 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x69, 0x0a, 0x16, 0x52, 0x6f, + 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, + 0x04, 0x66, 0x75, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x66, 0x75, 0x65, + 0x6c, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xe7, 0x02, 0x0a, 0x17, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, + 0x63, 0x6b, 0x65, 0x74, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4c, 0x61, 0x75, 0x6e, 0x63, + 0x68, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x14, + 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x75, 0x65, 0x6c, 0x5f, 0x74, 0x68, 0x72, + 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66, 0x75, + 0x65, 0x6c, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x69, + 0x67, 0x6e, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0b, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, + 0x20, 0x0a, 0x0c, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x73, 0x65, 0x74, 0x41, 0x74, 0x4d, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x73, 0x65, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x74, 0x6f, 0x70, 0x31, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x70, 0x31, 0x55, 0x73, 0x65, + 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x70, 0x31, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x67, - 0x6e, 0x69, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, - 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, - 0x47, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x18, - 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, - 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x0b, 0x6c, 0x61, 0x73, - 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x12, 0x51, 0x0a, 0x10, 0x70, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0e, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x50, 0x65, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x52, 0x0f, 0x70, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x12, 0x5a, 0x0a, 0x15, 0x63, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x79, 0x61, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x67, + 0x6e, 0x69, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x22, + 0xaa, 0x05, 0x0a, 0x0f, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x74, 0x5f, 0x66, 0x75, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x46, 0x75, 0x65, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x66, + 0x75, 0x65, 0x6c, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66, 0x75, 0x65, 0x6c, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, + 0x6c, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x69, 0x67, + 0x6e, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x20, + 0x0a, 0x0c, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x41, 0x74, 0x4d, 0x73, + 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, + 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5f, + 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x73, + 0x65, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x74, 0x6f, 0x70, 0x31, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, + 0x70, 0x31, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x67, 0x6e, 0x69, + 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0d, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x25, 0x0a, + 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x47, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, - 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x14, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xed, 0x02, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, - 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x18, 0x02, + 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x47, 0x72, 0x61, 0x6e, 0x74, + 0x52, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x12, 0x51, 0x0a, + 0x10, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, + 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, + 0x65, 0x74, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x52, + 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x73, + 0x12, 0x5a, 0x0a, 0x15, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x14, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x43, + 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xed, 0x02, 0x0a, + 0x0e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, + 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x06, 0x6c, 0x65, 0x76, + 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, + 0x63, 0x6b, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, + 0x73, 0x12, 0x34, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x27, 0x0a, + 0x0f, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, + 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, + 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0d, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x4d, 0x73, 0x12, 0x2c, + 0x0a, 0x12, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x61, + 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x62, 0x72, 0x6f, 0x61, + 0x64, 0x63, 0x61, 0x73, 0x74, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x4d, 0x73, 0x12, 0x2e, 0x0a, 0x13, + 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x72, 0x65, 0x77, 0x61, 0x72, + 0x64, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0xd2, 0x01, 0x0a, + 0x16, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x46, + 0x75, 0x65, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, + 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x67, 0x69, 0x66, + 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x67, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x25, 0x0a, 0x0e, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x5f, 0x70, 0x70, + 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, + 0x69, 0x65, 0x72, 0x50, 0x70, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x69, 0x78, 0x65, 0x64, 0x5f, + 0x66, 0x75, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x66, 0x69, 0x78, 0x65, + 0x64, 0x46, 0x75, 0x65, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, + 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, + 0x64, 0x22, 0xee, 0x04, 0x0a, 0x15, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, + 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, + 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, + 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x65, 0x6c, 0x5f, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x75, + 0x65, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x61, 0x75, 0x6e, + 0x63, 0x68, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0d, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x4d, 0x73, + 0x12, 0x2b, 0x0a, 0x11, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x62, 0x72, 0x6f, + 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x27, 0x0a, + 0x0f, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, + 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, + 0x61, 0x73, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x10, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x44, 0x65, 0x6c, + 0x61, 0x79, 0x4d, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x73, + 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x11, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x12, 0x36, 0x0a, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x12, 0x34, 0x0a, 0x05, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, - 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, - 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x62, 0x72, 0x6f, 0x61, - 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0e, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x53, 0x63, 0x6f, 0x70, - 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x5f, 0x64, 0x65, 0x6c, 0x61, - 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6c, 0x61, 0x75, 0x6e, - 0x63, 0x68, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x4d, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x72, 0x6f, - 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x73, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, - 0x44, 0x65, 0x6c, 0x61, 0x79, 0x4d, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x72, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x5f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x63, - 0x6b, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0xd2, 0x01, 0x0a, 0x16, 0x52, 0x6f, 0x6f, 0x6d, - 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x46, 0x75, 0x65, 0x6c, 0x52, 0x75, - 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, - 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, - 0x66, 0x74, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x67, 0x69, - 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x75, - 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x50, 0x70, - 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x66, 0x75, 0x65, 0x6c, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x66, 0x69, 0x78, 0x65, 0x64, 0x46, 0x75, 0x65, 0x6c, - 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x22, 0xee, 0x04, 0x0a, - 0x15, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, - 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, - 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x65, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x75, 0x65, 0x6c, 0x53, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x5f, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6c, 0x61, - 0x75, 0x6e, 0x63, 0x68, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x4d, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x62, - 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, - 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x62, 0x72, 0x6f, 0x61, - 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0e, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x53, 0x63, 0x6f, 0x70, - 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x64, - 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x62, - 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x4d, 0x73, 0x12, - 0x2e, 0x0a, 0x13, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, - 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x72, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, - 0x36, 0x0a, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, - 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x12, 0x4d, 0x0a, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x5f, - 0x66, 0x75, 0x65, 0x6c, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x46, - 0x75, 0x65, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x67, 0x69, 0x66, 0x74, 0x46, 0x75, 0x65, - 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x10, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, - 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x51, 0x0a, - 0x1f, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, - 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x22, 0x86, 0x01, 0x0a, 0x20, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, - 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, + 0x65, 0x76, 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x12, 0x4d, 0x0a, 0x0f, + 0x67, 0x69, 0x66, 0x74, 0x5f, 0x66, 0x75, 0x65, 0x6c, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, + 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, + 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, + 0x47, 0x69, 0x66, 0x74, 0x46, 0x75, 0x65, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x67, 0x69, + 0x66, 0x74, 0x46, 0x75, 0x65, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, + 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, + 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x4d, 0x73, 0x22, 0x51, 0x0a, 0x1f, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, + 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x86, 0x01, 0x0a, 0x20, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, + 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x06, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xad, + 0x01, 0x0a, 0x22, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, + 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x3c, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, - 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xad, 0x01, 0x0a, 0x22, 0x41, 0x64, - 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, - 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x12, 0x3c, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, - 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x07, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x89, 0x01, 0x0a, 0x23, 0x41, 0x64, - 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, - 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3c, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, - 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, - 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, - 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xcb, 0x01, 0x0a, 0x13, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, - 0x6f, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x32, 0x0a, - 0x15, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x5f, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x05, 0x52, 0x13, 0x63, 0x61, - 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x61, - 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x05, 0x52, 0x11, - 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x73, 0x65, 0x61, - 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x64, - 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, - 0x74, 0x4d, 0x73, 0x22, 0x4f, 0x0a, 0x1d, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x52, - 0x6f, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x22, 0x82, 0x01, 0x0a, 0x1e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, - 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x89, + 0x01, 0x0a, 0x23, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, + 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, + 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, + 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xcb, 0x01, 0x0a, 0x13, 0x41, + 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x5f, + 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x05, 0x52, 0x13, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x61, 0x74, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, + 0x64, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x05, 0x52, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x53, 0x65, 0x61, 0x74, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x10, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x65, 0x61, 0x74, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x4f, 0x0a, 0x1d, 0x41, 0x64, 0x6d, 0x69, + 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x82, 0x01, 0x0a, 0x1e, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x06, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, + 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xb0, + 0x01, 0x0a, 0x20, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, + 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x12, 0x2e, 0x0a, 0x13, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x73, + 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x05, + 0x52, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x73, + 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x10, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x22, 0x85, 0x01, 0x0a, 0x21, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xb0, 0x01, 0x0a, 0x20, 0x41, 0x64, - 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x65, 0x61, + 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x69, 0x0a, 0x1e, 0x41, 0x64, 0x6d, + 0x69, 0x6e, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, + 0x0a, 0x0e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x73, 0x22, 0x95, 0x01, 0x0a, 0x1e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x48, 0x75, + 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x61, + 0x78, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x2a, 0x0a, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x6f, 0x77, 0x6e, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x61, 0x6c, 0x6c, + 0x6f, 0x77, 0x65, 0x64, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, 0xd8, 0x0a, 0x0a, + 0x19, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, + 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, + 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, + 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, + 0x39, 0x0a, 0x19, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, + 0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x16, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, + 0x6d, 0x4d, 0x61, 0x78, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x31, 0x0a, 0x15, 0x72, 0x6f, + 0x6f, 0x6d, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x6f, 0x6e, 0x6c, + 0x69, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x72, 0x6f, 0x6f, 0x6d, 0x46, + 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x6f, 0x70, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x29, 0x0a, + 0x11, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x79, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, + 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x53, + 0x74, 0x61, 0x79, 0x4d, 0x69, 0x6e, 0x4d, 0x73, 0x12, 0x29, 0x0a, 0x11, 0x72, 0x6f, 0x62, 0x6f, + 0x74, 0x5f, 0x73, 0x74, 0x61, 0x79, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x79, 0x4d, 0x61, + 0x78, 0x4d, 0x73, 0x12, 0x2f, 0x0a, 0x14, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x72, 0x65, 0x70, + 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x11, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x4d, + 0x69, 0x6e, 0x4d, 0x73, 0x12, 0x2f, 0x0a, 0x14, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x72, 0x65, + 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x11, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, + 0x4d, 0x61, 0x78, 0x4d, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x5f, + 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x6d, 0x73, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x47, 0x69, + 0x66, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73, 0x12, 0x19, 0x0a, 0x08, + 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, + 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x75, 0x63, 0x6b, 0x79, + 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x0c, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x49, 0x64, 0x73, 0x12, 0x2f, 0x0a, + 0x14, 0x73, 0x75, 0x70, 0x65, 0x72, 0x5f, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x67, 0x69, 0x66, + 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x73, 0x75, 0x70, + 0x65, 0x72, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x49, 0x64, 0x73, 0x12, 0x26, + 0x0a, 0x0f, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x63, 0x6f, 0x6d, 0x62, 0x6f, 0x5f, 0x6d, 0x69, + 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x43, 0x6f, + 0x6d, 0x62, 0x6f, 0x4d, 0x69, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, + 0x63, 0x6f, 0x6d, 0x62, 0x6f, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0d, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x43, 0x6f, 0x6d, 0x62, 0x6f, 0x4d, 0x61, 0x78, 0x12, 0x2b, + 0x0a, 0x12, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x70, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x69, + 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6c, 0x75, 0x63, 0x6b, + 0x79, 0x50, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x69, 0x6e, 0x4d, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x6c, + 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x70, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6d, + 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x50, 0x61, + 0x75, 0x73, 0x65, 0x4d, 0x61, 0x78, 0x4d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x61, 0x78, 0x5f, + 0x67, 0x69, 0x66, 0x74, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x18, 0x11, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x47, 0x69, 0x66, 0x74, 0x53, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x73, 0x12, 0x52, 0x0a, 0x0d, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x70, 0x6f, + 0x6f, 0x6c, 0x73, 0x18, 0x12, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x48, + 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x79, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, + 0x79, 0x50, 0x6f, 0x6f, 0x6c, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x13, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x10, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x3c, 0x0a, + 0x1b, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x16, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x17, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x69, 0x6e, 0x4d, 0x73, 0x12, 0x3c, 0x0a, 0x1b, 0x6e, + 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x76, 0x61, 0x6c, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x73, 0x18, 0x17, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x17, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x61, 0x78, 0x4d, 0x73, 0x12, 0x33, 0x0a, 0x16, 0x72, 0x6f, 0x6f, + 0x6d, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6f, 0x6e, 0x6c, + 0x69, 0x6e, 0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x72, 0x6f, 0x6f, 0x6d, 0x54, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x4d, 0x69, 0x6e, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x33, + 0x0a, 0x16, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6d, 0x61, + 0x78, 0x5f, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x19, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, + 0x72, 0x6f, 0x6f, 0x6d, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4d, 0x61, 0x78, 0x4f, 0x6e, 0x6c, + 0x69, 0x6e, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x6f, + 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x1a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, + 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, + 0x32, 0x0a, 0x15, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x45, 0x6e, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x12, 0x52, 0x0a, 0x0d, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x72, + 0x75, 0x6c, 0x65, 0x73, 0x18, 0x1c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x22, 0x55, 0x0a, 0x23, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x47, 0x65, 0x74, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x2e, - 0x0a, 0x13, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x05, 0x52, 0x11, 0x61, 0x6c, 0x6c, - 0x6f, 0x77, 0x65, 0x64, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x2c, - 0x0a, 0x12, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x64, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x85, 0x01, 0x0a, - 0x21, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, - 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, - 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, - 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, - 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x69, 0x0a, 0x1e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x48, 0x75, 0x6d, - 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, - 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x6f, 0x62, - 0x6f, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x03, 0x52, 0x0c, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, - 0x95, 0x01, 0x0a, 0x1e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, - 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x75, - 0x6c, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, - 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, - 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x6f, 0x6f, - 0x6d, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6d, - 0x61, 0x78, 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x61, - 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x4f, - 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, 0xd8, 0x0a, 0x0a, 0x19, 0x41, 0x64, 0x6d, 0x69, - 0x6e, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, - 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x39, 0x0a, 0x19, 0x63, 0x61, - 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x6d, 0x61, 0x78, - 0x5f, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x16, 0x63, - 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x4d, 0x61, 0x78, 0x4f, - 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x31, 0x0a, 0x15, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x66, 0x75, - 0x6c, 0x6c, 0x5f, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x72, 0x6f, 0x6f, 0x6d, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, - 0x6f, 0x70, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x29, 0x0a, 0x11, 0x72, 0x6f, 0x62, 0x6f, - 0x74, 0x5f, 0x73, 0x74, 0x61, 0x79, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x79, 0x4d, 0x69, - 0x6e, 0x4d, 0x73, 0x12, 0x29, 0x0a, 0x11, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x73, 0x74, 0x61, - 0x79, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, - 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x79, 0x4d, 0x61, 0x78, 0x4d, 0x73, 0x12, 0x2f, - 0x0a, 0x14, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, - 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x72, 0x6f, - 0x62, 0x6f, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x4d, 0x69, 0x6e, 0x4d, 0x73, 0x12, - 0x2f, 0x0a, 0x14, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, - 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x72, - 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x4d, 0x61, 0x78, 0x4d, 0x73, - 0x12, 0x35, 0x0a, 0x17, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x14, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x69, 0x66, 0x74, 0x5f, - 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x67, 0x69, 0x66, 0x74, 0x49, - 0x64, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x67, 0x69, 0x66, 0x74, - 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x75, 0x63, 0x6b, - 0x79, 0x47, 0x69, 0x66, 0x74, 0x49, 0x64, 0x73, 0x12, 0x2f, 0x0a, 0x14, 0x73, 0x75, 0x70, 0x65, - 0x72, 0x5f, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x73, - 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x73, 0x75, 0x70, 0x65, 0x72, 0x4c, 0x75, 0x63, - 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x49, 0x64, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x75, 0x63, - 0x6b, 0x79, 0x5f, 0x63, 0x6f, 0x6d, 0x62, 0x6f, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x0d, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0d, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x43, 0x6f, 0x6d, 0x62, 0x6f, 0x4d, 0x69, - 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x63, 0x6f, 0x6d, 0x62, 0x6f, - 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6c, 0x75, 0x63, 0x6b, - 0x79, 0x43, 0x6f, 0x6d, 0x62, 0x6f, 0x4d, 0x61, 0x78, 0x12, 0x2b, 0x0a, 0x12, 0x6c, 0x75, 0x63, - 0x6b, 0x79, 0x5f, 0x70, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x73, 0x18, - 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x50, 0x61, 0x75, 0x73, - 0x65, 0x4d, 0x69, 0x6e, 0x4d, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, - 0x70, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x73, 0x18, 0x10, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0f, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x50, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x61, - 0x78, 0x4d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x61, 0x78, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, - 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6d, - 0x61, 0x78, 0x47, 0x69, 0x66, 0x74, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x12, 0x52, 0x0a, - 0x0d, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x73, 0x18, 0x12, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, - 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, - 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x50, - 0x6f, 0x6f, 0x6c, 0x52, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x50, 0x6f, 0x6f, 0x6c, - 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, - 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x13, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, - 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, - 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, - 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x3c, 0x0a, 0x1b, 0x6e, 0x6f, 0x72, 0x6d, - 0x61, 0x6c, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, - 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x16, 0x20, 0x01, 0x28, 0x03, 0x52, 0x17, 0x6e, - 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, - 0x6c, 0x4d, 0x69, 0x6e, 0x4d, 0x73, 0x12, 0x3c, 0x0a, 0x1b, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, - 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x6d, - 0x61, 0x78, 0x5f, 0x6d, 0x73, 0x18, 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, 0x17, 0x6e, 0x6f, 0x72, - 0x6d, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, - 0x61, 0x78, 0x4d, 0x73, 0x12, 0x33, 0x0a, 0x16, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x18, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x72, 0x6f, 0x6f, 0x6d, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x4d, 0x69, 0x6e, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x33, 0x0a, 0x16, 0x72, 0x6f, 0x6f, - 0x6d, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6f, 0x6e, 0x6c, - 0x69, 0x6e, 0x65, 0x18, 0x19, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x72, 0x6f, 0x6f, 0x6d, 0x54, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x4d, 0x61, 0x78, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x2a, - 0x0a, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x73, 0x18, 0x1a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, - 0x65, 0x64, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x65, 0x6e, 0x61, 0x62, - 0x6c, 0x65, 0x64, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x52, - 0x0a, 0x0d, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, - 0x1c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x48, 0x75, 0x6d, 0x61, 0x6e, - 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x75, 0x6c, - 0x65, 0x73, 0x22, 0x55, 0x0a, 0x23, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x48, 0x75, - 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, - 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x8e, 0x01, 0x0a, 0x24, 0x41, 0x64, - 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, - 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x40, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, - 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, - 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xb5, 0x01, 0x0a, 0x26, 0x41, - 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, - 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x40, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x48, 0x75, 0x6d, 0x61, 0x6e, + 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x8e, + 0x01, 0x0a, 0x24, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x61, 0x64, 0x6d, 0x69, 0x6e, - 0x49, 0x64, 0x22, 0x91, 0x01, 0x0a, 0x27, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, - 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, - 0x64, 0x6d, 0x69, 0x6e, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, - 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, - 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xea, 0x01, 0x0a, 0x10, 0x41, 0x64, 0x6d, 0x69, 0x6e, - 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x72, - 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, - 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x68, 0x6f, - 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x6f, 0x6f, - 0x6d, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, - 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77, 0x6e, - 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x1b, - 0x0a, 0x09, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x22, 0xfb, 0x03, 0x0a, 0x0c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, - 0x6d, 0x50, 0x69, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x02, 0x69, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, - 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, - 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x70, 0x69, 0x6e, - 0x6e, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0a, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, - 0x26, 0x0a, 0x0f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, - 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, - 0x6c, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, - 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x15, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, - 0x6c, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, 0x64, - 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, - 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0c, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, - 0x73, 0x12, 0x33, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x48, 0x75, 0x6d, + 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, + 0xb5, 0x01, 0x0a, 0x26, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, + 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x40, 0x0a, 0x06, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x91, 0x01, 0x0a, 0x27, 0x41, 0x64, 0x6d, 0x69, + 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, + 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, + 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xea, 0x01, 0x0a, 0x10, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, - 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x69, 0x6e, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x69, 0x6e, 0x54, 0x79, 0x70, - 0x65, 0x22, 0xf4, 0x01, 0x0a, 0x18, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, - 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x12, - 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, - 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, - 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, - 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x19, 0x0a, - 0x08, 0x70, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x70, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0x88, 0x01, 0x0a, 0x19, 0x41, 0x64, 0x6d, - 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x69, 0x6e, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, - 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, - 0x6e, 0x52, 0x04, 0x70, 0x69, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x24, 0x0a, - 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, - 0x65, 0x4d, 0x73, 0x22, 0x9d, 0x02, 0x0a, 0x19, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, - 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, - 0x61, 0x79, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x64, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x79, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x61, 0x64, 0x6d, 0x69, 0x6e, - 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, - 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, - 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, - 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, - 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x69, 0x6e, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x69, 0x6e, 0x54, - 0x79, 0x70, 0x65, 0x22, 0x71, 0x0a, 0x1a, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2d, 0x0a, 0x03, 0x70, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, - 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x52, 0x03, 0x70, 0x69, 0x6e, - 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, - 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x7d, 0x0a, 0x19, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, - 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x70, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x05, 0x70, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x64, - 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x61, 0x64, - 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x71, 0x0a, 0x1a, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x61, - 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x03, 0x70, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x52, 0x03, 0x70, - 0x69, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x9c, 0x04, 0x0a, 0x16, 0x41, 0x64, 0x6d, - 0x69, 0x6e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x52, - 0x75, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x73, 0x12, 0x24, - 0x0a, 0x0e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, - 0x74, 0x49, 0x64, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x5f, 0x67, - 0x69, 0x66, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x6d, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x47, 0x69, 0x66, - 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6c, - 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x63, 0x6f, 0x6d, 0x62, 0x6f, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x43, 0x6f, 0x6d, 0x62, 0x6f, - 0x4d, 0x69, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x63, 0x6f, 0x6d, - 0x62, 0x6f, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6c, 0x75, - 0x63, 0x6b, 0x79, 0x43, 0x6f, 0x6d, 0x62, 0x6f, 0x4d, 0x61, 0x78, 0x12, 0x2b, 0x0a, 0x12, 0x6c, - 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x70, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, - 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x50, 0x61, - 0x75, 0x73, 0x65, 0x4d, 0x69, 0x6e, 0x4d, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x6c, 0x75, 0x63, 0x6b, - 0x79, 0x5f, 0x70, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x73, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x50, 0x61, 0x75, 0x73, 0x65, - 0x4d, 0x61, 0x78, 0x4d, 0x73, 0x12, 0x29, 0x0a, 0x11, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x73, - 0x74, 0x61, 0x79, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x79, 0x4d, 0x69, 0x6e, 0x4d, 0x73, - 0x12, 0x29, 0x0a, 0x11, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x79, 0x5f, 0x6d, - 0x61, 0x78, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x6f, 0x62, - 0x6f, 0x74, 0x53, 0x74, 0x61, 0x79, 0x4d, 0x61, 0x78, 0x4d, 0x73, 0x12, 0x2f, 0x0a, 0x14, 0x72, - 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x6d, 0x69, 0x6e, - 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x72, 0x6f, 0x62, 0x6f, 0x74, - 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x4d, 0x69, 0x6e, 0x4d, 0x73, 0x12, 0x2f, 0x0a, 0x14, - 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x6d, 0x61, - 0x78, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x72, 0x6f, 0x62, 0x6f, - 0x74, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x4d, 0x61, 0x78, 0x4d, 0x73, 0x12, 0x28, 0x0a, - 0x10, 0x6d, 0x61, 0x78, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, - 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x47, 0x69, 0x66, 0x74, - 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x22, 0xbc, 0x04, 0x0a, 0x0e, 0x41, 0x64, 0x6d, 0x69, - 0x6e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, - 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, - 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x22, - 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x53, 0x68, 0x6f, 0x72, 0x74, - 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x76, 0x65, - 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x76, - 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, - 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, - 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x6f, 0x77, 0x6e, - 0x65, 0x72, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x52, 0x6f, 0x62, - 0x6f, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x6f, 0x62, 0x6f, - 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x03, - 0x52, 0x0c, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x42, - 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, - 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, - 0x47, 0x69, 0x66, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x52, 0x75, - 0x6c, 0x65, 0x12, 0x2d, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, - 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, - 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, - 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x63, 0x74, - 0x69, 0x76, 0x65, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, 0x6f, 0x62, - 0x6f, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x61, 0x74, 0x5f, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x61, - 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x95, 0x01, 0x0a, 0x1a, 0x41, 0x64, 0x6d, 0x69, 0x6e, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, - 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x8e, - 0x01, 0x0a, 0x1b, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x62, 0x6f, - 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, - 0x0a, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, - 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x05, 0x72, 0x6f, - 0x6f, 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, - 0x9b, 0x04, 0x0a, 0x1b, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, - 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, - 0x2d, 0x0a, 0x13, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x6f, 0x77, - 0x6e, 0x65, 0x72, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x37, - 0x0a, 0x18, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x62, 0x6f, - 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, - 0x52, 0x15, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x69, 0x6e, 0x5f, 0x72, - 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0d, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x52, 0x6f, 0x62, - 0x6f, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x6d, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x61, 0x76, 0x61, - 0x74, 0x61, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x41, - 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, - 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, - 0x64, 0x12, 0x42, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, - 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, - 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x08, 0x67, 0x69, 0x66, - 0x74, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, - 0x12, 0x2c, 0x0a, 0x12, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, - 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x77, - 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, - 0x0a, 0x0a, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x77, 0x0a, - 0x1c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x62, 0x6f, - 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, - 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, - 0x6e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, - 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, - 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x83, 0x01, 0x0a, 0x1e, 0x41, 0x64, 0x6d, 0x69, 0x6e, - 0x53, 0x65, 0x74, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, - 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x07, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x7a, 0x0a, 0x1f, - 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, - 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x31, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, - 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x04, 0x72, 0x6f, - 0x6f, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xc3, 0x01, 0x0a, 0x22, 0x41, 0x64, 0x6d, - 0x69, 0x6e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, - 0x28, 0x0a, 0x10, 0x6f, 0x6c, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x6c, 0x64, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x65, 0x77, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x65, 0x77, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x9e, - 0x01, 0x0a, 0x23, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x4f, 0x77, - 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x6c, - 0x69, 0x73, 0x74, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0f, 0x72, 0x6f, 0x6f, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x69, - 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x6e, 0x61, 0x70, 0x73, - 0x68, 0x6f, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x72, 0x6f, 0x6f, 0x6d, - 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, - 0x72, 0x0a, 0x25, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x41, 0x76, - 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, - 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x73, 0x22, 0xa8, 0x01, 0x0a, 0x26, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x46, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x6f, 0x6d, - 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, - 0x0a, 0x12, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x10, 0x61, 0x76, 0x61, 0x69, - 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x2a, 0x0a, 0x11, - 0x6f, 0x63, 0x63, 0x75, 0x70, 0x69, 0x65, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x63, 0x63, 0x75, 0x70, 0x69, 0x65, - 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x93, - 0x01, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, - 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, + 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, + 0x6d, 0x5f, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x2a, 0x0a, + 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, + 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, + 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, + 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xfb, 0x03, 0x0a, 0x0c, 0x41, 0x64, 0x6d, + 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, + 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, + 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x20, + 0x0a, 0x0c, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, - 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, - 0x41, 0x74, 0x4d, 0x73, 0x22, 0xf8, 0x06, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, - 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x22, - 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, - 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x74, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x64, 0x12, 0x35, 0x0a, 0x09, 0x6d, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x73, 0x18, 0x07, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, - 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x08, - 0x6d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x0c, 0x6f, 0x6e, 0x6c, 0x69, - 0x6e, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x52, 0x0b, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, - 0x73, 0x65, 0x72, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0c, 0x61, 0x64, - 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x62, 0x61, - 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x03, - 0x52, 0x0a, 0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x22, 0x0a, 0x0d, - 0x6d, 0x75, 0x74, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0b, 0x20, - 0x03, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x75, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, - 0x12, 0x34, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x72, 0x61, 0x6e, 0x6b, 0x18, 0x0c, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x6e, 0x6b, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x08, 0x67, 0x69, - 0x66, 0x74, 0x52, 0x61, 0x6e, 0x6b, 0x12, 0x43, 0x0a, 0x08, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x65, - 0x78, 0x74, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, - 0x70, 0x73, 0x68, 0x6f, 0x74, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x45, 0x78, 0x74, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x45, 0x78, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, - 0x65, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x68, 0x65, 0x61, 0x74, 0x12, - 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x68, 0x6f, - 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x6f, 0x6f, - 0x6d, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, - 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x12, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x13, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x06, - 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, - 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x72, 0x6f, - 0x63, 0x6b, 0x65, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x62, 0x61, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x6e, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x09, 0x62, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, - 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x16, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x1a, 0x3a, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, 0x45, 0x78, 0x74, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, - 0xe5, 0x01, 0x0a, 0x13, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, - 0x6e, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x61, 0x63, 0x6b, 0x67, - 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, - 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x75, - 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x55, - 0x72, 0x6c, 0x12, 0x2b, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, - 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, - 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x81, 0x01, 0x0a, 0x19, 0x53, 0x61, 0x76, 0x65, - 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1b, - 0x0a, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x55, 0x72, 0x6c, 0x22, 0x86, 0x01, 0x0a, 0x1a, - 0x53, 0x61, 0x76, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, - 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0a, 0x62, 0x61, - 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x6d, 0x61, - 0x67, 0x65, 0x52, 0x0a, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x24, - 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, - 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x8b, 0x01, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, - 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, + 0x41, 0x74, 0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, + 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2d, 0x0a, 0x13, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x15, 0x63, + 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x63, 0x61, 0x6e, 0x63, + 0x65, 0x6c, 0x6c, 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x22, + 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x5f, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x33, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, + 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x70, + 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, + 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0xf4, 0x01, 0x0a, 0x18, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, - 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x22, 0xc1, 0x01, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, - 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, - 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x0b, 0x62, 0x61, 0x63, - 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x65, 0x6c, 0x65, - 0x63, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, - 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x73, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x65, 0x64, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x55, 0x72, 0x6c, - 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, - 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x6f, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, - 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x62, 0x61, 0x63, 0x6b, 0x67, - 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x22, 0xc6, 0x01, 0x0a, 0x19, 0x53, 0x65, 0x74, 0x52, - 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, - 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, - 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x42, 0x0a, 0x0a, - 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, - 0x6d, 0x61, 0x67, 0x65, 0x52, 0x0a, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, - 0x22, 0xa2, 0x03, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, + 0x65, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, + 0x53, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, + 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, + 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0x88, 0x01, + 0x0a, 0x19, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x50, + 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70, + 0x69, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, + 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x52, 0x04, 0x70, 0x69, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x9d, 0x02, 0x0a, 0x19, 0x41, 0x64, 0x6d, + 0x69, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, - 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x61, 0x74, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, - 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x6d, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, - 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x41, 0x76, 0x61, - 0x74, 0x61, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x72, - 0x6f, 0x6f, 0x6d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, - 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x53, 0x68, 0x6f, 0x72, 0x74, - 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, - 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, - 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x12, - 0x24, 0x0a, 0x0e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, 0x7b, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, - 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, - 0x6f, 0x6d, 0x22, 0xa8, 0x02, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, - 0x6d, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, - 0x20, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, - 0x01, 0x12, 0x24, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x41, 0x76, - 0x61, 0x74, 0x61, 0x72, 0x88, 0x01, 0x01, 0x12, 0x2e, 0x0a, 0x10, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, - 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x02, 0x52, 0x0f, 0x72, 0x6f, 0x6f, 0x6d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x0a, 0x73, 0x65, 0x61, 0x74, 0x5f, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x48, 0x03, 0x52, 0x09, 0x73, - 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, - 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x72, 0x6f, - 0x6f, 0x6d, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x72, 0x6f, - 0x6f, 0x6d, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0d, - 0x0a, 0x0b, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x82, 0x01, - 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, - 0x6f, 0x6d, 0x22, 0xc7, 0x02, 0x0a, 0x18, 0x52, 0x6f, 0x6f, 0x6d, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x56, 0x65, 0x68, 0x69, 0x63, 0x6c, 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, - 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, - 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x64, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, - 0x77, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x65, - 0x76, 0x69, 0x65, 0x77, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6e, 0x69, 0x6d, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4a, 0x73, 0x6f, - 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x74, 0x69, 0x74, - 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, - 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xc2, 0x01, 0x0a, - 0x16, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, - 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, - 0x61, 0x74, 0x61, 0x72, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, - 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, - 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x22, 0xb7, 0x03, 0x0a, 0x0f, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, - 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, - 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x4c, 0x0a, 0x0d, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x76, - 0x65, 0x68, 0x69, 0x63, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, - 0x6d, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x56, 0x65, 0x68, 0x69, 0x63, 0x6c, 0x65, 0x53, 0x6e, 0x61, - 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x0c, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x56, 0x65, 0x68, 0x69, - 0x63, 0x6c, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x61, - 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x26, 0x0a, - 0x0f, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x69, - 0x73, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x61, - 0x63, 0x74, 0x6f, 0x72, 0x49, 0x73, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x12, 0x59, 0x0a, 0x15, 0x61, - 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x70, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, - 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, - 0x65, 0x52, 0x13, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x22, 0xa6, 0x01, 0x0a, 0x10, - 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, - 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, - 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, - 0x73, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, - 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x46, 0x0a, 0x14, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x72, - 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0xab, 0x01, 0x0a, - 0x15, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, - 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2b, 0x0a, 0x04, - 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, - 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, - 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x42, 0x0a, 0x10, 0x4c, 0x65, - 0x61, 0x76, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, - 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x7a, - 0x0a, 0x11, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, - 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x5a, 0x0a, 0x10, 0x43, 0x6c, - 0x6f, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, - 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x16, - 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x7a, 0x0a, 0x11, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, - 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, - 0x6f, 0x6d, 0x22, 0x8f, 0x05, 0x0a, 0x11, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, - 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, - 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x53, 0x68, - 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, - 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, - 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, - 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x63, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x72, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, - 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2b, 0x0a, 0x12, 0x63, 0x6c, 0x6f, 0x73, 0x65, - 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, 0x6d, - 0x69, 0x6e, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x62, - 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x11, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, - 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, - 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6c, 0x6f, - 0x73, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, 0x61, 0x74, 0x18, - 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x68, 0x65, 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6f, - 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x0b, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, - 0x0a, 0x0a, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e, 0x0a, - 0x13, 0x6f, 0x63, 0x63, 0x75, 0x70, 0x69, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x6f, 0x63, 0x63, 0x75, - 0x70, 0x69, 0x65, 0x64, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, - 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x22, 0x0a, 0x0d, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x12, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, - 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, - 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x4d, 0x73, 0x22, 0xba, 0x02, 0x0a, 0x15, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, - 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x12, - 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, - 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, - 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, - 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, - 0x07, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x62, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x64, - 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, - 0x73, 0x6f, 0x72, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, - 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x22, 0x8c, 0x01, 0x0a, 0x16, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x05, - 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, - 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x72, - 0x6f, 0x6f, 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, - 0x22, 0x5e, 0x0a, 0x13, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, - 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, - 0x22, 0x72, 0x0a, 0x14, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, - 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, - 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x24, - 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, - 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x8c, 0x04, 0x0a, 0x16, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, - 0x19, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, - 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, - 0x08, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b, - 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x02, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x03, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x04, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x11, 0x76, 0x69, 0x73, - 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x03, 0x48, 0x05, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, - 0x6f, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, - 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x07, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x64, 0x6d, 0x69, - 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, - 0x6d, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x12, 0x6f, 0x77, 0x6e, 0x65, 0x72, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0b, 0x20, - 0x01, 0x28, 0x09, 0x48, 0x06, 0x52, 0x10, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x74, - 0x69, 0x74, 0x6c, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, - 0x72, 0x6c, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x5f, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x76, 0x69, 0x73, 0x69, 0x62, - 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x42, 0x15, 0x0a, 0x13, - 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x22, 0x80, 0x01, 0x0a, 0x17, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, + 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, + 0x16, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x79, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x79, 0x73, 0x12, 0x19, 0x0a, 0x08, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x70, 0x69, 0x6e, 0x6e, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x70, + 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x19, 0x0a, + 0x08, 0x70, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x70, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0x71, 0x0a, 0x1a, 0x41, 0x64, 0x6d, 0x69, + 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x03, 0x70, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, - 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x82, 0x01, 0x0a, 0x16, 0x41, 0x64, 0x6d, 0x69, 0x6e, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x07, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x80, 0x01, 0x0a, 0x17, - 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, - 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, - 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0xb4, - 0x01, 0x0a, 0x0c, 0x4d, 0x69, 0x63, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, - 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x5b, 0x0a, 0x16, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, - 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, - 0x14, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, - 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0xe5, 0x01, 0x0a, 0x0d, 0x4d, 0x69, 0x63, 0x55, 0x70, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x17, 0x0a, - 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, - 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, - 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, - 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x69, 0x63, 0x5f, 0x73, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x6d, 0x69, 0x63, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2e, 0x0a, - 0x13, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, - 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x73, 0x68, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x73, 0x22, 0xdb, 0x01, - 0x0a, 0x0e, 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x5b, - 0x0a, 0x16, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, - 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x14, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x91, 0x01, 0x0a, 0x0f, - 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x2f, - 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, - 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, - 0xe2, 0x01, 0x0a, 0x14, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, + 0x52, 0x03, 0x70, 0x69, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x7d, 0x0a, 0x19, 0x41, + 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, - 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x5b, 0x0a, 0x16, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, - 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x14, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x22, 0x7e, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x69, - 0x63, 0x53, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, - 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, - 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0xd5, 0x02, 0x0a, 0x1b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, - 0x4d, 0x69, 0x63, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, + 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x70, 0x69, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x70, 0x69, 0x6e, 0x49, 0x64, 0x12, + 0x19, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x07, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x71, 0x0a, 0x1a, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x03, 0x70, 0x69, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, + 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x50, + 0x69, 0x6e, 0x52, 0x03, 0x70, 0x69, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x9c, 0x04, + 0x0a, 0x16, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, + 0x47, 0x69, 0x66, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x69, 0x66, 0x74, + 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x67, 0x69, 0x66, 0x74, + 0x49, 0x64, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x67, 0x69, 0x66, + 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x75, 0x63, + 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x49, 0x64, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x6e, 0x6f, 0x72, + 0x6d, 0x61, 0x6c, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, + 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x6e, 0x6f, 0x72, 0x6d, + 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73, + 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x63, 0x6f, 0x6d, 0x62, 0x6f, 0x5f, + 0x6d, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6c, 0x75, 0x63, 0x6b, 0x79, + 0x43, 0x6f, 0x6d, 0x62, 0x6f, 0x4d, 0x69, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x75, 0x63, 0x6b, + 0x79, 0x5f, 0x63, 0x6f, 0x6d, 0x62, 0x6f, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0d, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x43, 0x6f, 0x6d, 0x62, 0x6f, 0x4d, 0x61, 0x78, + 0x12, 0x2b, 0x0a, 0x12, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x70, 0x61, 0x75, 0x73, 0x65, 0x5f, + 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6c, 0x75, + 0x63, 0x6b, 0x79, 0x50, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x69, 0x6e, 0x4d, 0x73, 0x12, 0x2b, 0x0a, + 0x12, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x70, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x61, 0x78, + 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6c, 0x75, 0x63, 0x6b, 0x79, + 0x50, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x61, 0x78, 0x4d, 0x73, 0x12, 0x29, 0x0a, 0x11, 0x72, 0x6f, + 0x62, 0x6f, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x79, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x73, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x79, + 0x4d, 0x69, 0x6e, 0x4d, 0x73, 0x12, 0x29, 0x0a, 0x11, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x73, + 0x74, 0x61, 0x79, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x79, 0x4d, 0x61, 0x78, 0x4d, 0x73, + 0x12, 0x2f, 0x0a, 0x14, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, + 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, + 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x4d, 0x69, 0x6e, 0x4d, + 0x73, 0x12, 0x2f, 0x0a, 0x14, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, + 0x63, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x11, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x4d, 0x61, 0x78, + 0x4d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x61, 0x78, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x73, + 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6d, 0x61, + 0x78, 0x47, 0x69, 0x66, 0x74, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x22, 0xbc, 0x04, 0x0a, + 0x0e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x12, + 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, + 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, + 0x6d, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x68, 0x6f, 0x72, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, + 0x53, 0x68, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, + 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2d, + 0x0a, 0x13, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x6f, 0x77, 0x6e, + 0x65, 0x72, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, + 0x0e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, + 0x09, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x73, 0x12, 0x42, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x72, 0x75, 0x6c, 0x65, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, + 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x62, 0x6f, + 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x08, 0x67, + 0x69, 0x66, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x2d, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0b, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, + 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2c, + 0x0a, 0x12, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x09, 0x73, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x95, 0x01, 0x0a, 0x1a, + 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, + 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, + 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, + 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x1b, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, + 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, + 0x6d, 0x52, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x24, + 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, + 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x9b, 0x04, 0x0a, 0x1b, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x69, - 0x63, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x69, 0x63, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, - 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, - 0x5b, 0x0a, 0x16, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, - 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x14, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x9e, 0x01, 0x0a, - 0x1c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x4d, 0x69, 0x63, 0x50, 0x75, 0x62, 0x6c, 0x69, - 0x73, 0x68, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, - 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x2f, 0x0a, 0x04, - 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, - 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x91, 0x01, - 0x0a, 0x13, 0x4d, 0x69, 0x63, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x6d, - 0x69, 0x63, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x69, 0x63, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, - 0x64, 0x22, 0xc5, 0x01, 0x0a, 0x14, 0x4d, 0x69, 0x63, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, - 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x2d, 0x0a, 0x13, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x72, 0x6f, + 0x62, 0x6f, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x10, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x18, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, + 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x15, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, + 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x26, 0x0a, 0x0f, + 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x6f, 0x62, 0x6f, + 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6d, + 0x61, 0x78, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, + 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x72, 0x6f, 0x6f, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, + 0x6d, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x72, 0x6f, 0x6f, 0x6d, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, + 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x42, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x72, + 0x75, 0x6c, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, + 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x52, 0x75, 0x6c, 0x65, + 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x64, + 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x61, 0x64, + 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x10, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, + 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x22, 0x77, 0x0a, 0x1c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, + 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, + 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x83, 0x01, 0x0a, 0x1e, + 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, + 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, + 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x49, + 0x64, 0x22, 0x7a, 0x0a, 0x1f, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x62, + 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, + 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, + 0x6d, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xc3, 0x01, + 0x0a, 0x22, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x4f, 0x77, 0x6e, + 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x6c, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x6f, 0x6c, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, + 0x0a, 0x10, 0x6e, 0x65, 0x77, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x65, 0x77, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x61, 0x64, 0x6d, 0x69, + 0x6e, 0x49, 0x64, 0x22, 0x9e, 0x01, 0x0a, 0x23, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x6e, + 0x61, 0x6d, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, + 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x72, + 0x6f, 0x6f, 0x6d, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x6f, 0x6f, 0x6d, 0x4c, 0x69, 0x73, 0x74, + 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, + 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0d, 0x72, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x12, 0x24, + 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, + 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x72, 0x0a, 0x25, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x46, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x6f, 0x6d, + 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, + 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, + 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, 0xa8, 0x01, 0x0a, 0x26, 0x41, 0x64, 0x6d, + 0x69, 0x6e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, + 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, + 0x10, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x63, 0x63, 0x75, 0x70, 0x69, 0x65, 0x64, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x63, + 0x63, 0x75, 0x70, 0x69, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x24, 0x0a, + 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, + 0x65, 0x4d, 0x73, 0x22, 0x93, 0x01, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x6e, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, + 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, + 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xf8, 0x06, 0x0a, 0x0c, 0x52, 0x6f, + 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, + 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, + 0x6d, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, + 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x74, 0x5f, 0x65, 0x6e, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x74, 0x45, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x35, 0x0a, 0x09, 0x6d, 0x69, 0x63, 0x5f, 0x73, 0x65, + 0x61, 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x74, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x52, 0x08, 0x6d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x73, 0x12, 0x3a, 0x0a, + 0x0c, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x08, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x52, 0x0b, 0x6f, 0x6e, + 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x64, 0x6d, + 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, + 0x03, 0x52, 0x0c, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, + 0x20, 0x0a, 0x0c, 0x62, 0x61, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, + 0x0a, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0a, 0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x75, 0x74, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x75, 0x74, 0x65, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x34, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x72, 0x61, + 0x6e, 0x6b, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x6e, 0x6b, 0x49, 0x74, 0x65, + 0x6d, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x52, 0x61, 0x6e, 0x6b, 0x12, 0x43, 0x0a, 0x08, 0x72, + 0x6f, 0x6f, 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, + 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x45, + 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x45, 0x78, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, + 0x68, 0x65, 0x61, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, + 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, + 0x6d, 0x5f, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x2a, 0x0a, + 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, + 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, + 0x6b, 0x65, 0x64, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, + 0x64, 0x12, 0x36, 0x0a, 0x06, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x52, 0x06, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x62, 0x61, 0x6e, + 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, + 0x6f, 0x6d, 0x42, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x09, 0x62, 0x61, 0x6e, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x16, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6f, 0x6e, 0x6c, + 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0x3a, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, + 0x45, 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x22, 0xe5, 0x01, 0x0a, 0x13, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, + 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x23, 0x0a, 0x0d, + 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0c, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, + 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x6d, + 0x61, 0x67, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, + 0x6d, 0x61, 0x67, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x2b, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x81, 0x01, 0x0a, + 0x19, 0x53, 0x61, 0x76, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, + 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, + 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, + 0x6d, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x75, 0x72, 0x6c, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x55, 0x72, 0x6c, + 0x22, 0x86, 0x01, 0x0a, 0x1a, 0x53, 0x61, 0x76, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, + 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x42, 0x0a, 0x0a, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, + 0x6e, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x0a, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, + 0x75, 0x6e, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x8b, 0x01, 0x0a, 0x1a, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, + 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, + 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, + 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, 0x69, 0x65, 0x77, 0x65, + 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xc1, 0x01, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x62, 0x61, 0x63, 0x6b, 0x67, + 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, + 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, + 0x52, 0x0b, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x36, 0x0a, + 0x17, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, + 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, + 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, + 0x6e, 0x64, 0x55, 0x72, 0x6c, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x6f, 0x0a, 0x18, 0x53, + 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, + 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, + 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x61, 0x63, 0x6b, 0x67, + 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x22, 0xc6, 0x01, 0x0a, + 0x19, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, + 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, - 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x2d, 0x0a, 0x13, 0x6d, 0x69, - 0x63, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, - 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x6d, 0x69, 0x63, 0x48, 0x65, 0x61, 0x72, - 0x74, 0x62, 0x65, 0x61, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xdc, 0x01, 0x0a, 0x11, 0x53, 0x65, - 0x74, 0x4d, 0x69, 0x63, 0x4d, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, - 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x12, 0x5b, 0x0a, 0x16, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x70, 0x72, - 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, - 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x52, 0x14, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, - 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x94, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x74, - 0x4d, 0x69, 0x63, 0x4d, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x2f, - 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, - 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, - 0x8b, 0x02, 0x0a, 0x14, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x54, 0x43, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, - 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, - 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x22, 0x0a, - 0x0d, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x4d, - 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x65, 0x76, - 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x65, 0x78, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x97, 0x01, - 0x0a, 0x15, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x54, 0x43, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x17, 0x0a, - 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, - 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, - 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, - 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x78, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x4d, 0x69, - 0x63, 0x53, 0x65, 0x61, 0x74, 0x4c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, - 0x6b, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, - 0x64, 0x22, 0x7f, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x4c, - 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, - 0x6f, 0x6d, 0x22, 0x61, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x43, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, + 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, + 0x6d, 0x12, 0x42, 0x0a, 0x0a, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, + 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, + 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x0a, 0x62, 0x61, 0x63, 0x6b, 0x67, + 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0xa2, 0x03, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x65, - 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x7f, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x43, 0x68, 0x61, 0x74, - 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, - 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x7c, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, - 0x6d, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, - 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, - 0x77, 0x6f, 0x72, 0x64, 0x22, 0x80, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, - 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x73, + 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x09, 0x73, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, + 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x2a, + 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, + 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, + 0x6f, 0x6d, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, + 0x6f, 0x6f, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, + 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, + 0x6f, 0x6d, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x6f, 0x6f, 0x6d, + 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x72, 0x6f, 0x6f, 0x6d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x68, 0x6f, 0x72, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, + 0x53, 0x68, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x6f, 0x77, 0x6e, 0x65, 0x72, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, + 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x72, + 0x6f, 0x6f, 0x6d, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x6f, 0x62, 0x6f, 0x74, + 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x6f, + 0x62, 0x6f, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, 0x7b, 0x0a, 0x12, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, - 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x85, 0x01, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x52, - 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, - 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, - 0x7d, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, + 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0xa8, 0x02, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x20, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x6d, 0x4e, + 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x61, + 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0a, 0x72, + 0x6f, 0x6f, 0x6d, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x88, 0x01, 0x01, 0x12, 0x2e, 0x0a, 0x10, + 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x0f, 0x72, 0x6f, 0x6f, 0x6d, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x0a, + 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, + 0x48, 0x03, 0x52, 0x09, 0x73, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x88, 0x01, 0x01, + 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0e, + 0x0a, 0x0c, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x42, 0x13, + 0x0a, 0x11, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x22, 0x82, 0x01, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, + 0x6d, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, + 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0xc7, 0x02, 0x0a, 0x18, 0x52, 0x6f, 0x6f, 0x6d, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x56, 0x65, 0x68, 0x69, 0x63, 0x6c, 0x65, 0x53, 0x6e, 0x61, 0x70, + 0x73, 0x68, 0x6f, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1b, + 0x0a, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, + 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, + 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, + 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6a, 0x73, + 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x22, 0x0a, + 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, + 0x73, 0x22, 0xc2, 0x01, 0x0a, 0x16, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x33, 0x0a, 0x16, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x13, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xe4, 0x03, 0x0a, 0x0f, 0x4a, 0x6f, 0x69, 0x6e, 0x52, + 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, + 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x4c, 0x0a, 0x0d, 0x65, 0x6e, + 0x74, 0x72, 0x79, 0x5f, 0x76, 0x65, 0x68, 0x69, 0x63, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x56, 0x65, 0x68, 0x69, 0x63, + 0x6c, 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x0c, 0x65, 0x6e, 0x74, 0x72, + 0x79, 0x56, 0x65, 0x68, 0x69, 0x63, 0x6c, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x63, 0x74, 0x6f, + 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0e, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, + 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x61, 0x63, 0x74, + 0x6f, 0x72, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x63, + 0x74, 0x6f, 0x72, 0x5f, 0x69, 0x73, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0c, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x49, 0x73, 0x52, 0x6f, 0x62, 0x6f, 0x74, + 0x12, 0x59, 0x0a, 0x15, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, + 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x13, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x44, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x6f, 0x64, 0x65, + 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x63, + 0x6f, 0x76, 0x65, 0x72, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x70, 0x72, 0x65, + 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x22, 0xa2, 0x01, + 0x0a, 0x18, 0x52, 0x6f, 0x6f, 0x6d, 0x45, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x56, + 0x69, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, + 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, + 0x76, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x19, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x5f, 0x65, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x72, 0x6f, 0x6f, 0x6d, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x22, 0xf4, 0x01, 0x0a, 0x10, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, - 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2b, 0x0a, + 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, - 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x7d, - 0x0a, 0x0f, 0x4d, 0x75, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, + 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x4c, 0x0a, 0x0d, 0x65, + 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x76, 0x69, 0x70, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x45, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, + 0x56, 0x69, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x0c, 0x65, 0x66, 0x66, + 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x56, 0x69, 0x70, 0x22, 0x46, 0x0a, 0x14, 0x52, 0x6f, 0x6f, + 0x6d, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x22, 0x79, 0x0a, - 0x10, 0x4d, 0x75, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, - 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, - 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x88, 0x01, 0x0a, 0x0f, 0x4b, 0x69, 0x63, - 0x6b, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, - 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x4d, 0x73, 0x22, 0xbe, 0x01, 0x0a, 0x10, 0x4b, 0x69, 0x63, 0x6b, 0x55, 0x73, 0x65, 0x72, + 0x61, 0x22, 0xab, 0x01, 0x0a, 0x15, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, + 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x17, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x2f, + 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, + 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, + 0x42, 0x0a, 0x10, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x22, 0x7a, 0x0a, 0x11, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, - 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, - 0x1d, 0x0a, 0x0a, 0x72, 0x74, 0x63, 0x5f, 0x6b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x74, 0x63, 0x4b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x24, - 0x0a, 0x0e, 0x72, 0x74, 0x63, 0x5f, 0x6b, 0x69, 0x63, 0x6b, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x74, 0x63, 0x4b, 0x69, 0x63, 0x6b, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x22, 0x68, 0x0a, 0x10, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, - 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, - 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x7a, - 0x0a, 0x11, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, - 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0xd4, 0x01, 0x0a, 0x16, 0x53, - 0x79, 0x73, 0x74, 0x65, 0x6d, 0x45, 0x76, 0x69, 0x63, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, + 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, + 0x5a, 0x0a, 0x10, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x7a, 0x0a, 0x11, 0x43, + 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, + 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x8f, 0x05, 0x0a, 0x11, 0x41, 0x64, 0x6d, 0x69, + 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x17, 0x0a, + 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, + 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, + 0x6f, 0x6f, 0x6d, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, + 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, + 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, + 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x12, 0x0a, + 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, + 0x73, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2b, 0x0a, 0x12, + 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, + 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x63, 0x6c, 0x6f, + 0x73, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x42, + 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x63, 0x6c, + 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0a, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x12, 0x0a, 0x04, + 0x68, 0x65, 0x61, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x68, 0x65, 0x61, 0x74, + 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x6f, 0x63, 0x63, 0x75, 0x70, 0x69, 0x65, 0x64, 0x5f, 0x73, + 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x11, 0x6f, 0x63, 0x63, 0x75, 0x70, 0x69, 0x65, 0x64, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, + 0x18, 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x53, 0x63, 0x6f, 0x72, + 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xba, 0x02, 0x0a, 0x15, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, + 0x53, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, + 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, + 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x62, 0x79, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x73, + 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x6f, 0x72, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x8c, 0x01, 0x0a, 0x16, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x36, 0x0a, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, + 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, + 0x65, 0x6d, 0x52, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, + 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, + 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x5e, 0x0a, 0x13, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, + 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, + 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, + 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x22, 0x72, 0x0a, 0x14, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, + 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, + 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, + 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x72, + 0x6f, 0x6f, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x8c, 0x04, 0x0a, 0x16, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x88, 0x01, 0x01, 0x12, + 0x20, 0x0a, 0x09, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x01, 0x52, 0x08, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x88, 0x01, + 0x01, 0x12, 0x25, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x88, 0x01, + 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x04, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x12, 0x2f, + 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x48, 0x05, 0x52, 0x0f, 0x76, 0x69, 0x73, + 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, + 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, + 0x0a, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x12, + 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x48, 0x06, 0x52, 0x10, 0x6f, 0x77, 0x6e, 0x65, + 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x88, 0x01, 0x01, 0x42, + 0x08, 0x0a, 0x06, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6d, 0x6f, 0x64, 0x65, + 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x14, 0x0a, 0x12, 0x5f, + 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x64, 0x42, 0x15, 0x0a, 0x13, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x80, 0x01, 0x0a, 0x17, 0x41, 0x64, 0x6d, + 0x69, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, + 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x82, 0x01, 0x0a, 0x16, + 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, + 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, + 0x22, 0x80, 0x01, 0x0a, 0x17, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, + 0x6f, 0x6f, 0x6d, 0x22, 0xb4, 0x01, 0x0a, 0x0c, 0x4d, 0x69, 0x63, 0x55, 0x70, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x5b, 0x0a, + 0x16, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, + 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, + 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x52, 0x14, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0xe5, 0x01, 0x0a, 0x0d, 0x4d, + 0x69, 0x63, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x2f, 0x0a, 0x04, 0x72, + 0x6f, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x24, 0x0a, 0x0e, + 0x6d, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x69, 0x63, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x64, 0x65, + 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x11, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, + 0x4d, 0x73, 0x22, 0xdb, 0x01, 0x0a, 0x0e, 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x22, 0x0a, - 0x0d, 0x62, 0x61, 0x6e, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x62, 0x61, 0x6e, 0x46, 0x72, 0x6f, 0x6d, 0x52, 0x6f, 0x6f, - 0x6d, 0x22, 0x88, 0x02, 0x0a, 0x17, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x45, 0x76, 0x69, 0x63, - 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, - 0x10, 0x68, 0x61, 0x64, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x6f, - 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x68, 0x61, 0x64, 0x43, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, - 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, - 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x17, - 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x74, 0x63, 0x5f, 0x6b, - 0x69, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x74, 0x63, - 0x4b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x74, 0x63, 0x5f, 0x6b, 0x69, - 0x63, 0x6b, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x72, 0x74, 0x63, 0x4b, 0x69, 0x63, 0x6b, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xd6, 0x01, 0x0a, - 0x17, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, - 0x6f, 0x73, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, - 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x73, 0x5f, 0x68, 0x6f, 0x73, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x73, - 0x48, 0x6f, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x68, - 0x6f, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x12, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1b, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x17, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xc2, 0x01, 0x0a, 0x16, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, - 0x66, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, - 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, - 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x26, 0x0a, - 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, - 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x8a, 0x04, 0x0a, 0x13, 0x53, - 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x54, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, - 0x69, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x4e, 0x69, 0x63, - 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, - 0x72, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, - 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x37, - 0x0a, 0x18, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x15, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, - 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x44, 0x0a, 0x1f, 0x72, 0x65, 0x63, 0x65, 0x69, - 0x76, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x1b, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, - 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, - 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2a, 0x0a, 0x11, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x47, - 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, - 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, - 0x69, 0x6e, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, 0x6c, 0x6c, 0x69, - 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x63, 0x65, - 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x49, 0x64, 0x12, 0x41, 0x0a, 0x0a, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x67, 0x69, - 0x66, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, - 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x09, 0x6c, 0x75, - 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x22, 0xf4, 0x06, 0x0a, 0x14, 0x53, 0x65, 0x6e, 0x64, - 0x47, 0x69, 0x66, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, - 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x76, 0x61, - 0x74, 0x61, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, - 0x72, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x33, 0x0a, 0x16, 0x73, 0x65, 0x6e, 0x64, 0x65, - 0x72, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x44, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x1d, - 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x19, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x72, 0x65, 0x74, 0x74, - 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, - 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, - 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, - 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, - 0x70, 0x65, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, - 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, - 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x6d, - 0x5f, 0x68, 0x65, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x6f, 0x6f, - 0x6d, 0x48, 0x65, 0x61, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, - 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x24, - 0x0a, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x67, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x70, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, - 0x63, 0x70, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, - 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x67, - 0x69, 0x66, 0x74, 0x5f, 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x11, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x67, 0x69, 0x66, 0x74, 0x49, 0x63, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, - 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x66, - 0x74, 0x41, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x2a, 0x0a, - 0x11, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x45, 0x66, - 0x66, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x14, 0x20, 0x03, - 0x28, 0x03, 0x52, 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x73, 0x12, 0x3c, 0x0a, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x18, 0x15, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x12, - 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x16, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x22, 0xe2, - 0x06, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, - 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, - 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, - 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x73, 0x5f, - 0x68, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x49, 0x73, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, - 0x6f, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1b, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6f, 0x77, 0x6e, - 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x17, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x77, - 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6e, - 0x64, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, - 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, - 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x54, - 0x0a, 0x12, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x63, - 0x6f, 0x70, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x47, - 0x69, 0x66, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x6f, - 0x70, 0x65, 0x52, 0x10, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x63, - 0x6f, 0x70, 0x65, 0x73, 0x12, 0x5b, 0x0a, 0x16, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x64, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x10, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x12, 0x5b, 0x0a, 0x16, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x64, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x14, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x22, 0x91, 0x01, 0x0a, 0x0f, 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, + 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, + 0x74, 0x4e, 0x6f, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, + 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0xe2, 0x01, 0x0a, 0x14, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, + 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, + 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x5b, 0x0a, 0x16, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x70, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, + 0x6d, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x52, 0x14, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x7e, 0x0a, 0x15, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, + 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0xd5, 0x02, 0x0a, 0x1b, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x4d, 0x69, 0x63, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, + 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x24, 0x0a, 0x0e, 0x6d, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x69, 0x63, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x6f, 0x6f, + 0x6d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x12, 0x5b, 0x0a, 0x16, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x64, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, - 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x44, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x14, 0x73, 0x65, 0x6e, - 0x64, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, - 0x65, 0x12, 0x5d, 0x0a, 0x17, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x15, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, - 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, - 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4d, - 0x6f, 0x64, 0x65, 0x22, 0xed, 0x04, 0x0a, 0x10, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2c, - 0x0a, 0x12, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, - 0x69, 0x6e, 0x67, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, - 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x68, 0x65, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x08, 0x72, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x74, 0x12, 0x34, 0x0a, 0x09, 0x67, 0x69, 0x66, - 0x74, 0x5f, 0x72, 0x61, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x6e, - 0x6b, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x52, 0x61, 0x6e, 0x6b, 0x12, - 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, - 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, - 0x12, 0x36, 0x0a, 0x06, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x52, 0x06, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x41, 0x0a, 0x0a, 0x6c, 0x75, 0x63, 0x6b, - 0x79, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, - 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x52, 0x09, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x12, 0x43, 0x0a, 0x0b, 0x6c, - 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x52, 0x0a, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x73, - 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, - 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, - 0x69, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x39, - 0x0a, 0x19, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x65, 0x5f, 0x62, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x16, 0x67, 0x69, 0x66, 0x74, 0x49, 0x6e, 0x63, 0x6f, 0x6d, 0x65, 0x42, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x48, 0x0a, 0x0d, 0x62, 0x61, 0x74, - 0x63, 0x68, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x52, 0x0c, 0x62, 0x61, 0x74, 0x63, 0x68, 0x44, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x22, 0x6a, 0x0a, 0x1b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x70, 0x65, 0x61, - 0x6b, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, - 0x73, 0x0a, 0x1c, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x50, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x18, 0x0a, 0x07, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xa6, 0x01, 0x0a, 0x19, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, - 0x6f, 0x6f, 0x6d, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x71, 0x0a, - 0x1a, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x72, 0x65, 0x73, 0x65, - 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, - 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, - 0x65, 0x73, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x21, 0x0a, - 0x0c, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x22, 0xed, 0x02, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x14, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x22, 0x9e, 0x01, 0x0a, 0x1c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x4d, 0x69, 0x63, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, + 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, + 0x6f, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, + 0x6f, 0x6d, 0x22, 0x91, 0x01, 0x0a, 0x13, 0x4d, 0x69, 0x63, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, + 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x69, 0x63, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xc5, 0x01, 0x0a, 0x14, 0x4d, 0x69, 0x63, 0x48, 0x65, + 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x2f, + 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, + 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, + 0x2d, 0x0a, 0x13, 0x6d, 0x69, 0x63, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x6d, 0x69, + 0x63, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xdc, + 0x01, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x63, 0x4d, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x75, + 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, + 0x12, 0x5b, 0x0a, 0x16, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x14, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x44, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x94, 0x01, + 0x0a, 0x12, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x63, 0x4d, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, + 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, + 0x74, 0x4e, 0x6f, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, + 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x8b, 0x02, 0x0a, 0x14, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x54, + 0x43, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, + 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x49, 0x64, 0x22, 0x97, 0x01, 0x0a, 0x15, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x54, 0x43, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x2f, 0x0a, 0x04, 0x72, + 0x6f, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x78, 0x0a, 0x15, + 0x53, 0x65, 0x74, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x4c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, - 0x69, 0x65, 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, - 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x62, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x62, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x75, 0x72, - 0x73, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, - 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x21, 0x0a, - 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, - 0x12, 0x2e, 0x0a, 0x13, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x76, - 0x69, 0x65, 0x77, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, - 0x12, 0x2e, 0x0a, 0x13, 0x61, 0x6c, 0x6c, 0x5f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, - 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x61, - 0x6c, 0x6c, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, - 0x22, 0xec, 0x01, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x42, 0x79, - 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x16, + 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, + 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x22, 0x7f, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x63, + 0x53, 0x65, 0x61, 0x74, 0x4c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, + 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x61, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x43, 0x68, + 0x61, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x7f, 0x0a, 0x16, 0x53, 0x65, + 0x74, 0x43, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, + 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x7c, 0x0a, 0x16, 0x53, + 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x1a, 0x0a, + 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x80, 0x01, 0x0a, 0x17, 0x53, 0x65, + 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, + 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, + 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x85, 0x01, 0x0a, + 0x13, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x22, 0x7d, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x41, + 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, + 0x6f, 0x6f, 0x6d, 0x22, 0x7d, 0x0a, 0x0f, 0x4d, 0x75, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, + 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, + 0x6d, 0x75, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6d, 0x75, 0x74, + 0x65, 0x64, 0x22, 0x79, 0x0a, 0x10, 0x4d, 0x75, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, + 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, + 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, + 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x88, 0x01, + 0x0a, 0x0f, 0x4b, 0x69, 0x63, 0x6b, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x22, 0xbe, 0x01, 0x0a, 0x10, 0x4b, 0x69, 0x63, + 0x6b, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, + 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, + 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x74, 0x63, 0x5f, 0x6b, 0x69, 0x63, 0x6b, + 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x74, 0x63, 0x4b, 0x69, 0x63, + 0x6b, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x74, 0x63, 0x5f, 0x6b, 0x69, 0x63, 0x6b, 0x5f, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x74, 0x63, + 0x4b, 0x69, 0x63, 0x6b, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x68, 0x0a, 0x10, 0x55, 0x6e, 0x62, + 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, + 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x22, 0x7a, 0x0a, 0x11, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, + 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, + 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, + 0xd4, 0x01, 0x0a, 0x16, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x45, 0x76, 0x69, 0x63, 0x74, 0x55, + 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0d, 0x62, 0x61, 0x6e, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x72, + 0x6f, 0x6f, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x62, 0x61, 0x6e, 0x46, 0x72, + 0x6f, 0x6d, 0x52, 0x6f, 0x6f, 0x6d, 0x22, 0x88, 0x02, 0x0a, 0x17, 0x53, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x45, 0x76, 0x69, 0x63, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x68, 0x61, 0x64, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x68, 0x61, + 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x34, 0x0a, 0x06, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, + 0x6f, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x72, 0x74, 0x63, 0x5f, 0x6b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x09, 0x72, 0x74, 0x63, 0x4b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x72, + 0x74, 0x63, 0x5f, 0x6b, 0x69, 0x63, 0x6b, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x74, 0x63, 0x4b, 0x69, 0x63, 0x6b, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x22, 0xd6, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x24, 0x0a, + 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x73, + 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x49, 0x73, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1b, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x17, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, + 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xc2, 0x01, 0x0a, 0x16, 0x53, + 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, + 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, + 0x61, 0x74, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, + 0x61, 0x72, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x70, 0x72, + 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x65, 0x74, + 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, + 0x8a, 0x04, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, + 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, + 0x65, 0x72, 0x4e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, + 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x41, 0x76, 0x61, + 0x74, 0x61, 0x72, 0x12, 0x37, 0x0a, 0x18, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, + 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x44, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x44, 0x0a, 0x1f, + 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, + 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x50, + 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x67, 0x69, 0x66, 0x74, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d, 0x0a, + 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, + 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, + 0x67, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x41, 0x0a, 0x0a, 0x6c, 0x75, 0x63, + 0x6b, 0x79, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, + 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x52, 0x09, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x22, 0xf4, 0x06, 0x0a, + 0x14, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, + 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x33, 0x0a, 0x16, + 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x73, 0x65, + 0x6e, 0x64, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x40, 0x0a, 0x1d, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x74, + 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x19, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, + 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, + 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, + 0x2c, 0x0a, 0x12, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, + 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, + 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, + 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x68, 0x65, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, + 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, + 0x6c, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x67, 0x69, 0x66, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x70, 0x5f, + 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0f, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x70, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, 0x72, + 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x67, 0x69, 0x66, 0x74, 0x49, 0x63, 0x6f, + 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x61, 0x6e, 0x69, + 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x10, 0x67, 0x69, 0x66, 0x74, 0x41, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, + 0x72, 0x6c, 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x65, 0x66, 0x66, 0x65, 0x63, + 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x67, + 0x69, 0x66, 0x74, 0x45, 0x66, 0x66, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x26, + 0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x3c, 0x0a, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x07, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, + 0x69, 0x64, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x49, 0x64, 0x22, 0xa9, 0x07, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, + 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, + 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, + 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x03, 0x52, + 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x17, + 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x5f, 0x69, 0x73, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x73, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x31, 0x0a, + 0x15, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x12, 0x3c, 0x0a, 0x1b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x63, + 0x79, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x17, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x67, 0x65, + 0x6e, 0x63, 0x79, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, + 0x0a, 0x10, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, + 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x65, 0x6e, 0x64, + 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x72, 0x79, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0x54, 0x0a, 0x12, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x68, 0x6f, + 0x73, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, + 0x73, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x10, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, + 0x6f, 0x73, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x5b, 0x0a, 0x16, 0x73, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, + 0x66, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x52, 0x14, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x5d, 0x0a, 0x17, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, + 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x15, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x62, + 0x6f, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x13, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x62, 0x6f, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x73, 0x65, 0x71, 0x18, + 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x62, 0x61, 0x74, 0x63, 0x68, 0x53, 0x65, 0x71, 0x22, + 0x9e, 0x05, 0x0a, 0x10, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, + 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, + 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x6d, + 0x5f, 0x68, 0x65, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x6f, 0x6f, + 0x6d, 0x48, 0x65, 0x61, 0x74, 0x12, 0x34, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x72, 0x61, + 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x6e, 0x6b, 0x49, 0x74, 0x65, + 0x6d, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x52, 0x61, 0x6e, 0x6b, 0x12, 0x2f, 0x0a, 0x04, 0x72, + 0x6f, 0x6f, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x36, 0x0a, 0x06, + 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, + 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x72, 0x6f, + 0x63, 0x6b, 0x65, 0x74, 0x12, 0x41, 0x0a, 0x0a, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x67, 0x69, + 0x66, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, + 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x09, 0x6c, 0x75, + 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x12, 0x43, 0x0a, 0x0b, 0x6c, 0x75, 0x63, 0x6b, 0x79, + 0x5f, 0x67, 0x69, 0x66, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, + 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x52, 0x0a, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x73, 0x12, 0x2c, 0x0a, 0x12, + 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, + 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x69, 0x6e, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x19, 0x67, 0x69, + 0x66, 0x74, 0x5f, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x16, 0x67, + 0x69, 0x66, 0x74, 0x49, 0x6e, 0x63, 0x6f, 0x6d, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x48, 0x0a, 0x0d, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x64, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, + 0x64, 0x47, 0x69, 0x66, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, + 0x79, 0x52, 0x0c, 0x62, 0x61, 0x74, 0x63, 0x68, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x12, + 0x2f, 0x0a, 0x02, 0x76, 0x32, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, + 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x56, 0x32, 0x52, 0x02, 0x76, 0x32, + 0x22, 0x6a, 0x0a, 0x1b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x50, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x73, 0x0a, 0x1c, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x61, + 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x21, + 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x22, 0xa6, 0x01, 0x0a, 0x19, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x6f, 0x6f, 0x6d, + 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, + 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x71, 0x0a, 0x1a, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x65, 0x73, + 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, + 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x6f, + 0x6f, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x53, 0x0a, + 0x19, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x70, 0x70, 0x43, + 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, + 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, + 0x49, 0x64, 0x22, 0x37, 0x0a, 0x1a, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x52, 0x6f, 0x6f, + 0x6d, 0x41, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x97, 0x03, 0x0a, 0x10, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, + 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, + 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x74, 0x61, 0x62, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, + 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x76, + 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x61, + 0x6c, 0x6c, 0x5f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x61, 0x6c, 0x6c, 0x56, 0x69, 0x73, + 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xec, 0x01, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, + 0x6f, 0x6d, 0x73, 0x42, 0x79, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, 0x69, 0x65, 0x77, + 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x77, 0x6e, 0x65, + 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, + 0x52, 0x0c, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x10, + 0x0a, 0x03, 0x74, 0x61, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x62, + 0x12, 0x16, 0x0a, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, + 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x14, + 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, + 0x75, 0x65, 0x72, 0x79, 0x22, 0xb7, 0x02, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, + 0x6d, 0x46, 0x65, 0x65, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0c, 0x6f, 0x77, 0x6e, - 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x62, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x62, 0x12, 0x16, 0x0a, 0x06, 0x63, - 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x75, 0x72, - 0x73, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, - 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, - 0xb7, 0x02, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x46, 0x65, 0x65, 0x64, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, - 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, - 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, - 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, - 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, - 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x62, 0x12, 0x16, 0x0a, 0x06, - 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x75, - 0x72, 0x73, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, - 0x65, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, - 0x12, 0x47, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x46, 0x65, 0x65, 0x64, - 0x52, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x52, 0x0c, 0x72, 0x65, 0x6c, - 0x61, 0x74, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x1e, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x16, 0x0a, 0x06, - 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x65, - 0x72, 0x69, 0x6f, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, - 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, - 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x63, 0x0a, 0x13, 0x52, 0x6f, 0x6f, 0x6d, 0x46, 0x65, 0x65, - 0x64, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xd6, 0x03, 0x0a, 0x0c, 0x52, - 0x6f, 0x6f, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x72, - 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, - 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77, 0x6e, - 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x1b, - 0x0a, 0x09, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x6d, - 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, - 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, 0x61, 0x74, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x68, 0x65, 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6f, - 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x0b, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, - 0x0a, 0x0a, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e, 0x0a, - 0x13, 0x6f, 0x63, 0x63, 0x75, 0x70, 0x69, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x6f, 0x63, 0x63, 0x75, - 0x70, 0x69, 0x65, 0x64, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, - 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, - 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x68, 0x6f, - 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x6f, 0x6f, - 0x6d, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x6b, - 0x65, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, - 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, - 0x6f, 0x64, 0x65, 0x22, 0x67, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x05, 0x72, 0x6f, 0x6f, 0x6d, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x4c, 0x69, 0x73, 0x74, - 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, - 0x65, 0x78, 0x74, 0x5f, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x6e, 0x65, 0x78, 0x74, 0x43, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x22, 0x96, 0x01, 0x0a, - 0x17, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, - 0x6f, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x61, 0x6e, 0x6b, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x12, 0x17, 0x0a, 0x07, - 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x70, - 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x53, - 0x70, 0x65, 0x6e, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, - 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0xef, 0x01, 0x0a, 0x1f, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, - 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, - 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x05, 0x69, 0x74, 0x65, - 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, - 0x74, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, - 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x16, 0x0a, - 0x06, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, - 0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x61, - 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1a, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x74, 0x5f, - 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x41, 0x74, 0x4d, - 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, - 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xbe, 0x01, 0x0a, 0x1f, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x61, 0x6e, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x72, - 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, - 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, 0x14, 0x0a, 0x05, - 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, - 0x69, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, 0x69, 0x65, 0x77, - 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x8a, 0x02, 0x0a, 0x20, 0x4c, 0x69, 0x73, - 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x61, 0x6e, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, - 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, 0x1e, - 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1a, - 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x3d, 0x0a, 0x05, 0x69, 0x74, - 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x6f, - 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x6b, 0x49, 0x74, - 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, - 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, - 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, - 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x66, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f, - 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, - 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, - 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x85, 0x01, - 0x0a, 0x11, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x68, 0x61, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x61, 0x73, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x2f, - 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, + 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, + 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, + 0x10, 0x0a, 0x03, 0x74, 0x61, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, + 0x62, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, + 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x47, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, - 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, - 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, - 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, - 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x60, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, + 0x6d, 0x46, 0x65, 0x65, 0x64, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x73, 0x22, 0x99, + 0x01, 0x0a, 0x1e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x4c, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x63, 0x0a, 0x13, 0x52, 0x6f, + 0x6f, 0x6d, 0x46, 0x65, 0x65, 0x64, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x55, 0x73, 0x65, + 0x72, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x72, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x72, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, + 0xd6, 0x03, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, + 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, + 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, + 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, + 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, + 0x68, 0x65, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x68, 0x65, 0x61, 0x74, + 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x6f, 0x63, 0x63, 0x75, 0x70, 0x69, 0x65, 0x64, 0x5f, 0x73, + 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x11, 0x6f, 0x63, 0x63, 0x75, 0x70, 0x69, 0x65, 0x64, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, + 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x19, + 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, + 0x6d, 0x5f, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, + 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6c, + 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, + 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x67, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, + 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, + 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, + 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x65, 0x78, 0x74, 0x43, 0x75, 0x72, 0x73, 0x6f, + 0x72, 0x22, 0x96, 0x01, 0x0a, 0x17, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x4c, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, + 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x72, 0x61, 0x6e, + 0x6b, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, + 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, + 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, + 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x4c, 0x69, 0x73, 0x74, + 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0xef, 0x01, 0x0a, 0x1f, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x4c, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, + 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, + 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, + 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1a, 0x0a, 0x09, 0x65, 0x6e, + 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, + 0x6e, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xbe, 0x01, 0x0a, + 0x1f, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x65, 0x72, + 0x69, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x65, 0x72, 0x69, 0x6f, + 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, + 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0c, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x8a, 0x02, + 0x0a, 0x20, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, + 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x65, 0x72, + 0x69, 0x6f, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x41, + 0x74, 0x4d, 0x73, 0x12, 0x1a, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, + 0x3d, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x6f, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x61, 0x6e, 0x6b, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x14, + 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x66, 0x0a, 0x10, 0x47, 0x65, + 0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, - 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xd6, 0x02, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x43, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x68, 0x61, 0x73, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x74, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x68, 0x61, - 0x73, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, - 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x6f, 0x6f, - 0x6d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x24, 0x0a, 0x0e, - 0x6d, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x69, 0x63, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, - 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x2b, 0x0a, 0x12, 0x6e, 0x65, 0x65, 0x64, 0x5f, - 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x69, 0x6d, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6e, 0x65, 0x65, 0x64, 0x4a, 0x6f, 0x69, 0x6e, 0x49, 0x6d, 0x47, - 0x72, 0x6f, 0x75, 0x70, 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x65, 0x65, 0x64, 0x5f, 0x72, 0x74, 0x63, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6e, 0x65, - 0x65, 0x64, 0x52, 0x74, 0x63, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, - 0x22, 0x87, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x72, - 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, - 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, 0x69, - 0x65, 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x91, 0x01, 0x0a, 0x17, 0x47, - 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, - 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, - 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x1f, 0x0a, - 0x0b, 0x69, 0x73, 0x5f, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x22, 0x85, - 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, - 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, - 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x74, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, - 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x35, 0x0a, 0x06, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, - 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xd0, 0x01, 0x0a, - 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, - 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x72, - 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, - 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, 0x69, - 0x65, 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, - 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, - 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, - 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x22, - 0xee, 0x01, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x4f, 0x6e, 0x6c, 0x69, - 0x6e, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x2d, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, + 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x22, + 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x22, 0x85, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x68, 0x61, 0x73, 0x5f, + 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x61, 0x73, 0x52, + 0x6f, 0x6f, 0x6d, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, + 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x60, 0x0a, 0x15, 0x47, 0x65, + 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xd6, 0x02, 0x0a, + 0x16, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x68, 0x61, 0x73, 0x5f, 0x63, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0e, 0x68, 0x61, 0x73, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, + 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x6f, + 0x6f, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, + 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6c, + 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x69, 0x63, 0x53, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x73, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x2b, 0x0a, 0x12, + 0x6e, 0x65, 0x65, 0x64, 0x5f, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x69, 0x6d, 0x5f, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6e, 0x65, 0x65, 0x64, 0x4a, 0x6f, + 0x69, 0x6e, 0x49, 0x6d, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x65, 0x65, + 0x64, 0x5f, 0x72, 0x74, 0x63, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0c, 0x6e, 0x65, 0x65, 0x64, 0x52, 0x74, 0x63, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, + 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, + 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, + 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x87, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, + 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0x14, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, + 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, + 0x91, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x72, + 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x24, 0x0a, 0x0e, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, + 0x4d, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, + 0x77, 0x65, 0x64, 0x22, 0x85, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, + 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, + 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, + 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, + 0x69, 0x65, 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x74, 0x0a, 0x15, 0x47, + 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x06, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, + 0x73, 0x22, 0xd0, 0x01, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x4f, 0x6e, + 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, + 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, + 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x73, 0x6f, 0x72, 0x74, 0x22, 0xee, 0x01, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, + 0x6d, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, + 0x65, 0x72, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, + 0x12, 0x33, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x6f, 0x6f, 0x6d, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, + 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xd2, 0x01, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, + 0x6e, 0x6e, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x75, 0x6e, 0x62, + 0x61, 0x6e, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, + 0x75, 0x6e, 0x62, 0x61, 0x6e, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x6d, + 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x4d, 0x73, 0x12, 0x1c, 0x0a, 0x09, + 0x70, 0x65, 0x72, 0x6d, 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x09, 0x70, 0x65, 0x72, 0x6d, 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x22, 0xbc, 0x01, 0x0a, 0x1a, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x55, 0x73, 0x65, + 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, + 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, + 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, 0x69, 0x65, 0x77, + 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, + 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0xbf, 0x01, 0x0a, 0x1b, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x05, 0x69, 0x74, 0x65, + 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x6e, + 0x6e, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x33, 0x0a, 0x05, 0x69, - 0x74, 0x65, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x4f, - 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, - 0x22, 0xd2, 0x01, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x55, - 0x73, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, - 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, - 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x75, 0x6e, 0x62, 0x61, 0x6e, 0x5f, 0x61, 0x74, - 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x6e, 0x62, 0x61, 0x6e, - 0x41, 0x74, 0x4d, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, - 0x67, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x61, - 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x4d, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x65, 0x72, 0x6d, 0x61, - 0x6e, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x70, 0x65, 0x72, 0x6d, - 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x22, 0xbc, 0x01, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, - 0x6f, 0x6d, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x24, 0x0a, - 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, - 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, - 0x53, 0x69, 0x7a, 0x65, 0x22, 0xbf, 0x01, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, - 0x6d, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x55, 0x73, - 0x65, 0x72, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, - 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, - 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x75, 0x0a, 0x11, 0x46, + 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x22, 0x95, 0x01, 0x0a, 0x12, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x6f, 0x6f, + 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, + 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, + 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x24, + 0x0a, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, + 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x77, 0x0a, 0x13, 0x55, 0x6e, + 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x22, 0x71, 0x0a, 0x14, 0x55, 0x6e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, + 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x72, + 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, + 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, - 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x75, 0x0a, 0x11, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, - 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x72, - 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, - 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x95, 0x01, - 0x0a, 0x12, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1a, 0x0a, - 0x08, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x08, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x6f, 0x6c, - 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0c, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, - 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, - 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, - 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x77, 0x0a, 0x13, 0x55, 0x6e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, - 0x77, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, - 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x71, - 0x0a, 0x14, 0x55, 0x6e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, - 0x1a, 0x0a, 0x08, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x08, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, + 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xe4, 0x01, 0x0a, 0x10, 0x53, 0x65, 0x6e, 0x64, 0x47, + 0x69, 0x66, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x48, 0x69, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, + 0x69, 0x66, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x09, 0x67, 0x69, 0x66, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, + 0x61, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x61, + 0x77, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x54, 0x69, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, + 0x72, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x75, 0x6c, + 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x50, 0x70, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x22, 0xb1, 0x07, + 0x0a, 0x10, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x56, 0x32, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x62, 0x6f, 0x5f, 0x73, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, + 0x6d, 0x62, 0x6f, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, + 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x73, 0x65, 0x71, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x08, 0x62, 0x61, 0x74, 0x63, 0x68, 0x53, 0x65, 0x71, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x64, 0x65, + 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x74, 0x65, 0x64, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, + 0x64, 0x52, 0x6f, 0x6f, 0x6d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x12, + 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, + 0x67, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x6f, + 0x69, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x69, 0x6e, 0x42, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x19, 0x67, 0x69, 0x66, 0x74, + 0x5f, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, + 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x16, 0x67, 0x69, 0x66, + 0x74, 0x49, 0x6e, 0x63, 0x6f, 0x6d, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, + 0x74, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x67, + 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x10, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x35, 0x0a, 0x17, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, + 0x28, 0x03, 0x52, 0x14, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x62, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x41, 0x0a, 0x0a, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x18, + 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, + 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, + 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x09, 0x6c, 0x75, 0x63, 0x6b, 0x79, + 0x47, 0x69, 0x66, 0x74, 0x12, 0x43, 0x0a, 0x0b, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f, 0x67, 0x69, + 0x66, 0x74, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, + 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x0a, 0x6c, + 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x73, 0x12, 0x3e, 0x0a, 0x0a, 0x6c, 0x75, 0x63, + 0x6b, 0x79, 0x5f, 0x68, 0x69, 0x74, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, + 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x48, 0x69, 0x74, 0x52, 0x09, + 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x48, 0x69, 0x74, 0x73, 0x12, 0x48, 0x0a, 0x0d, 0x62, 0x61, 0x74, + 0x63, 0x68, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x52, 0x0c, 0x62, 0x61, 0x74, 0x63, 0x68, 0x44, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x68, 0x65, 0x61, 0x74, + 0x18, 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x74, + 0x12, 0x34, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x72, 0x61, 0x6e, 0x6b, 0x18, 0x12, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x6e, 0x6b, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x08, 0x67, 0x69, + 0x66, 0x74, 0x52, 0x61, 0x6e, 0x6b, 0x12, 0x29, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x32, 0xb2, 0x1b, 0x0a, 0x12, 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x51, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, @@ -14663,7 +15292,7 @@ var file_proto_room_v1_room_proto_rawDesc = []byte{ 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xee, 0x01, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x47, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xd9, 0x02, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x75, 0x61, 0x72, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, @@ -14678,144 +15307,151 @@ var file_proto_room_v1_room_proto_rawDesc = []byte{ 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xe4, 0x10, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x0e, - 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x12, 0x24, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, - 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, - 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, - 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x41, - 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x22, 0x2e, 0x68, 0x79, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x6f, 0x6c, + 0x76, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x52, + 0x6f, 0x6f, 0x6d, 0x41, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x32, 0xe4, 0x10, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, + 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, + 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x52, + 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x7b, 0x0a, 0x18, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, + 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, - 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7b, 0x0a, 0x18, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, - 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, - 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, - 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x75, 0x0a, 0x16, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, - 0x6d, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2c, 0x2e, 0x68, 0x79, + 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, - 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, - 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x87, 0x01, 0x0a, 0x1c, 0x41, 0x64, 0x6d, - 0x69, 0x6e, 0x47, 0x65, 0x74, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, - 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, - 0x65, 0x74, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, - 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, - 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x66, 0x0a, 0x11, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x73, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, - 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, - 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x41, 0x64, - 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, - 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, - 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x62, 0x6f, 0x74, - 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, + 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x75, 0x0a, 0x16, + 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, + 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x52, + 0x6f, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, + 0x6d, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x87, 0x01, 0x0a, 0x1c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, + 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, 0x74, 0x48, 0x75, 0x6d, + 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x65, + 0x74, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, + 0x11, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, + 0x6e, 0x73, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, + 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, + 0x50, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, + 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8d, 0x01, 0x0a, 0x1e, 0x41, 0x64, 0x6d, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x8d, 0x01, 0x0a, 0x1e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x46, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x6f, 0x6d, + 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x12, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, + 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x46, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, + 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, - 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x12, 0x34, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, - 0x6e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, - 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, - 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x41, 0x76, 0x61, - 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, - 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x42, 0x79, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x27, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x42, 0x79, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, - 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x6f, 0x6f, 0x6d, 0x46, 0x65, 0x65, 0x64, 0x73, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, + 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, + 0x42, 0x79, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, + 0x6d, 0x73, 0x42, 0x79, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x46, + 0x65, 0x65, 0x64, 0x73, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x46, 0x65, 0x65, + 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, - 0x6f, 0x6d, 0x46, 0x65, 0x65, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x78, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, - 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x2d, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, - 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, - 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7b, 0x0a, 0x18, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x6b, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, - 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x43, - 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x6b, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, - 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x43, - 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x6b, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x4d, 0x79, - 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, - 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, - 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, - 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, - 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, - 0x6e, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, + 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, 0x17, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x4c, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, + 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x47, + 0x69, 0x66, 0x74, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, + 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, + 0x66, 0x74, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7b, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, + 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, + 0x6b, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x12, + 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, + 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, + 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, + 0x73, 0x68, 0x6f, 0x74, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, + 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, + 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, + 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, - 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, + 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, + 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, + 0x65, 0x74, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, - 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x4f, 0x6e, - 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, - 0x6f, 0x6d, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, + 0x13, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, + 0x73, 0x65, 0x72, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x4f, 0x6e, 0x6c, - 0x69, 0x6e, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x6c, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x6e, 0x6e, - 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, - 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, + 0x69, 0x6e, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x73, + 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x55, 0x73, 0x65, + 0x72, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x6e, 0x6e, 0x65, - 0x64, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, - 0x5a, 0x24, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x6f, 0x6f, 0x6d, 0x2f, 0x76, 0x31, 0x3b, - 0x72, 0x6f, 0x6f, 0x6d, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x64, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2f, 0x72, 0x6f, 0x6f, 0x6d, 0x2f, 0x76, 0x31, 0x3b, 0x72, 0x6f, 0x6f, 0x6d, 0x76, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -14830,7 +15466,7 @@ func file_proto_room_v1_room_proto_rawDescGZIP() []byte { return file_proto_room_v1_room_proto_rawDescData } -var file_proto_room_v1_room_proto_msgTypes = make([]protoimpl.MessageInfo, 154) +var file_proto_room_v1_room_proto_msgTypes = make([]protoimpl.MessageInfo, 159) var file_proto_room_v1_room_proto_goTypes = []any{ (*RequestMeta)(nil), // 0: hyapp.room.v1.RequestMeta (*CommandResult)(nil), // 1: hyapp.room.v1.CommandResult @@ -14901,390 +15537,405 @@ var file_proto_room_v1_room_proto_goTypes = []any{ (*RoomEntryVehicleSnapshot)(nil), // 66: hyapp.room.v1.RoomEntryVehicleSnapshot (*RoomUserDisplayProfile)(nil), // 67: hyapp.room.v1.RoomUserDisplayProfile (*JoinRoomRequest)(nil), // 68: hyapp.room.v1.JoinRoomRequest - (*JoinRoomResponse)(nil), // 69: hyapp.room.v1.JoinRoomResponse - (*RoomHeartbeatRequest)(nil), // 70: hyapp.room.v1.RoomHeartbeatRequest - (*RoomHeartbeatResponse)(nil), // 71: hyapp.room.v1.RoomHeartbeatResponse - (*LeaveRoomRequest)(nil), // 72: hyapp.room.v1.LeaveRoomRequest - (*LeaveRoomResponse)(nil), // 73: hyapp.room.v1.LeaveRoomResponse - (*CloseRoomRequest)(nil), // 74: hyapp.room.v1.CloseRoomRequest - (*CloseRoomResponse)(nil), // 75: hyapp.room.v1.CloseRoomResponse - (*AdminRoomListItem)(nil), // 76: hyapp.room.v1.AdminRoomListItem - (*AdminListRoomsRequest)(nil), // 77: hyapp.room.v1.AdminListRoomsRequest - (*AdminListRoomsResponse)(nil), // 78: hyapp.room.v1.AdminListRoomsResponse - (*AdminGetRoomRequest)(nil), // 79: hyapp.room.v1.AdminGetRoomRequest - (*AdminGetRoomResponse)(nil), // 80: hyapp.room.v1.AdminGetRoomResponse - (*AdminUpdateRoomRequest)(nil), // 81: hyapp.room.v1.AdminUpdateRoomRequest - (*AdminUpdateRoomResponse)(nil), // 82: hyapp.room.v1.AdminUpdateRoomResponse - (*AdminDeleteRoomRequest)(nil), // 83: hyapp.room.v1.AdminDeleteRoomRequest - (*AdminDeleteRoomResponse)(nil), // 84: hyapp.room.v1.AdminDeleteRoomResponse - (*MicUpRequest)(nil), // 85: hyapp.room.v1.MicUpRequest - (*MicUpResponse)(nil), // 86: hyapp.room.v1.MicUpResponse - (*MicDownRequest)(nil), // 87: hyapp.room.v1.MicDownRequest - (*MicDownResponse)(nil), // 88: hyapp.room.v1.MicDownResponse - (*ChangeMicSeatRequest)(nil), // 89: hyapp.room.v1.ChangeMicSeatRequest - (*ChangeMicSeatResponse)(nil), // 90: hyapp.room.v1.ChangeMicSeatResponse - (*ConfirmMicPublishingRequest)(nil), // 91: hyapp.room.v1.ConfirmMicPublishingRequest - (*ConfirmMicPublishingResponse)(nil), // 92: hyapp.room.v1.ConfirmMicPublishingResponse - (*MicHeartbeatRequest)(nil), // 93: hyapp.room.v1.MicHeartbeatRequest - (*MicHeartbeatResponse)(nil), // 94: hyapp.room.v1.MicHeartbeatResponse - (*SetMicMuteRequest)(nil), // 95: hyapp.room.v1.SetMicMuteRequest - (*SetMicMuteResponse)(nil), // 96: hyapp.room.v1.SetMicMuteResponse - (*ApplyRTCEventRequest)(nil), // 97: hyapp.room.v1.ApplyRTCEventRequest - (*ApplyRTCEventResponse)(nil), // 98: hyapp.room.v1.ApplyRTCEventResponse - (*SetMicSeatLockRequest)(nil), // 99: hyapp.room.v1.SetMicSeatLockRequest - (*SetMicSeatLockResponse)(nil), // 100: hyapp.room.v1.SetMicSeatLockResponse - (*SetChatEnabledRequest)(nil), // 101: hyapp.room.v1.SetChatEnabledRequest - (*SetChatEnabledResponse)(nil), // 102: hyapp.room.v1.SetChatEnabledResponse - (*SetRoomPasswordRequest)(nil), // 103: hyapp.room.v1.SetRoomPasswordRequest - (*SetRoomPasswordResponse)(nil), // 104: hyapp.room.v1.SetRoomPasswordResponse - (*SetRoomAdminRequest)(nil), // 105: hyapp.room.v1.SetRoomAdminRequest - (*SetRoomAdminResponse)(nil), // 106: hyapp.room.v1.SetRoomAdminResponse - (*MuteUserRequest)(nil), // 107: hyapp.room.v1.MuteUserRequest - (*MuteUserResponse)(nil), // 108: hyapp.room.v1.MuteUserResponse - (*KickUserRequest)(nil), // 109: hyapp.room.v1.KickUserRequest - (*KickUserResponse)(nil), // 110: hyapp.room.v1.KickUserResponse - (*UnbanUserRequest)(nil), // 111: hyapp.room.v1.UnbanUserRequest - (*UnbanUserResponse)(nil), // 112: hyapp.room.v1.UnbanUserResponse - (*SystemEvictUserRequest)(nil), // 113: hyapp.room.v1.SystemEvictUserRequest - (*SystemEvictUserResponse)(nil), // 114: hyapp.room.v1.SystemEvictUserResponse - (*SendGiftTargetHostScope)(nil), // 115: hyapp.room.v1.SendGiftTargetHostScope - (*SendGiftDisplayProfile)(nil), // 116: hyapp.room.v1.SendGiftDisplayProfile - (*SendGiftBatchTarget)(nil), // 117: hyapp.room.v1.SendGiftBatchTarget - (*SendGiftBatchDisplay)(nil), // 118: hyapp.room.v1.SendGiftBatchDisplay - (*SendGiftRequest)(nil), // 119: hyapp.room.v1.SendGiftRequest - (*SendGiftResponse)(nil), // 120: hyapp.room.v1.SendGiftResponse - (*CheckSpeakPermissionRequest)(nil), // 121: hyapp.room.v1.CheckSpeakPermissionRequest - (*CheckSpeakPermissionResponse)(nil), // 122: hyapp.room.v1.CheckSpeakPermissionResponse - (*VerifyRoomPresenceRequest)(nil), // 123: hyapp.room.v1.VerifyRoomPresenceRequest - (*VerifyRoomPresenceResponse)(nil), // 124: hyapp.room.v1.VerifyRoomPresenceResponse - (*ListRoomsRequest)(nil), // 125: hyapp.room.v1.ListRoomsRequest - (*ListRoomsByOwnersRequest)(nil), // 126: hyapp.room.v1.ListRoomsByOwnersRequest - (*ListRoomFeedsRequest)(nil), // 127: hyapp.room.v1.ListRoomFeedsRequest - (*ListRoomGiftLeaderboardRequest)(nil), // 128: hyapp.room.v1.ListRoomGiftLeaderboardRequest - (*RoomFeedRelatedUser)(nil), // 129: hyapp.room.v1.RoomFeedRelatedUser - (*RoomListItem)(nil), // 130: hyapp.room.v1.RoomListItem - (*ListRoomsResponse)(nil), // 131: hyapp.room.v1.ListRoomsResponse - (*RoomGiftLeaderboardItem)(nil), // 132: hyapp.room.v1.RoomGiftLeaderboardItem - (*ListRoomGiftLeaderboardResponse)(nil), // 133: hyapp.room.v1.ListRoomGiftLeaderboardResponse - (*ListRoomContributionRankRequest)(nil), // 134: hyapp.room.v1.ListRoomContributionRankRequest - (*ListRoomContributionRankResponse)(nil), // 135: hyapp.room.v1.ListRoomContributionRankResponse - (*GetMyRoomRequest)(nil), // 136: hyapp.room.v1.GetMyRoomRequest - (*GetMyRoomResponse)(nil), // 137: hyapp.room.v1.GetMyRoomResponse - (*GetCurrentRoomRequest)(nil), // 138: hyapp.room.v1.GetCurrentRoomRequest - (*GetCurrentRoomResponse)(nil), // 139: hyapp.room.v1.GetCurrentRoomResponse - (*GetRoomSnapshotRequest)(nil), // 140: hyapp.room.v1.GetRoomSnapshotRequest - (*GetRoomSnapshotResponse)(nil), // 141: hyapp.room.v1.GetRoomSnapshotResponse - (*GetRoomRocketRequest)(nil), // 142: hyapp.room.v1.GetRoomRocketRequest - (*GetRoomRocketResponse)(nil), // 143: hyapp.room.v1.GetRoomRocketResponse - (*ListRoomOnlineUsersRequest)(nil), // 144: hyapp.room.v1.ListRoomOnlineUsersRequest - (*ListRoomOnlineUsersResponse)(nil), // 145: hyapp.room.v1.ListRoomOnlineUsersResponse - (*RoomBannedUser)(nil), // 146: hyapp.room.v1.RoomBannedUser - (*ListRoomBannedUsersRequest)(nil), // 147: hyapp.room.v1.ListRoomBannedUsersRequest - (*ListRoomBannedUsersResponse)(nil), // 148: hyapp.room.v1.ListRoomBannedUsersResponse - (*FollowRoomRequest)(nil), // 149: hyapp.room.v1.FollowRoomRequest - (*FollowRoomResponse)(nil), // 150: hyapp.room.v1.FollowRoomResponse - (*UnfollowRoomRequest)(nil), // 151: hyapp.room.v1.UnfollowRoomRequest - (*UnfollowRoomResponse)(nil), // 152: hyapp.room.v1.UnfollowRoomResponse - nil, // 153: hyapp.room.v1.RoomSnapshot.RoomExtEntry + (*RoomEffectiveVipSnapshot)(nil), // 69: hyapp.room.v1.RoomEffectiveVipSnapshot + (*JoinRoomResponse)(nil), // 70: hyapp.room.v1.JoinRoomResponse + (*RoomHeartbeatRequest)(nil), // 71: hyapp.room.v1.RoomHeartbeatRequest + (*RoomHeartbeatResponse)(nil), // 72: hyapp.room.v1.RoomHeartbeatResponse + (*LeaveRoomRequest)(nil), // 73: hyapp.room.v1.LeaveRoomRequest + (*LeaveRoomResponse)(nil), // 74: hyapp.room.v1.LeaveRoomResponse + (*CloseRoomRequest)(nil), // 75: hyapp.room.v1.CloseRoomRequest + (*CloseRoomResponse)(nil), // 76: hyapp.room.v1.CloseRoomResponse + (*AdminRoomListItem)(nil), // 77: hyapp.room.v1.AdminRoomListItem + (*AdminListRoomsRequest)(nil), // 78: hyapp.room.v1.AdminListRoomsRequest + (*AdminListRoomsResponse)(nil), // 79: hyapp.room.v1.AdminListRoomsResponse + (*AdminGetRoomRequest)(nil), // 80: hyapp.room.v1.AdminGetRoomRequest + (*AdminGetRoomResponse)(nil), // 81: hyapp.room.v1.AdminGetRoomResponse + (*AdminUpdateRoomRequest)(nil), // 82: hyapp.room.v1.AdminUpdateRoomRequest + (*AdminUpdateRoomResponse)(nil), // 83: hyapp.room.v1.AdminUpdateRoomResponse + (*AdminDeleteRoomRequest)(nil), // 84: hyapp.room.v1.AdminDeleteRoomRequest + (*AdminDeleteRoomResponse)(nil), // 85: hyapp.room.v1.AdminDeleteRoomResponse + (*MicUpRequest)(nil), // 86: hyapp.room.v1.MicUpRequest + (*MicUpResponse)(nil), // 87: hyapp.room.v1.MicUpResponse + (*MicDownRequest)(nil), // 88: hyapp.room.v1.MicDownRequest + (*MicDownResponse)(nil), // 89: hyapp.room.v1.MicDownResponse + (*ChangeMicSeatRequest)(nil), // 90: hyapp.room.v1.ChangeMicSeatRequest + (*ChangeMicSeatResponse)(nil), // 91: hyapp.room.v1.ChangeMicSeatResponse + (*ConfirmMicPublishingRequest)(nil), // 92: hyapp.room.v1.ConfirmMicPublishingRequest + (*ConfirmMicPublishingResponse)(nil), // 93: hyapp.room.v1.ConfirmMicPublishingResponse + (*MicHeartbeatRequest)(nil), // 94: hyapp.room.v1.MicHeartbeatRequest + (*MicHeartbeatResponse)(nil), // 95: hyapp.room.v1.MicHeartbeatResponse + (*SetMicMuteRequest)(nil), // 96: hyapp.room.v1.SetMicMuteRequest + (*SetMicMuteResponse)(nil), // 97: hyapp.room.v1.SetMicMuteResponse + (*ApplyRTCEventRequest)(nil), // 98: hyapp.room.v1.ApplyRTCEventRequest + (*ApplyRTCEventResponse)(nil), // 99: hyapp.room.v1.ApplyRTCEventResponse + (*SetMicSeatLockRequest)(nil), // 100: hyapp.room.v1.SetMicSeatLockRequest + (*SetMicSeatLockResponse)(nil), // 101: hyapp.room.v1.SetMicSeatLockResponse + (*SetChatEnabledRequest)(nil), // 102: hyapp.room.v1.SetChatEnabledRequest + (*SetChatEnabledResponse)(nil), // 103: hyapp.room.v1.SetChatEnabledResponse + (*SetRoomPasswordRequest)(nil), // 104: hyapp.room.v1.SetRoomPasswordRequest + (*SetRoomPasswordResponse)(nil), // 105: hyapp.room.v1.SetRoomPasswordResponse + (*SetRoomAdminRequest)(nil), // 106: hyapp.room.v1.SetRoomAdminRequest + (*SetRoomAdminResponse)(nil), // 107: hyapp.room.v1.SetRoomAdminResponse + (*MuteUserRequest)(nil), // 108: hyapp.room.v1.MuteUserRequest + (*MuteUserResponse)(nil), // 109: hyapp.room.v1.MuteUserResponse + (*KickUserRequest)(nil), // 110: hyapp.room.v1.KickUserRequest + (*KickUserResponse)(nil), // 111: hyapp.room.v1.KickUserResponse + (*UnbanUserRequest)(nil), // 112: hyapp.room.v1.UnbanUserRequest + (*UnbanUserResponse)(nil), // 113: hyapp.room.v1.UnbanUserResponse + (*SystemEvictUserRequest)(nil), // 114: hyapp.room.v1.SystemEvictUserRequest + (*SystemEvictUserResponse)(nil), // 115: hyapp.room.v1.SystemEvictUserResponse + (*SendGiftTargetHostScope)(nil), // 116: hyapp.room.v1.SendGiftTargetHostScope + (*SendGiftDisplayProfile)(nil), // 117: hyapp.room.v1.SendGiftDisplayProfile + (*SendGiftBatchTarget)(nil), // 118: hyapp.room.v1.SendGiftBatchTarget + (*SendGiftBatchDisplay)(nil), // 119: hyapp.room.v1.SendGiftBatchDisplay + (*SendGiftRequest)(nil), // 120: hyapp.room.v1.SendGiftRequest + (*SendGiftResponse)(nil), // 121: hyapp.room.v1.SendGiftResponse + (*CheckSpeakPermissionRequest)(nil), // 122: hyapp.room.v1.CheckSpeakPermissionRequest + (*CheckSpeakPermissionResponse)(nil), // 123: hyapp.room.v1.CheckSpeakPermissionResponse + (*VerifyRoomPresenceRequest)(nil), // 124: hyapp.room.v1.VerifyRoomPresenceRequest + (*VerifyRoomPresenceResponse)(nil), // 125: hyapp.room.v1.VerifyRoomPresenceResponse + (*ResolveRoomAppCodeRequest)(nil), // 126: hyapp.room.v1.ResolveRoomAppCodeRequest + (*ResolveRoomAppCodeResponse)(nil), // 127: hyapp.room.v1.ResolveRoomAppCodeResponse + (*ListRoomsRequest)(nil), // 128: hyapp.room.v1.ListRoomsRequest + (*ListRoomsByOwnersRequest)(nil), // 129: hyapp.room.v1.ListRoomsByOwnersRequest + (*ListRoomFeedsRequest)(nil), // 130: hyapp.room.v1.ListRoomFeedsRequest + (*ListRoomGiftLeaderboardRequest)(nil), // 131: hyapp.room.v1.ListRoomGiftLeaderboardRequest + (*RoomFeedRelatedUser)(nil), // 132: hyapp.room.v1.RoomFeedRelatedUser + (*RoomListItem)(nil), // 133: hyapp.room.v1.RoomListItem + (*ListRoomsResponse)(nil), // 134: hyapp.room.v1.ListRoomsResponse + (*RoomGiftLeaderboardItem)(nil), // 135: hyapp.room.v1.RoomGiftLeaderboardItem + (*ListRoomGiftLeaderboardResponse)(nil), // 136: hyapp.room.v1.ListRoomGiftLeaderboardResponse + (*ListRoomContributionRankRequest)(nil), // 137: hyapp.room.v1.ListRoomContributionRankRequest + (*ListRoomContributionRankResponse)(nil), // 138: hyapp.room.v1.ListRoomContributionRankResponse + (*GetMyRoomRequest)(nil), // 139: hyapp.room.v1.GetMyRoomRequest + (*GetMyRoomResponse)(nil), // 140: hyapp.room.v1.GetMyRoomResponse + (*GetCurrentRoomRequest)(nil), // 141: hyapp.room.v1.GetCurrentRoomRequest + (*GetCurrentRoomResponse)(nil), // 142: hyapp.room.v1.GetCurrentRoomResponse + (*GetRoomSnapshotRequest)(nil), // 143: hyapp.room.v1.GetRoomSnapshotRequest + (*GetRoomSnapshotResponse)(nil), // 144: hyapp.room.v1.GetRoomSnapshotResponse + (*GetRoomRocketRequest)(nil), // 145: hyapp.room.v1.GetRoomRocketRequest + (*GetRoomRocketResponse)(nil), // 146: hyapp.room.v1.GetRoomRocketResponse + (*ListRoomOnlineUsersRequest)(nil), // 147: hyapp.room.v1.ListRoomOnlineUsersRequest + (*ListRoomOnlineUsersResponse)(nil), // 148: hyapp.room.v1.ListRoomOnlineUsersResponse + (*RoomBannedUser)(nil), // 149: hyapp.room.v1.RoomBannedUser + (*ListRoomBannedUsersRequest)(nil), // 150: hyapp.room.v1.ListRoomBannedUsersRequest + (*ListRoomBannedUsersResponse)(nil), // 151: hyapp.room.v1.ListRoomBannedUsersResponse + (*FollowRoomRequest)(nil), // 152: hyapp.room.v1.FollowRoomRequest + (*FollowRoomResponse)(nil), // 153: hyapp.room.v1.FollowRoomResponse + (*UnfollowRoomRequest)(nil), // 154: hyapp.room.v1.UnfollowRoomRequest + (*UnfollowRoomResponse)(nil), // 155: hyapp.room.v1.UnfollowRoomResponse + (*SendGiftLuckyHit)(nil), // 156: hyapp.room.v1.SendGiftLuckyHit + (*SendGiftResultV2)(nil), // 157: hyapp.room.v1.SendGiftResultV2 + nil, // 158: hyapp.room.v1.RoomSnapshot.RoomExtEntry } var file_proto_room_v1_room_proto_depIdxs = []int32{ - 8, // 0: hyapp.room.v1.RoomRocketLevel.in_room_rewards:type_name -> hyapp.room.v1.RoomRocketRewardItem - 8, // 1: hyapp.room.v1.RoomRocketLevel.top1_rewards:type_name -> hyapp.room.v1.RoomRocketRewardItem - 8, // 2: hyapp.room.v1.RoomRocketLevel.igniter_rewards:type_name -> hyapp.room.v1.RoomRocketRewardItem - 10, // 3: hyapp.room.v1.RoomRocketState.last_rewards:type_name -> hyapp.room.v1.RoomRocketRewardGrant - 12, // 4: hyapp.room.v1.RoomRocketState.pending_launches:type_name -> hyapp.room.v1.RoomRocketPendingLaunch - 11, // 5: hyapp.room.v1.RoomRocketState.current_contributions:type_name -> hyapp.room.v1.RoomRocketContribution - 9, // 6: hyapp.room.v1.RoomRocketInfo.levels:type_name -> hyapp.room.v1.RoomRocketLevel - 13, // 7: hyapp.room.v1.RoomRocketInfo.state:type_name -> hyapp.room.v1.RoomRocketState - 9, // 8: hyapp.room.v1.AdminRoomRocketConfig.levels:type_name -> hyapp.room.v1.RoomRocketLevel - 15, // 9: hyapp.room.v1.AdminRoomRocketConfig.gift_fuel_rules:type_name -> hyapp.room.v1.RoomRocketGiftFuelRule - 0, // 10: hyapp.room.v1.AdminGetRoomRocketConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 16, // 11: hyapp.room.v1.AdminGetRoomRocketConfigResponse.config:type_name -> hyapp.room.v1.AdminRoomRocketConfig - 0, // 12: hyapp.room.v1.AdminUpdateRoomRocketConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 16, // 13: hyapp.room.v1.AdminUpdateRoomRocketConfigRequest.config:type_name -> hyapp.room.v1.AdminRoomRocketConfig - 16, // 14: hyapp.room.v1.AdminUpdateRoomRocketConfigResponse.config:type_name -> hyapp.room.v1.AdminRoomRocketConfig - 0, // 15: hyapp.room.v1.AdminGetRoomSeatConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 21, // 16: hyapp.room.v1.AdminGetRoomSeatConfigResponse.config:type_name -> hyapp.room.v1.AdminRoomSeatConfig - 0, // 17: hyapp.room.v1.AdminUpdateRoomSeatConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 21, // 18: hyapp.room.v1.AdminUpdateRoomSeatConfigResponse.config:type_name -> hyapp.room.v1.AdminRoomSeatConfig - 26, // 19: hyapp.room.v1.AdminHumanRoomRobotConfig.country_pools:type_name -> hyapp.room.v1.AdminHumanRoomRobotCountryPool - 27, // 20: hyapp.room.v1.AdminHumanRoomRobotConfig.country_rules:type_name -> hyapp.room.v1.AdminHumanRoomRobotCountryRule - 0, // 21: hyapp.room.v1.AdminGetHumanRoomRobotConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 28, // 22: hyapp.room.v1.AdminGetHumanRoomRobotConfigResponse.config:type_name -> hyapp.room.v1.AdminHumanRoomRobotConfig - 0, // 23: hyapp.room.v1.AdminUpdateHumanRoomRobotConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 28, // 24: hyapp.room.v1.AdminUpdateHumanRoomRobotConfigRequest.config:type_name -> hyapp.room.v1.AdminHumanRoomRobotConfig - 28, // 25: hyapp.room.v1.AdminUpdateHumanRoomRobotConfigResponse.config:type_name -> hyapp.room.v1.AdminHumanRoomRobotConfig - 33, // 26: hyapp.room.v1.AdminRoomPin.room:type_name -> hyapp.room.v1.AdminRoomPinRoom - 0, // 27: hyapp.room.v1.AdminListRoomPinsRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 34, // 28: hyapp.room.v1.AdminListRoomPinsResponse.pins:type_name -> hyapp.room.v1.AdminRoomPin - 0, // 29: hyapp.room.v1.AdminCreateRoomPinRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 34, // 30: hyapp.room.v1.AdminCreateRoomPinResponse.pin:type_name -> hyapp.room.v1.AdminRoomPin - 0, // 31: hyapp.room.v1.AdminCancelRoomPinRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 34, // 32: hyapp.room.v1.AdminCancelRoomPinResponse.pin:type_name -> hyapp.room.v1.AdminRoomPin - 41, // 33: hyapp.room.v1.AdminRobotRoom.gift_rule:type_name -> hyapp.room.v1.AdminRobotRoomGiftRule - 0, // 34: hyapp.room.v1.AdminListRobotRoomsRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 42, // 35: hyapp.room.v1.AdminListRobotRoomsResponse.rooms:type_name -> hyapp.room.v1.AdminRobotRoom - 0, // 36: hyapp.room.v1.AdminCreateRobotRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 41, // 37: hyapp.room.v1.AdminCreateRobotRoomRequest.gift_rule:type_name -> hyapp.room.v1.AdminRobotRoomGiftRule - 42, // 38: hyapp.room.v1.AdminCreateRobotRoomResponse.room:type_name -> hyapp.room.v1.AdminRobotRoom - 0, // 39: hyapp.room.v1.AdminSetRobotRoomStatusRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 42, // 40: hyapp.room.v1.AdminSetRobotRoomStatusResponse.room:type_name -> hyapp.room.v1.AdminRobotRoom - 0, // 41: hyapp.room.v1.AdminRenameOwnerCountryCodeRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 0, // 42: hyapp.room.v1.AdminFilterAvailableRoomRobotsRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 4, // 43: hyapp.room.v1.RoomSnapshot.mic_seats:type_name -> hyapp.room.v1.SeatState - 2, // 44: hyapp.room.v1.RoomSnapshot.online_users:type_name -> hyapp.room.v1.RoomUser - 5, // 45: hyapp.room.v1.RoomSnapshot.gift_rank:type_name -> hyapp.room.v1.RankItem - 153, // 46: hyapp.room.v1.RoomSnapshot.room_ext:type_name -> hyapp.room.v1.RoomSnapshot.RoomExtEntry - 13, // 47: hyapp.room.v1.RoomSnapshot.rocket:type_name -> hyapp.room.v1.RoomRocketState - 53, // 48: hyapp.room.v1.RoomSnapshot.ban_states:type_name -> hyapp.room.v1.RoomBanState - 0, // 49: hyapp.room.v1.SaveRoomBackgroundRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 55, // 50: hyapp.room.v1.SaveRoomBackgroundResponse.background:type_name -> hyapp.room.v1.RoomBackgroundImage - 0, // 51: hyapp.room.v1.ListRoomBackgroundsRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 55, // 52: hyapp.room.v1.ListRoomBackgroundsResponse.backgrounds:type_name -> hyapp.room.v1.RoomBackgroundImage - 0, // 53: hyapp.room.v1.SetRoomBackgroundRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 54: hyapp.room.v1.SetRoomBackgroundResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 55: hyapp.room.v1.SetRoomBackgroundResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 55, // 56: hyapp.room.v1.SetRoomBackgroundResponse.background:type_name -> hyapp.room.v1.RoomBackgroundImage - 0, // 57: hyapp.room.v1.CreateRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 58: hyapp.room.v1.CreateRoomResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 59: hyapp.room.v1.CreateRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 60: hyapp.room.v1.UpdateRoomProfileRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 61: hyapp.room.v1.UpdateRoomProfileResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 62: hyapp.room.v1.UpdateRoomProfileResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 63: hyapp.room.v1.JoinRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 66, // 64: hyapp.room.v1.JoinRoomRequest.entry_vehicle:type_name -> hyapp.room.v1.RoomEntryVehicleSnapshot - 67, // 65: hyapp.room.v1.JoinRoomRequest.actor_display_profile:type_name -> hyapp.room.v1.RoomUserDisplayProfile - 1, // 66: hyapp.room.v1.JoinRoomResponse.result:type_name -> hyapp.room.v1.CommandResult - 2, // 67: hyapp.room.v1.JoinRoomResponse.user:type_name -> hyapp.room.v1.RoomUser - 54, // 68: hyapp.room.v1.JoinRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 69: hyapp.room.v1.RoomHeartbeatRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 70: hyapp.room.v1.RoomHeartbeatResponse.result:type_name -> hyapp.room.v1.CommandResult - 2, // 71: hyapp.room.v1.RoomHeartbeatResponse.user:type_name -> hyapp.room.v1.RoomUser - 54, // 72: hyapp.room.v1.RoomHeartbeatResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 73: hyapp.room.v1.LeaveRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 74: hyapp.room.v1.LeaveRoomResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 75: hyapp.room.v1.LeaveRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 76: hyapp.room.v1.CloseRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 77: hyapp.room.v1.CloseRoomResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 78: hyapp.room.v1.CloseRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 79: hyapp.room.v1.AdminListRoomsRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 76, // 80: hyapp.room.v1.AdminListRoomsResponse.rooms:type_name -> hyapp.room.v1.AdminRoomListItem - 0, // 81: hyapp.room.v1.AdminGetRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 76, // 82: hyapp.room.v1.AdminGetRoomResponse.room:type_name -> hyapp.room.v1.AdminRoomListItem - 0, // 83: hyapp.room.v1.AdminUpdateRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 84: hyapp.room.v1.AdminUpdateRoomResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 85: hyapp.room.v1.AdminUpdateRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 86: hyapp.room.v1.AdminDeleteRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 87: hyapp.room.v1.AdminDeleteRoomResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 88: hyapp.room.v1.AdminDeleteRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 89: hyapp.room.v1.MicUpRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 67, // 90: hyapp.room.v1.MicUpRequest.target_display_profile:type_name -> hyapp.room.v1.RoomUserDisplayProfile - 1, // 91: hyapp.room.v1.MicUpResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 92: hyapp.room.v1.MicUpResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 93: hyapp.room.v1.MicDownRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 67, // 94: hyapp.room.v1.MicDownRequest.target_display_profile:type_name -> hyapp.room.v1.RoomUserDisplayProfile - 1, // 95: hyapp.room.v1.MicDownResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 96: hyapp.room.v1.MicDownResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 97: hyapp.room.v1.ChangeMicSeatRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 67, // 98: hyapp.room.v1.ChangeMicSeatRequest.target_display_profile:type_name -> hyapp.room.v1.RoomUserDisplayProfile - 1, // 99: hyapp.room.v1.ChangeMicSeatResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 100: hyapp.room.v1.ChangeMicSeatResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 101: hyapp.room.v1.ConfirmMicPublishingRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 67, // 102: hyapp.room.v1.ConfirmMicPublishingRequest.target_display_profile:type_name -> hyapp.room.v1.RoomUserDisplayProfile - 1, // 103: hyapp.room.v1.ConfirmMicPublishingResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 104: hyapp.room.v1.ConfirmMicPublishingResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 105: hyapp.room.v1.MicHeartbeatRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 106: hyapp.room.v1.MicHeartbeatResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 107: hyapp.room.v1.MicHeartbeatResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 108: hyapp.room.v1.SetMicMuteRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 67, // 109: hyapp.room.v1.SetMicMuteRequest.target_display_profile:type_name -> hyapp.room.v1.RoomUserDisplayProfile - 1, // 110: hyapp.room.v1.SetMicMuteResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 111: hyapp.room.v1.SetMicMuteResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 112: hyapp.room.v1.ApplyRTCEventRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 113: hyapp.room.v1.ApplyRTCEventResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 114: hyapp.room.v1.ApplyRTCEventResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 115: hyapp.room.v1.SetMicSeatLockRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 116: hyapp.room.v1.SetMicSeatLockResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 117: hyapp.room.v1.SetMicSeatLockResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 118: hyapp.room.v1.SetChatEnabledRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 119: hyapp.room.v1.SetChatEnabledResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 120: hyapp.room.v1.SetChatEnabledResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 121: hyapp.room.v1.SetRoomPasswordRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 122: hyapp.room.v1.SetRoomPasswordResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 123: hyapp.room.v1.SetRoomPasswordResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 124: hyapp.room.v1.SetRoomAdminRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 125: hyapp.room.v1.SetRoomAdminResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 126: hyapp.room.v1.SetRoomAdminResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 127: hyapp.room.v1.MuteUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 128: hyapp.room.v1.MuteUserResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 129: hyapp.room.v1.MuteUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 130: hyapp.room.v1.KickUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 131: hyapp.room.v1.KickUserResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 132: hyapp.room.v1.KickUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 133: hyapp.room.v1.UnbanUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 134: hyapp.room.v1.UnbanUserResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 135: hyapp.room.v1.UnbanUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 136: hyapp.room.v1.SystemEvictUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 137: hyapp.room.v1.SystemEvictUserResponse.result:type_name -> hyapp.room.v1.CommandResult - 54, // 138: hyapp.room.v1.SystemEvictUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 7, // 139: hyapp.room.v1.SendGiftBatchTarget.lucky_gift:type_name -> hyapp.room.v1.LuckyGiftDrawResult - 117, // 140: hyapp.room.v1.SendGiftBatchDisplay.targets:type_name -> hyapp.room.v1.SendGiftBatchTarget - 0, // 141: hyapp.room.v1.SendGiftRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 115, // 142: hyapp.room.v1.SendGiftRequest.target_host_scopes:type_name -> hyapp.room.v1.SendGiftTargetHostScope - 116, // 143: hyapp.room.v1.SendGiftRequest.sender_display_profile:type_name -> hyapp.room.v1.SendGiftDisplayProfile - 116, // 144: hyapp.room.v1.SendGiftRequest.target_display_profiles:type_name -> hyapp.room.v1.SendGiftDisplayProfile - 1, // 145: hyapp.room.v1.SendGiftResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 146: hyapp.room.v1.SendGiftResponse.gift_rank:type_name -> hyapp.room.v1.RankItem - 54, // 147: hyapp.room.v1.SendGiftResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 13, // 148: hyapp.room.v1.SendGiftResponse.rocket:type_name -> hyapp.room.v1.RoomRocketState - 7, // 149: hyapp.room.v1.SendGiftResponse.lucky_gift:type_name -> hyapp.room.v1.LuckyGiftDrawResult - 7, // 150: hyapp.room.v1.SendGiftResponse.lucky_gifts:type_name -> hyapp.room.v1.LuckyGiftDrawResult - 118, // 151: hyapp.room.v1.SendGiftResponse.batch_display:type_name -> hyapp.room.v1.SendGiftBatchDisplay - 0, // 152: hyapp.room.v1.ListRoomsRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 0, // 153: hyapp.room.v1.ListRoomsByOwnersRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 0, // 154: hyapp.room.v1.ListRoomFeedsRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 129, // 155: hyapp.room.v1.ListRoomFeedsRequest.related_users:type_name -> hyapp.room.v1.RoomFeedRelatedUser - 0, // 156: hyapp.room.v1.ListRoomGiftLeaderboardRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 130, // 157: hyapp.room.v1.ListRoomsResponse.rooms:type_name -> hyapp.room.v1.RoomListItem - 130, // 158: hyapp.room.v1.RoomGiftLeaderboardItem.room:type_name -> hyapp.room.v1.RoomListItem - 132, // 159: hyapp.room.v1.ListRoomGiftLeaderboardResponse.items:type_name -> hyapp.room.v1.RoomGiftLeaderboardItem - 0, // 160: hyapp.room.v1.ListRoomContributionRankRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 6, // 161: hyapp.room.v1.ListRoomContributionRankResponse.items:type_name -> hyapp.room.v1.RoomContributionRankItem - 0, // 162: hyapp.room.v1.GetMyRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 130, // 163: hyapp.room.v1.GetMyRoomResponse.room:type_name -> hyapp.room.v1.RoomListItem - 0, // 164: hyapp.room.v1.GetCurrentRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 0, // 165: hyapp.room.v1.GetRoomSnapshotRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 54, // 166: hyapp.room.v1.GetRoomSnapshotResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 167: hyapp.room.v1.GetRoomRocketRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 14, // 168: hyapp.room.v1.GetRoomRocketResponse.rocket:type_name -> hyapp.room.v1.RoomRocketInfo - 0, // 169: hyapp.room.v1.ListRoomOnlineUsersRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 2, // 170: hyapp.room.v1.ListRoomOnlineUsersResponse.users:type_name -> hyapp.room.v1.RoomUser - 3, // 171: hyapp.room.v1.ListRoomOnlineUsersResponse.items:type_name -> hyapp.room.v1.RoomOnlineUser - 0, // 172: hyapp.room.v1.ListRoomBannedUsersRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 146, // 173: hyapp.room.v1.ListRoomBannedUsersResponse.items:type_name -> hyapp.room.v1.RoomBannedUser - 0, // 174: hyapp.room.v1.FollowRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 0, // 175: hyapp.room.v1.UnfollowRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 62, // 176: hyapp.room.v1.RoomCommandService.CreateRoom:input_type -> hyapp.room.v1.CreateRoomRequest - 64, // 177: hyapp.room.v1.RoomCommandService.UpdateRoomProfile:input_type -> hyapp.room.v1.UpdateRoomProfileRequest - 56, // 178: hyapp.room.v1.RoomCommandService.SaveRoomBackground:input_type -> hyapp.room.v1.SaveRoomBackgroundRequest - 60, // 179: hyapp.room.v1.RoomCommandService.SetRoomBackground:input_type -> hyapp.room.v1.SetRoomBackgroundRequest - 68, // 180: hyapp.room.v1.RoomCommandService.JoinRoom:input_type -> hyapp.room.v1.JoinRoomRequest - 70, // 181: hyapp.room.v1.RoomCommandService.RoomHeartbeat:input_type -> hyapp.room.v1.RoomHeartbeatRequest - 72, // 182: hyapp.room.v1.RoomCommandService.LeaveRoom:input_type -> hyapp.room.v1.LeaveRoomRequest - 74, // 183: hyapp.room.v1.RoomCommandService.CloseRoom:input_type -> hyapp.room.v1.CloseRoomRequest - 81, // 184: hyapp.room.v1.RoomCommandService.AdminUpdateRoom:input_type -> hyapp.room.v1.AdminUpdateRoomRequest - 83, // 185: hyapp.room.v1.RoomCommandService.AdminDeleteRoom:input_type -> hyapp.room.v1.AdminDeleteRoomRequest - 19, // 186: hyapp.room.v1.RoomCommandService.AdminUpdateRoomRocketConfig:input_type -> hyapp.room.v1.AdminUpdateRoomRocketConfigRequest - 24, // 187: hyapp.room.v1.RoomCommandService.AdminUpdateRoomSeatConfig:input_type -> hyapp.room.v1.AdminUpdateRoomSeatConfigRequest - 31, // 188: hyapp.room.v1.RoomCommandService.AdminUpdateHumanRoomRobotConfig:input_type -> hyapp.room.v1.AdminUpdateHumanRoomRobotConfigRequest - 37, // 189: hyapp.room.v1.RoomCommandService.AdminCreateRoomPin:input_type -> hyapp.room.v1.AdminCreateRoomPinRequest - 39, // 190: hyapp.room.v1.RoomCommandService.AdminCancelRoomPin:input_type -> hyapp.room.v1.AdminCancelRoomPinRequest - 45, // 191: hyapp.room.v1.RoomCommandService.AdminCreateRobotRoom:input_type -> hyapp.room.v1.AdminCreateRobotRoomRequest - 47, // 192: hyapp.room.v1.RoomCommandService.AdminSetRobotRoomStatus:input_type -> hyapp.room.v1.AdminSetRobotRoomStatusRequest - 49, // 193: hyapp.room.v1.RoomCommandService.AdminRenameOwnerCountryCode:input_type -> hyapp.room.v1.AdminRenameOwnerCountryCodeRequest - 85, // 194: hyapp.room.v1.RoomCommandService.MicUp:input_type -> hyapp.room.v1.MicUpRequest - 87, // 195: hyapp.room.v1.RoomCommandService.MicDown:input_type -> hyapp.room.v1.MicDownRequest - 89, // 196: hyapp.room.v1.RoomCommandService.ChangeMicSeat:input_type -> hyapp.room.v1.ChangeMicSeatRequest - 91, // 197: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:input_type -> hyapp.room.v1.ConfirmMicPublishingRequest - 93, // 198: hyapp.room.v1.RoomCommandService.MicHeartbeat:input_type -> hyapp.room.v1.MicHeartbeatRequest - 95, // 199: hyapp.room.v1.RoomCommandService.SetMicMute:input_type -> hyapp.room.v1.SetMicMuteRequest - 97, // 200: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:input_type -> hyapp.room.v1.ApplyRTCEventRequest - 99, // 201: hyapp.room.v1.RoomCommandService.SetMicSeatLock:input_type -> hyapp.room.v1.SetMicSeatLockRequest - 101, // 202: hyapp.room.v1.RoomCommandService.SetChatEnabled:input_type -> hyapp.room.v1.SetChatEnabledRequest - 103, // 203: hyapp.room.v1.RoomCommandService.SetRoomPassword:input_type -> hyapp.room.v1.SetRoomPasswordRequest - 105, // 204: hyapp.room.v1.RoomCommandService.SetRoomAdmin:input_type -> hyapp.room.v1.SetRoomAdminRequest - 107, // 205: hyapp.room.v1.RoomCommandService.MuteUser:input_type -> hyapp.room.v1.MuteUserRequest - 109, // 206: hyapp.room.v1.RoomCommandService.KickUser:input_type -> hyapp.room.v1.KickUserRequest - 111, // 207: hyapp.room.v1.RoomCommandService.UnbanUser:input_type -> hyapp.room.v1.UnbanUserRequest - 113, // 208: hyapp.room.v1.RoomCommandService.SystemEvictUser:input_type -> hyapp.room.v1.SystemEvictUserRequest - 119, // 209: hyapp.room.v1.RoomCommandService.SendGift:input_type -> hyapp.room.v1.SendGiftRequest - 149, // 210: hyapp.room.v1.RoomCommandService.FollowRoom:input_type -> hyapp.room.v1.FollowRoomRequest - 151, // 211: hyapp.room.v1.RoomCommandService.UnfollowRoom:input_type -> hyapp.room.v1.UnfollowRoomRequest - 121, // 212: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:input_type -> hyapp.room.v1.CheckSpeakPermissionRequest - 123, // 213: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:input_type -> hyapp.room.v1.VerifyRoomPresenceRequest - 77, // 214: hyapp.room.v1.RoomQueryService.AdminListRooms:input_type -> hyapp.room.v1.AdminListRoomsRequest - 79, // 215: hyapp.room.v1.RoomQueryService.AdminGetRoom:input_type -> hyapp.room.v1.AdminGetRoomRequest - 17, // 216: hyapp.room.v1.RoomQueryService.AdminGetRoomRocketConfig:input_type -> hyapp.room.v1.AdminGetRoomRocketConfigRequest - 22, // 217: hyapp.room.v1.RoomQueryService.AdminGetRoomSeatConfig:input_type -> hyapp.room.v1.AdminGetRoomSeatConfigRequest - 29, // 218: hyapp.room.v1.RoomQueryService.AdminGetHumanRoomRobotConfig:input_type -> hyapp.room.v1.AdminGetHumanRoomRobotConfigRequest - 35, // 219: hyapp.room.v1.RoomQueryService.AdminListRoomPins:input_type -> hyapp.room.v1.AdminListRoomPinsRequest - 43, // 220: hyapp.room.v1.RoomQueryService.AdminListRobotRooms:input_type -> hyapp.room.v1.AdminListRobotRoomsRequest - 51, // 221: hyapp.room.v1.RoomQueryService.AdminFilterAvailableRoomRobots:input_type -> hyapp.room.v1.AdminFilterAvailableRoomRobotsRequest - 125, // 222: hyapp.room.v1.RoomQueryService.ListRooms:input_type -> hyapp.room.v1.ListRoomsRequest - 126, // 223: hyapp.room.v1.RoomQueryService.ListRoomsByOwners:input_type -> hyapp.room.v1.ListRoomsByOwnersRequest - 127, // 224: hyapp.room.v1.RoomQueryService.ListRoomFeeds:input_type -> hyapp.room.v1.ListRoomFeedsRequest - 128, // 225: hyapp.room.v1.RoomQueryService.ListRoomGiftLeaderboard:input_type -> hyapp.room.v1.ListRoomGiftLeaderboardRequest - 134, // 226: hyapp.room.v1.RoomQueryService.ListRoomContributionRank:input_type -> hyapp.room.v1.ListRoomContributionRankRequest - 136, // 227: hyapp.room.v1.RoomQueryService.GetMyRoom:input_type -> hyapp.room.v1.GetMyRoomRequest - 138, // 228: hyapp.room.v1.RoomQueryService.GetCurrentRoom:input_type -> hyapp.room.v1.GetCurrentRoomRequest - 140, // 229: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:input_type -> hyapp.room.v1.GetRoomSnapshotRequest - 58, // 230: hyapp.room.v1.RoomQueryService.ListRoomBackgrounds:input_type -> hyapp.room.v1.ListRoomBackgroundsRequest - 142, // 231: hyapp.room.v1.RoomQueryService.GetRoomRocket:input_type -> hyapp.room.v1.GetRoomRocketRequest - 144, // 232: hyapp.room.v1.RoomQueryService.ListRoomOnlineUsers:input_type -> hyapp.room.v1.ListRoomOnlineUsersRequest - 147, // 233: hyapp.room.v1.RoomQueryService.ListRoomBannedUsers:input_type -> hyapp.room.v1.ListRoomBannedUsersRequest - 63, // 234: hyapp.room.v1.RoomCommandService.CreateRoom:output_type -> hyapp.room.v1.CreateRoomResponse - 65, // 235: hyapp.room.v1.RoomCommandService.UpdateRoomProfile:output_type -> hyapp.room.v1.UpdateRoomProfileResponse - 57, // 236: hyapp.room.v1.RoomCommandService.SaveRoomBackground:output_type -> hyapp.room.v1.SaveRoomBackgroundResponse - 61, // 237: hyapp.room.v1.RoomCommandService.SetRoomBackground:output_type -> hyapp.room.v1.SetRoomBackgroundResponse - 69, // 238: hyapp.room.v1.RoomCommandService.JoinRoom:output_type -> hyapp.room.v1.JoinRoomResponse - 71, // 239: hyapp.room.v1.RoomCommandService.RoomHeartbeat:output_type -> hyapp.room.v1.RoomHeartbeatResponse - 73, // 240: hyapp.room.v1.RoomCommandService.LeaveRoom:output_type -> hyapp.room.v1.LeaveRoomResponse - 75, // 241: hyapp.room.v1.RoomCommandService.CloseRoom:output_type -> hyapp.room.v1.CloseRoomResponse - 82, // 242: hyapp.room.v1.RoomCommandService.AdminUpdateRoom:output_type -> hyapp.room.v1.AdminUpdateRoomResponse - 84, // 243: hyapp.room.v1.RoomCommandService.AdminDeleteRoom:output_type -> hyapp.room.v1.AdminDeleteRoomResponse - 20, // 244: hyapp.room.v1.RoomCommandService.AdminUpdateRoomRocketConfig:output_type -> hyapp.room.v1.AdminUpdateRoomRocketConfigResponse - 25, // 245: hyapp.room.v1.RoomCommandService.AdminUpdateRoomSeatConfig:output_type -> hyapp.room.v1.AdminUpdateRoomSeatConfigResponse - 32, // 246: hyapp.room.v1.RoomCommandService.AdminUpdateHumanRoomRobotConfig:output_type -> hyapp.room.v1.AdminUpdateHumanRoomRobotConfigResponse - 38, // 247: hyapp.room.v1.RoomCommandService.AdminCreateRoomPin:output_type -> hyapp.room.v1.AdminCreateRoomPinResponse - 40, // 248: hyapp.room.v1.RoomCommandService.AdminCancelRoomPin:output_type -> hyapp.room.v1.AdminCancelRoomPinResponse - 46, // 249: hyapp.room.v1.RoomCommandService.AdminCreateRobotRoom:output_type -> hyapp.room.v1.AdminCreateRobotRoomResponse - 48, // 250: hyapp.room.v1.RoomCommandService.AdminSetRobotRoomStatus:output_type -> hyapp.room.v1.AdminSetRobotRoomStatusResponse - 50, // 251: hyapp.room.v1.RoomCommandService.AdminRenameOwnerCountryCode:output_type -> hyapp.room.v1.AdminRenameOwnerCountryCodeResponse - 86, // 252: hyapp.room.v1.RoomCommandService.MicUp:output_type -> hyapp.room.v1.MicUpResponse - 88, // 253: hyapp.room.v1.RoomCommandService.MicDown:output_type -> hyapp.room.v1.MicDownResponse - 90, // 254: hyapp.room.v1.RoomCommandService.ChangeMicSeat:output_type -> hyapp.room.v1.ChangeMicSeatResponse - 92, // 255: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:output_type -> hyapp.room.v1.ConfirmMicPublishingResponse - 94, // 256: hyapp.room.v1.RoomCommandService.MicHeartbeat:output_type -> hyapp.room.v1.MicHeartbeatResponse - 96, // 257: hyapp.room.v1.RoomCommandService.SetMicMute:output_type -> hyapp.room.v1.SetMicMuteResponse - 98, // 258: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:output_type -> hyapp.room.v1.ApplyRTCEventResponse - 100, // 259: hyapp.room.v1.RoomCommandService.SetMicSeatLock:output_type -> hyapp.room.v1.SetMicSeatLockResponse - 102, // 260: hyapp.room.v1.RoomCommandService.SetChatEnabled:output_type -> hyapp.room.v1.SetChatEnabledResponse - 104, // 261: hyapp.room.v1.RoomCommandService.SetRoomPassword:output_type -> hyapp.room.v1.SetRoomPasswordResponse - 106, // 262: hyapp.room.v1.RoomCommandService.SetRoomAdmin:output_type -> hyapp.room.v1.SetRoomAdminResponse - 108, // 263: hyapp.room.v1.RoomCommandService.MuteUser:output_type -> hyapp.room.v1.MuteUserResponse - 110, // 264: hyapp.room.v1.RoomCommandService.KickUser:output_type -> hyapp.room.v1.KickUserResponse - 112, // 265: hyapp.room.v1.RoomCommandService.UnbanUser:output_type -> hyapp.room.v1.UnbanUserResponse - 114, // 266: hyapp.room.v1.RoomCommandService.SystemEvictUser:output_type -> hyapp.room.v1.SystemEvictUserResponse - 120, // 267: hyapp.room.v1.RoomCommandService.SendGift:output_type -> hyapp.room.v1.SendGiftResponse - 150, // 268: hyapp.room.v1.RoomCommandService.FollowRoom:output_type -> hyapp.room.v1.FollowRoomResponse - 152, // 269: hyapp.room.v1.RoomCommandService.UnfollowRoom:output_type -> hyapp.room.v1.UnfollowRoomResponse - 122, // 270: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:output_type -> hyapp.room.v1.CheckSpeakPermissionResponse - 124, // 271: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:output_type -> hyapp.room.v1.VerifyRoomPresenceResponse - 78, // 272: hyapp.room.v1.RoomQueryService.AdminListRooms:output_type -> hyapp.room.v1.AdminListRoomsResponse - 80, // 273: hyapp.room.v1.RoomQueryService.AdminGetRoom:output_type -> hyapp.room.v1.AdminGetRoomResponse - 18, // 274: hyapp.room.v1.RoomQueryService.AdminGetRoomRocketConfig:output_type -> hyapp.room.v1.AdminGetRoomRocketConfigResponse - 23, // 275: hyapp.room.v1.RoomQueryService.AdminGetRoomSeatConfig:output_type -> hyapp.room.v1.AdminGetRoomSeatConfigResponse - 30, // 276: hyapp.room.v1.RoomQueryService.AdminGetHumanRoomRobotConfig:output_type -> hyapp.room.v1.AdminGetHumanRoomRobotConfigResponse - 36, // 277: hyapp.room.v1.RoomQueryService.AdminListRoomPins:output_type -> hyapp.room.v1.AdminListRoomPinsResponse - 44, // 278: hyapp.room.v1.RoomQueryService.AdminListRobotRooms:output_type -> hyapp.room.v1.AdminListRobotRoomsResponse - 52, // 279: hyapp.room.v1.RoomQueryService.AdminFilterAvailableRoomRobots:output_type -> hyapp.room.v1.AdminFilterAvailableRoomRobotsResponse - 131, // 280: hyapp.room.v1.RoomQueryService.ListRooms:output_type -> hyapp.room.v1.ListRoomsResponse - 131, // 281: hyapp.room.v1.RoomQueryService.ListRoomsByOwners:output_type -> hyapp.room.v1.ListRoomsResponse - 131, // 282: hyapp.room.v1.RoomQueryService.ListRoomFeeds:output_type -> hyapp.room.v1.ListRoomsResponse - 133, // 283: hyapp.room.v1.RoomQueryService.ListRoomGiftLeaderboard:output_type -> hyapp.room.v1.ListRoomGiftLeaderboardResponse - 135, // 284: hyapp.room.v1.RoomQueryService.ListRoomContributionRank:output_type -> hyapp.room.v1.ListRoomContributionRankResponse - 137, // 285: hyapp.room.v1.RoomQueryService.GetMyRoom:output_type -> hyapp.room.v1.GetMyRoomResponse - 139, // 286: hyapp.room.v1.RoomQueryService.GetCurrentRoom:output_type -> hyapp.room.v1.GetCurrentRoomResponse - 141, // 287: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:output_type -> hyapp.room.v1.GetRoomSnapshotResponse - 59, // 288: hyapp.room.v1.RoomQueryService.ListRoomBackgrounds:output_type -> hyapp.room.v1.ListRoomBackgroundsResponse - 143, // 289: hyapp.room.v1.RoomQueryService.GetRoomRocket:output_type -> hyapp.room.v1.GetRoomRocketResponse - 145, // 290: hyapp.room.v1.RoomQueryService.ListRoomOnlineUsers:output_type -> hyapp.room.v1.ListRoomOnlineUsersResponse - 148, // 291: hyapp.room.v1.RoomQueryService.ListRoomBannedUsers:output_type -> hyapp.room.v1.ListRoomBannedUsersResponse - 234, // [234:292] is the sub-list for method output_type - 176, // [176:234] is the sub-list for method input_type - 176, // [176:176] is the sub-list for extension type_name - 176, // [176:176] is the sub-list for extension extendee - 0, // [0:176] is the sub-list for field type_name + 156, // 0: hyapp.room.v1.LuckyGiftDrawResult.hits:type_name -> hyapp.room.v1.SendGiftLuckyHit + 8, // 1: hyapp.room.v1.RoomRocketLevel.in_room_rewards:type_name -> hyapp.room.v1.RoomRocketRewardItem + 8, // 2: hyapp.room.v1.RoomRocketLevel.top1_rewards:type_name -> hyapp.room.v1.RoomRocketRewardItem + 8, // 3: hyapp.room.v1.RoomRocketLevel.igniter_rewards:type_name -> hyapp.room.v1.RoomRocketRewardItem + 10, // 4: hyapp.room.v1.RoomRocketState.last_rewards:type_name -> hyapp.room.v1.RoomRocketRewardGrant + 12, // 5: hyapp.room.v1.RoomRocketState.pending_launches:type_name -> hyapp.room.v1.RoomRocketPendingLaunch + 11, // 6: hyapp.room.v1.RoomRocketState.current_contributions:type_name -> hyapp.room.v1.RoomRocketContribution + 9, // 7: hyapp.room.v1.RoomRocketInfo.levels:type_name -> hyapp.room.v1.RoomRocketLevel + 13, // 8: hyapp.room.v1.RoomRocketInfo.state:type_name -> hyapp.room.v1.RoomRocketState + 9, // 9: hyapp.room.v1.AdminRoomRocketConfig.levels:type_name -> hyapp.room.v1.RoomRocketLevel + 15, // 10: hyapp.room.v1.AdminRoomRocketConfig.gift_fuel_rules:type_name -> hyapp.room.v1.RoomRocketGiftFuelRule + 0, // 11: hyapp.room.v1.AdminGetRoomRocketConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 16, // 12: hyapp.room.v1.AdminGetRoomRocketConfigResponse.config:type_name -> hyapp.room.v1.AdminRoomRocketConfig + 0, // 13: hyapp.room.v1.AdminUpdateRoomRocketConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 16, // 14: hyapp.room.v1.AdminUpdateRoomRocketConfigRequest.config:type_name -> hyapp.room.v1.AdminRoomRocketConfig + 16, // 15: hyapp.room.v1.AdminUpdateRoomRocketConfigResponse.config:type_name -> hyapp.room.v1.AdminRoomRocketConfig + 0, // 16: hyapp.room.v1.AdminGetRoomSeatConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 21, // 17: hyapp.room.v1.AdminGetRoomSeatConfigResponse.config:type_name -> hyapp.room.v1.AdminRoomSeatConfig + 0, // 18: hyapp.room.v1.AdminUpdateRoomSeatConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 21, // 19: hyapp.room.v1.AdminUpdateRoomSeatConfigResponse.config:type_name -> hyapp.room.v1.AdminRoomSeatConfig + 26, // 20: hyapp.room.v1.AdminHumanRoomRobotConfig.country_pools:type_name -> hyapp.room.v1.AdminHumanRoomRobotCountryPool + 27, // 21: hyapp.room.v1.AdminHumanRoomRobotConfig.country_rules:type_name -> hyapp.room.v1.AdminHumanRoomRobotCountryRule + 0, // 22: hyapp.room.v1.AdminGetHumanRoomRobotConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 28, // 23: hyapp.room.v1.AdminGetHumanRoomRobotConfigResponse.config:type_name -> hyapp.room.v1.AdminHumanRoomRobotConfig + 0, // 24: hyapp.room.v1.AdminUpdateHumanRoomRobotConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 28, // 25: hyapp.room.v1.AdminUpdateHumanRoomRobotConfigRequest.config:type_name -> hyapp.room.v1.AdminHumanRoomRobotConfig + 28, // 26: hyapp.room.v1.AdminUpdateHumanRoomRobotConfigResponse.config:type_name -> hyapp.room.v1.AdminHumanRoomRobotConfig + 33, // 27: hyapp.room.v1.AdminRoomPin.room:type_name -> hyapp.room.v1.AdminRoomPinRoom + 0, // 28: hyapp.room.v1.AdminListRoomPinsRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 34, // 29: hyapp.room.v1.AdminListRoomPinsResponse.pins:type_name -> hyapp.room.v1.AdminRoomPin + 0, // 30: hyapp.room.v1.AdminCreateRoomPinRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 34, // 31: hyapp.room.v1.AdminCreateRoomPinResponse.pin:type_name -> hyapp.room.v1.AdminRoomPin + 0, // 32: hyapp.room.v1.AdminCancelRoomPinRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 34, // 33: hyapp.room.v1.AdminCancelRoomPinResponse.pin:type_name -> hyapp.room.v1.AdminRoomPin + 41, // 34: hyapp.room.v1.AdminRobotRoom.gift_rule:type_name -> hyapp.room.v1.AdminRobotRoomGiftRule + 0, // 35: hyapp.room.v1.AdminListRobotRoomsRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 42, // 36: hyapp.room.v1.AdminListRobotRoomsResponse.rooms:type_name -> hyapp.room.v1.AdminRobotRoom + 0, // 37: hyapp.room.v1.AdminCreateRobotRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 41, // 38: hyapp.room.v1.AdminCreateRobotRoomRequest.gift_rule:type_name -> hyapp.room.v1.AdminRobotRoomGiftRule + 42, // 39: hyapp.room.v1.AdminCreateRobotRoomResponse.room:type_name -> hyapp.room.v1.AdminRobotRoom + 0, // 40: hyapp.room.v1.AdminSetRobotRoomStatusRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 42, // 41: hyapp.room.v1.AdminSetRobotRoomStatusResponse.room:type_name -> hyapp.room.v1.AdminRobotRoom + 0, // 42: hyapp.room.v1.AdminRenameOwnerCountryCodeRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 0, // 43: hyapp.room.v1.AdminFilterAvailableRoomRobotsRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 4, // 44: hyapp.room.v1.RoomSnapshot.mic_seats:type_name -> hyapp.room.v1.SeatState + 2, // 45: hyapp.room.v1.RoomSnapshot.online_users:type_name -> hyapp.room.v1.RoomUser + 5, // 46: hyapp.room.v1.RoomSnapshot.gift_rank:type_name -> hyapp.room.v1.RankItem + 158, // 47: hyapp.room.v1.RoomSnapshot.room_ext:type_name -> hyapp.room.v1.RoomSnapshot.RoomExtEntry + 13, // 48: hyapp.room.v1.RoomSnapshot.rocket:type_name -> hyapp.room.v1.RoomRocketState + 53, // 49: hyapp.room.v1.RoomSnapshot.ban_states:type_name -> hyapp.room.v1.RoomBanState + 0, // 50: hyapp.room.v1.SaveRoomBackgroundRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 55, // 51: hyapp.room.v1.SaveRoomBackgroundResponse.background:type_name -> hyapp.room.v1.RoomBackgroundImage + 0, // 52: hyapp.room.v1.ListRoomBackgroundsRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 55, // 53: hyapp.room.v1.ListRoomBackgroundsResponse.backgrounds:type_name -> hyapp.room.v1.RoomBackgroundImage + 0, // 54: hyapp.room.v1.SetRoomBackgroundRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 55: hyapp.room.v1.SetRoomBackgroundResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 56: hyapp.room.v1.SetRoomBackgroundResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 55, // 57: hyapp.room.v1.SetRoomBackgroundResponse.background:type_name -> hyapp.room.v1.RoomBackgroundImage + 0, // 58: hyapp.room.v1.CreateRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 59: hyapp.room.v1.CreateRoomResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 60: hyapp.room.v1.CreateRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 61: hyapp.room.v1.UpdateRoomProfileRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 62: hyapp.room.v1.UpdateRoomProfileResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 63: hyapp.room.v1.UpdateRoomProfileResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 64: hyapp.room.v1.JoinRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 66, // 65: hyapp.room.v1.JoinRoomRequest.entry_vehicle:type_name -> hyapp.room.v1.RoomEntryVehicleSnapshot + 67, // 66: hyapp.room.v1.JoinRoomRequest.actor_display_profile:type_name -> hyapp.room.v1.RoomUserDisplayProfile + 1, // 67: hyapp.room.v1.JoinRoomResponse.result:type_name -> hyapp.room.v1.CommandResult + 2, // 68: hyapp.room.v1.JoinRoomResponse.user:type_name -> hyapp.room.v1.RoomUser + 54, // 69: hyapp.room.v1.JoinRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 69, // 70: hyapp.room.v1.JoinRoomResponse.effective_vip:type_name -> hyapp.room.v1.RoomEffectiveVipSnapshot + 0, // 71: hyapp.room.v1.RoomHeartbeatRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 72: hyapp.room.v1.RoomHeartbeatResponse.result:type_name -> hyapp.room.v1.CommandResult + 2, // 73: hyapp.room.v1.RoomHeartbeatResponse.user:type_name -> hyapp.room.v1.RoomUser + 54, // 74: hyapp.room.v1.RoomHeartbeatResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 75: hyapp.room.v1.LeaveRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 76: hyapp.room.v1.LeaveRoomResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 77: hyapp.room.v1.LeaveRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 78: hyapp.room.v1.CloseRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 79: hyapp.room.v1.CloseRoomResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 80: hyapp.room.v1.CloseRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 81: hyapp.room.v1.AdminListRoomsRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 77, // 82: hyapp.room.v1.AdminListRoomsResponse.rooms:type_name -> hyapp.room.v1.AdminRoomListItem + 0, // 83: hyapp.room.v1.AdminGetRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 77, // 84: hyapp.room.v1.AdminGetRoomResponse.room:type_name -> hyapp.room.v1.AdminRoomListItem + 0, // 85: hyapp.room.v1.AdminUpdateRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 86: hyapp.room.v1.AdminUpdateRoomResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 87: hyapp.room.v1.AdminUpdateRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 88: hyapp.room.v1.AdminDeleteRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 89: hyapp.room.v1.AdminDeleteRoomResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 90: hyapp.room.v1.AdminDeleteRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 91: hyapp.room.v1.MicUpRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 67, // 92: hyapp.room.v1.MicUpRequest.target_display_profile:type_name -> hyapp.room.v1.RoomUserDisplayProfile + 1, // 93: hyapp.room.v1.MicUpResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 94: hyapp.room.v1.MicUpResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 95: hyapp.room.v1.MicDownRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 67, // 96: hyapp.room.v1.MicDownRequest.target_display_profile:type_name -> hyapp.room.v1.RoomUserDisplayProfile + 1, // 97: hyapp.room.v1.MicDownResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 98: hyapp.room.v1.MicDownResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 99: hyapp.room.v1.ChangeMicSeatRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 67, // 100: hyapp.room.v1.ChangeMicSeatRequest.target_display_profile:type_name -> hyapp.room.v1.RoomUserDisplayProfile + 1, // 101: hyapp.room.v1.ChangeMicSeatResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 102: hyapp.room.v1.ChangeMicSeatResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 103: hyapp.room.v1.ConfirmMicPublishingRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 67, // 104: hyapp.room.v1.ConfirmMicPublishingRequest.target_display_profile:type_name -> hyapp.room.v1.RoomUserDisplayProfile + 1, // 105: hyapp.room.v1.ConfirmMicPublishingResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 106: hyapp.room.v1.ConfirmMicPublishingResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 107: hyapp.room.v1.MicHeartbeatRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 108: hyapp.room.v1.MicHeartbeatResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 109: hyapp.room.v1.MicHeartbeatResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 110: hyapp.room.v1.SetMicMuteRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 67, // 111: hyapp.room.v1.SetMicMuteRequest.target_display_profile:type_name -> hyapp.room.v1.RoomUserDisplayProfile + 1, // 112: hyapp.room.v1.SetMicMuteResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 113: hyapp.room.v1.SetMicMuteResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 114: hyapp.room.v1.ApplyRTCEventRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 115: hyapp.room.v1.ApplyRTCEventResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 116: hyapp.room.v1.ApplyRTCEventResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 117: hyapp.room.v1.SetMicSeatLockRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 118: hyapp.room.v1.SetMicSeatLockResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 119: hyapp.room.v1.SetMicSeatLockResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 120: hyapp.room.v1.SetChatEnabledRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 121: hyapp.room.v1.SetChatEnabledResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 122: hyapp.room.v1.SetChatEnabledResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 123: hyapp.room.v1.SetRoomPasswordRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 124: hyapp.room.v1.SetRoomPasswordResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 125: hyapp.room.v1.SetRoomPasswordResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 126: hyapp.room.v1.SetRoomAdminRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 127: hyapp.room.v1.SetRoomAdminResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 128: hyapp.room.v1.SetRoomAdminResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 129: hyapp.room.v1.MuteUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 130: hyapp.room.v1.MuteUserResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 131: hyapp.room.v1.MuteUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 132: hyapp.room.v1.KickUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 133: hyapp.room.v1.KickUserResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 134: hyapp.room.v1.KickUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 135: hyapp.room.v1.UnbanUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 136: hyapp.room.v1.UnbanUserResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 137: hyapp.room.v1.UnbanUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 138: hyapp.room.v1.SystemEvictUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 139: hyapp.room.v1.SystemEvictUserResponse.result:type_name -> hyapp.room.v1.CommandResult + 54, // 140: hyapp.room.v1.SystemEvictUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 7, // 141: hyapp.room.v1.SendGiftBatchTarget.lucky_gift:type_name -> hyapp.room.v1.LuckyGiftDrawResult + 118, // 142: hyapp.room.v1.SendGiftBatchDisplay.targets:type_name -> hyapp.room.v1.SendGiftBatchTarget + 0, // 143: hyapp.room.v1.SendGiftRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 116, // 144: hyapp.room.v1.SendGiftRequest.target_host_scopes:type_name -> hyapp.room.v1.SendGiftTargetHostScope + 117, // 145: hyapp.room.v1.SendGiftRequest.sender_display_profile:type_name -> hyapp.room.v1.SendGiftDisplayProfile + 117, // 146: hyapp.room.v1.SendGiftRequest.target_display_profiles:type_name -> hyapp.room.v1.SendGiftDisplayProfile + 1, // 147: hyapp.room.v1.SendGiftResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 148: hyapp.room.v1.SendGiftResponse.gift_rank:type_name -> hyapp.room.v1.RankItem + 54, // 149: hyapp.room.v1.SendGiftResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 13, // 150: hyapp.room.v1.SendGiftResponse.rocket:type_name -> hyapp.room.v1.RoomRocketState + 7, // 151: hyapp.room.v1.SendGiftResponse.lucky_gift:type_name -> hyapp.room.v1.LuckyGiftDrawResult + 7, // 152: hyapp.room.v1.SendGiftResponse.lucky_gifts:type_name -> hyapp.room.v1.LuckyGiftDrawResult + 119, // 153: hyapp.room.v1.SendGiftResponse.batch_display:type_name -> hyapp.room.v1.SendGiftBatchDisplay + 157, // 154: hyapp.room.v1.SendGiftResponse.v2:type_name -> hyapp.room.v1.SendGiftResultV2 + 0, // 155: hyapp.room.v1.ListRoomsRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 0, // 156: hyapp.room.v1.ListRoomsByOwnersRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 0, // 157: hyapp.room.v1.ListRoomFeedsRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 132, // 158: hyapp.room.v1.ListRoomFeedsRequest.related_users:type_name -> hyapp.room.v1.RoomFeedRelatedUser + 0, // 159: hyapp.room.v1.ListRoomGiftLeaderboardRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 133, // 160: hyapp.room.v1.ListRoomsResponse.rooms:type_name -> hyapp.room.v1.RoomListItem + 133, // 161: hyapp.room.v1.RoomGiftLeaderboardItem.room:type_name -> hyapp.room.v1.RoomListItem + 135, // 162: hyapp.room.v1.ListRoomGiftLeaderboardResponse.items:type_name -> hyapp.room.v1.RoomGiftLeaderboardItem + 0, // 163: hyapp.room.v1.ListRoomContributionRankRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 6, // 164: hyapp.room.v1.ListRoomContributionRankResponse.items:type_name -> hyapp.room.v1.RoomContributionRankItem + 0, // 165: hyapp.room.v1.GetMyRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 133, // 166: hyapp.room.v1.GetMyRoomResponse.room:type_name -> hyapp.room.v1.RoomListItem + 0, // 167: hyapp.room.v1.GetCurrentRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 0, // 168: hyapp.room.v1.GetRoomSnapshotRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 54, // 169: hyapp.room.v1.GetRoomSnapshotResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 170: hyapp.room.v1.GetRoomRocketRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 14, // 171: hyapp.room.v1.GetRoomRocketResponse.rocket:type_name -> hyapp.room.v1.RoomRocketInfo + 0, // 172: hyapp.room.v1.ListRoomOnlineUsersRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 2, // 173: hyapp.room.v1.ListRoomOnlineUsersResponse.users:type_name -> hyapp.room.v1.RoomUser + 3, // 174: hyapp.room.v1.ListRoomOnlineUsersResponse.items:type_name -> hyapp.room.v1.RoomOnlineUser + 0, // 175: hyapp.room.v1.ListRoomBannedUsersRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 149, // 176: hyapp.room.v1.ListRoomBannedUsersResponse.items:type_name -> hyapp.room.v1.RoomBannedUser + 0, // 177: hyapp.room.v1.FollowRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 0, // 178: hyapp.room.v1.UnfollowRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 7, // 179: hyapp.room.v1.SendGiftResultV2.lucky_gift:type_name -> hyapp.room.v1.LuckyGiftDrawResult + 7, // 180: hyapp.room.v1.SendGiftResultV2.lucky_gifts:type_name -> hyapp.room.v1.LuckyGiftDrawResult + 156, // 181: hyapp.room.v1.SendGiftResultV2.lucky_hits:type_name -> hyapp.room.v1.SendGiftLuckyHit + 119, // 182: hyapp.room.v1.SendGiftResultV2.batch_display:type_name -> hyapp.room.v1.SendGiftBatchDisplay + 5, // 183: hyapp.room.v1.SendGiftResultV2.gift_rank:type_name -> hyapp.room.v1.RankItem + 62, // 184: hyapp.room.v1.RoomCommandService.CreateRoom:input_type -> hyapp.room.v1.CreateRoomRequest + 64, // 185: hyapp.room.v1.RoomCommandService.UpdateRoomProfile:input_type -> hyapp.room.v1.UpdateRoomProfileRequest + 56, // 186: hyapp.room.v1.RoomCommandService.SaveRoomBackground:input_type -> hyapp.room.v1.SaveRoomBackgroundRequest + 60, // 187: hyapp.room.v1.RoomCommandService.SetRoomBackground:input_type -> hyapp.room.v1.SetRoomBackgroundRequest + 68, // 188: hyapp.room.v1.RoomCommandService.JoinRoom:input_type -> hyapp.room.v1.JoinRoomRequest + 71, // 189: hyapp.room.v1.RoomCommandService.RoomHeartbeat:input_type -> hyapp.room.v1.RoomHeartbeatRequest + 73, // 190: hyapp.room.v1.RoomCommandService.LeaveRoom:input_type -> hyapp.room.v1.LeaveRoomRequest + 75, // 191: hyapp.room.v1.RoomCommandService.CloseRoom:input_type -> hyapp.room.v1.CloseRoomRequest + 82, // 192: hyapp.room.v1.RoomCommandService.AdminUpdateRoom:input_type -> hyapp.room.v1.AdminUpdateRoomRequest + 84, // 193: hyapp.room.v1.RoomCommandService.AdminDeleteRoom:input_type -> hyapp.room.v1.AdminDeleteRoomRequest + 19, // 194: hyapp.room.v1.RoomCommandService.AdminUpdateRoomRocketConfig:input_type -> hyapp.room.v1.AdminUpdateRoomRocketConfigRequest + 24, // 195: hyapp.room.v1.RoomCommandService.AdminUpdateRoomSeatConfig:input_type -> hyapp.room.v1.AdminUpdateRoomSeatConfigRequest + 31, // 196: hyapp.room.v1.RoomCommandService.AdminUpdateHumanRoomRobotConfig:input_type -> hyapp.room.v1.AdminUpdateHumanRoomRobotConfigRequest + 37, // 197: hyapp.room.v1.RoomCommandService.AdminCreateRoomPin:input_type -> hyapp.room.v1.AdminCreateRoomPinRequest + 39, // 198: hyapp.room.v1.RoomCommandService.AdminCancelRoomPin:input_type -> hyapp.room.v1.AdminCancelRoomPinRequest + 45, // 199: hyapp.room.v1.RoomCommandService.AdminCreateRobotRoom:input_type -> hyapp.room.v1.AdminCreateRobotRoomRequest + 47, // 200: hyapp.room.v1.RoomCommandService.AdminSetRobotRoomStatus:input_type -> hyapp.room.v1.AdminSetRobotRoomStatusRequest + 49, // 201: hyapp.room.v1.RoomCommandService.AdminRenameOwnerCountryCode:input_type -> hyapp.room.v1.AdminRenameOwnerCountryCodeRequest + 86, // 202: hyapp.room.v1.RoomCommandService.MicUp:input_type -> hyapp.room.v1.MicUpRequest + 88, // 203: hyapp.room.v1.RoomCommandService.MicDown:input_type -> hyapp.room.v1.MicDownRequest + 90, // 204: hyapp.room.v1.RoomCommandService.ChangeMicSeat:input_type -> hyapp.room.v1.ChangeMicSeatRequest + 92, // 205: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:input_type -> hyapp.room.v1.ConfirmMicPublishingRequest + 94, // 206: hyapp.room.v1.RoomCommandService.MicHeartbeat:input_type -> hyapp.room.v1.MicHeartbeatRequest + 96, // 207: hyapp.room.v1.RoomCommandService.SetMicMute:input_type -> hyapp.room.v1.SetMicMuteRequest + 98, // 208: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:input_type -> hyapp.room.v1.ApplyRTCEventRequest + 100, // 209: hyapp.room.v1.RoomCommandService.SetMicSeatLock:input_type -> hyapp.room.v1.SetMicSeatLockRequest + 102, // 210: hyapp.room.v1.RoomCommandService.SetChatEnabled:input_type -> hyapp.room.v1.SetChatEnabledRequest + 104, // 211: hyapp.room.v1.RoomCommandService.SetRoomPassword:input_type -> hyapp.room.v1.SetRoomPasswordRequest + 106, // 212: hyapp.room.v1.RoomCommandService.SetRoomAdmin:input_type -> hyapp.room.v1.SetRoomAdminRequest + 108, // 213: hyapp.room.v1.RoomCommandService.MuteUser:input_type -> hyapp.room.v1.MuteUserRequest + 110, // 214: hyapp.room.v1.RoomCommandService.KickUser:input_type -> hyapp.room.v1.KickUserRequest + 112, // 215: hyapp.room.v1.RoomCommandService.UnbanUser:input_type -> hyapp.room.v1.UnbanUserRequest + 114, // 216: hyapp.room.v1.RoomCommandService.SystemEvictUser:input_type -> hyapp.room.v1.SystemEvictUserRequest + 120, // 217: hyapp.room.v1.RoomCommandService.SendGift:input_type -> hyapp.room.v1.SendGiftRequest + 152, // 218: hyapp.room.v1.RoomCommandService.FollowRoom:input_type -> hyapp.room.v1.FollowRoomRequest + 154, // 219: hyapp.room.v1.RoomCommandService.UnfollowRoom:input_type -> hyapp.room.v1.UnfollowRoomRequest + 122, // 220: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:input_type -> hyapp.room.v1.CheckSpeakPermissionRequest + 124, // 221: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:input_type -> hyapp.room.v1.VerifyRoomPresenceRequest + 126, // 222: hyapp.room.v1.RoomGuardService.ResolveRoomAppCode:input_type -> hyapp.room.v1.ResolveRoomAppCodeRequest + 78, // 223: hyapp.room.v1.RoomQueryService.AdminListRooms:input_type -> hyapp.room.v1.AdminListRoomsRequest + 80, // 224: hyapp.room.v1.RoomQueryService.AdminGetRoom:input_type -> hyapp.room.v1.AdminGetRoomRequest + 17, // 225: hyapp.room.v1.RoomQueryService.AdminGetRoomRocketConfig:input_type -> hyapp.room.v1.AdminGetRoomRocketConfigRequest + 22, // 226: hyapp.room.v1.RoomQueryService.AdminGetRoomSeatConfig:input_type -> hyapp.room.v1.AdminGetRoomSeatConfigRequest + 29, // 227: hyapp.room.v1.RoomQueryService.AdminGetHumanRoomRobotConfig:input_type -> hyapp.room.v1.AdminGetHumanRoomRobotConfigRequest + 35, // 228: hyapp.room.v1.RoomQueryService.AdminListRoomPins:input_type -> hyapp.room.v1.AdminListRoomPinsRequest + 43, // 229: hyapp.room.v1.RoomQueryService.AdminListRobotRooms:input_type -> hyapp.room.v1.AdminListRobotRoomsRequest + 51, // 230: hyapp.room.v1.RoomQueryService.AdminFilterAvailableRoomRobots:input_type -> hyapp.room.v1.AdminFilterAvailableRoomRobotsRequest + 128, // 231: hyapp.room.v1.RoomQueryService.ListRooms:input_type -> hyapp.room.v1.ListRoomsRequest + 129, // 232: hyapp.room.v1.RoomQueryService.ListRoomsByOwners:input_type -> hyapp.room.v1.ListRoomsByOwnersRequest + 130, // 233: hyapp.room.v1.RoomQueryService.ListRoomFeeds:input_type -> hyapp.room.v1.ListRoomFeedsRequest + 131, // 234: hyapp.room.v1.RoomQueryService.ListRoomGiftLeaderboard:input_type -> hyapp.room.v1.ListRoomGiftLeaderboardRequest + 137, // 235: hyapp.room.v1.RoomQueryService.ListRoomContributionRank:input_type -> hyapp.room.v1.ListRoomContributionRankRequest + 139, // 236: hyapp.room.v1.RoomQueryService.GetMyRoom:input_type -> hyapp.room.v1.GetMyRoomRequest + 141, // 237: hyapp.room.v1.RoomQueryService.GetCurrentRoom:input_type -> hyapp.room.v1.GetCurrentRoomRequest + 143, // 238: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:input_type -> hyapp.room.v1.GetRoomSnapshotRequest + 58, // 239: hyapp.room.v1.RoomQueryService.ListRoomBackgrounds:input_type -> hyapp.room.v1.ListRoomBackgroundsRequest + 145, // 240: hyapp.room.v1.RoomQueryService.GetRoomRocket:input_type -> hyapp.room.v1.GetRoomRocketRequest + 147, // 241: hyapp.room.v1.RoomQueryService.ListRoomOnlineUsers:input_type -> hyapp.room.v1.ListRoomOnlineUsersRequest + 150, // 242: hyapp.room.v1.RoomQueryService.ListRoomBannedUsers:input_type -> hyapp.room.v1.ListRoomBannedUsersRequest + 63, // 243: hyapp.room.v1.RoomCommandService.CreateRoom:output_type -> hyapp.room.v1.CreateRoomResponse + 65, // 244: hyapp.room.v1.RoomCommandService.UpdateRoomProfile:output_type -> hyapp.room.v1.UpdateRoomProfileResponse + 57, // 245: hyapp.room.v1.RoomCommandService.SaveRoomBackground:output_type -> hyapp.room.v1.SaveRoomBackgroundResponse + 61, // 246: hyapp.room.v1.RoomCommandService.SetRoomBackground:output_type -> hyapp.room.v1.SetRoomBackgroundResponse + 70, // 247: hyapp.room.v1.RoomCommandService.JoinRoom:output_type -> hyapp.room.v1.JoinRoomResponse + 72, // 248: hyapp.room.v1.RoomCommandService.RoomHeartbeat:output_type -> hyapp.room.v1.RoomHeartbeatResponse + 74, // 249: hyapp.room.v1.RoomCommandService.LeaveRoom:output_type -> hyapp.room.v1.LeaveRoomResponse + 76, // 250: hyapp.room.v1.RoomCommandService.CloseRoom:output_type -> hyapp.room.v1.CloseRoomResponse + 83, // 251: hyapp.room.v1.RoomCommandService.AdminUpdateRoom:output_type -> hyapp.room.v1.AdminUpdateRoomResponse + 85, // 252: hyapp.room.v1.RoomCommandService.AdminDeleteRoom:output_type -> hyapp.room.v1.AdminDeleteRoomResponse + 20, // 253: hyapp.room.v1.RoomCommandService.AdminUpdateRoomRocketConfig:output_type -> hyapp.room.v1.AdminUpdateRoomRocketConfigResponse + 25, // 254: hyapp.room.v1.RoomCommandService.AdminUpdateRoomSeatConfig:output_type -> hyapp.room.v1.AdminUpdateRoomSeatConfigResponse + 32, // 255: hyapp.room.v1.RoomCommandService.AdminUpdateHumanRoomRobotConfig:output_type -> hyapp.room.v1.AdminUpdateHumanRoomRobotConfigResponse + 38, // 256: hyapp.room.v1.RoomCommandService.AdminCreateRoomPin:output_type -> hyapp.room.v1.AdminCreateRoomPinResponse + 40, // 257: hyapp.room.v1.RoomCommandService.AdminCancelRoomPin:output_type -> hyapp.room.v1.AdminCancelRoomPinResponse + 46, // 258: hyapp.room.v1.RoomCommandService.AdminCreateRobotRoom:output_type -> hyapp.room.v1.AdminCreateRobotRoomResponse + 48, // 259: hyapp.room.v1.RoomCommandService.AdminSetRobotRoomStatus:output_type -> hyapp.room.v1.AdminSetRobotRoomStatusResponse + 50, // 260: hyapp.room.v1.RoomCommandService.AdminRenameOwnerCountryCode:output_type -> hyapp.room.v1.AdminRenameOwnerCountryCodeResponse + 87, // 261: hyapp.room.v1.RoomCommandService.MicUp:output_type -> hyapp.room.v1.MicUpResponse + 89, // 262: hyapp.room.v1.RoomCommandService.MicDown:output_type -> hyapp.room.v1.MicDownResponse + 91, // 263: hyapp.room.v1.RoomCommandService.ChangeMicSeat:output_type -> hyapp.room.v1.ChangeMicSeatResponse + 93, // 264: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:output_type -> hyapp.room.v1.ConfirmMicPublishingResponse + 95, // 265: hyapp.room.v1.RoomCommandService.MicHeartbeat:output_type -> hyapp.room.v1.MicHeartbeatResponse + 97, // 266: hyapp.room.v1.RoomCommandService.SetMicMute:output_type -> hyapp.room.v1.SetMicMuteResponse + 99, // 267: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:output_type -> hyapp.room.v1.ApplyRTCEventResponse + 101, // 268: hyapp.room.v1.RoomCommandService.SetMicSeatLock:output_type -> hyapp.room.v1.SetMicSeatLockResponse + 103, // 269: hyapp.room.v1.RoomCommandService.SetChatEnabled:output_type -> hyapp.room.v1.SetChatEnabledResponse + 105, // 270: hyapp.room.v1.RoomCommandService.SetRoomPassword:output_type -> hyapp.room.v1.SetRoomPasswordResponse + 107, // 271: hyapp.room.v1.RoomCommandService.SetRoomAdmin:output_type -> hyapp.room.v1.SetRoomAdminResponse + 109, // 272: hyapp.room.v1.RoomCommandService.MuteUser:output_type -> hyapp.room.v1.MuteUserResponse + 111, // 273: hyapp.room.v1.RoomCommandService.KickUser:output_type -> hyapp.room.v1.KickUserResponse + 113, // 274: hyapp.room.v1.RoomCommandService.UnbanUser:output_type -> hyapp.room.v1.UnbanUserResponse + 115, // 275: hyapp.room.v1.RoomCommandService.SystemEvictUser:output_type -> hyapp.room.v1.SystemEvictUserResponse + 121, // 276: hyapp.room.v1.RoomCommandService.SendGift:output_type -> hyapp.room.v1.SendGiftResponse + 153, // 277: hyapp.room.v1.RoomCommandService.FollowRoom:output_type -> hyapp.room.v1.FollowRoomResponse + 155, // 278: hyapp.room.v1.RoomCommandService.UnfollowRoom:output_type -> hyapp.room.v1.UnfollowRoomResponse + 123, // 279: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:output_type -> hyapp.room.v1.CheckSpeakPermissionResponse + 125, // 280: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:output_type -> hyapp.room.v1.VerifyRoomPresenceResponse + 127, // 281: hyapp.room.v1.RoomGuardService.ResolveRoomAppCode:output_type -> hyapp.room.v1.ResolveRoomAppCodeResponse + 79, // 282: hyapp.room.v1.RoomQueryService.AdminListRooms:output_type -> hyapp.room.v1.AdminListRoomsResponse + 81, // 283: hyapp.room.v1.RoomQueryService.AdminGetRoom:output_type -> hyapp.room.v1.AdminGetRoomResponse + 18, // 284: hyapp.room.v1.RoomQueryService.AdminGetRoomRocketConfig:output_type -> hyapp.room.v1.AdminGetRoomRocketConfigResponse + 23, // 285: hyapp.room.v1.RoomQueryService.AdminGetRoomSeatConfig:output_type -> hyapp.room.v1.AdminGetRoomSeatConfigResponse + 30, // 286: hyapp.room.v1.RoomQueryService.AdminGetHumanRoomRobotConfig:output_type -> hyapp.room.v1.AdminGetHumanRoomRobotConfigResponse + 36, // 287: hyapp.room.v1.RoomQueryService.AdminListRoomPins:output_type -> hyapp.room.v1.AdminListRoomPinsResponse + 44, // 288: hyapp.room.v1.RoomQueryService.AdminListRobotRooms:output_type -> hyapp.room.v1.AdminListRobotRoomsResponse + 52, // 289: hyapp.room.v1.RoomQueryService.AdminFilterAvailableRoomRobots:output_type -> hyapp.room.v1.AdminFilterAvailableRoomRobotsResponse + 134, // 290: hyapp.room.v1.RoomQueryService.ListRooms:output_type -> hyapp.room.v1.ListRoomsResponse + 134, // 291: hyapp.room.v1.RoomQueryService.ListRoomsByOwners:output_type -> hyapp.room.v1.ListRoomsResponse + 134, // 292: hyapp.room.v1.RoomQueryService.ListRoomFeeds:output_type -> hyapp.room.v1.ListRoomsResponse + 136, // 293: hyapp.room.v1.RoomQueryService.ListRoomGiftLeaderboard:output_type -> hyapp.room.v1.ListRoomGiftLeaderboardResponse + 138, // 294: hyapp.room.v1.RoomQueryService.ListRoomContributionRank:output_type -> hyapp.room.v1.ListRoomContributionRankResponse + 140, // 295: hyapp.room.v1.RoomQueryService.GetMyRoom:output_type -> hyapp.room.v1.GetMyRoomResponse + 142, // 296: hyapp.room.v1.RoomQueryService.GetCurrentRoom:output_type -> hyapp.room.v1.GetCurrentRoomResponse + 144, // 297: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:output_type -> hyapp.room.v1.GetRoomSnapshotResponse + 59, // 298: hyapp.room.v1.RoomQueryService.ListRoomBackgrounds:output_type -> hyapp.room.v1.ListRoomBackgroundsResponse + 146, // 299: hyapp.room.v1.RoomQueryService.GetRoomRocket:output_type -> hyapp.room.v1.GetRoomRocketResponse + 148, // 300: hyapp.room.v1.RoomQueryService.ListRoomOnlineUsers:output_type -> hyapp.room.v1.ListRoomOnlineUsersResponse + 151, // 301: hyapp.room.v1.RoomQueryService.ListRoomBannedUsers:output_type -> hyapp.room.v1.ListRoomBannedUsersResponse + 243, // [243:302] is the sub-list for method output_type + 184, // [184:243] is the sub-list for method input_type + 184, // [184:184] is the sub-list for extension type_name + 184, // [184:184] is the sub-list for extension extendee + 0, // [0:184] is the sub-list for field type_name } func init() { file_proto_room_v1_room_proto_init() } @@ -15293,14 +15944,14 @@ func file_proto_room_v1_room_proto_init() { return } file_proto_room_v1_room_proto_msgTypes[64].OneofWrappers = []any{} - file_proto_room_v1_room_proto_msgTypes[81].OneofWrappers = []any{} + file_proto_room_v1_room_proto_msgTypes[82].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_room_v1_room_proto_rawDesc, NumEnums: 0, - NumMessages: 154, + NumMessages: 159, NumExtensions: 0, NumServices: 3, }, diff --git a/api/proto/room/v1/room.proto b/api/proto/room/v1/room.proto index a5b44452..cffe79cb 100644 --- a/api/proto/room/v1/room.proto +++ b/api/proto/room/v1/room.proto @@ -110,6 +110,8 @@ message LuckyGiftDrawResult { int64 coin_balance_after = 17; // target_user_id 是本次幸运礼物抽奖对应的收礼用户;多目标送礼用它关联结果。 int64 target_user_id = 18; + // hits 只包含 gift_count 微批中实际中奖的位置;旧客户端忽略该追加字段。 + repeated SendGiftLuckyHit hits = 19; } // RoomRocketRewardItem 是后台配置给客户端展示的火箭奖励候选项。 @@ -680,6 +682,18 @@ message JoinRoomRequest { RoomUserDisplayProfile actor_display_profile = 8; // response_mode=lite 只裁剪响应快照,不改变 Room Cell 命令提交、outbox 或 IM 事件语义。 string response_mode = 9; + // presence_recovery 只用于客户端仍在房间但业务 presence 已超时清理的恢复。 + // 恢复成功只补回 Room Cell 在线状态,不发布 RoomUserJoined IM/outbox,避免重复入场表现和统计。 + bool presence_recovery = 10; +} + +// RoomEffectiveVipSnapshot 是 room-service 在进房时从 wallet-service 固化的轻量 VIP 展示事实。 +// 权益执行仍以后端 effective_benefits 为准,客户端不能用该快照自行声明房间权限。 +message RoomEffectiveVipSnapshot { + string program_type = 1; + int32 level = 2; + string name = 3; + bool room_entry_notice_enabled = 4; } // JoinRoomResponse 返回加入后的房间快照。 @@ -687,6 +701,7 @@ message JoinRoomResponse { CommandResult result = 1; RoomUser user = 2; RoomSnapshot room = 3; + RoomEffectiveVipSnapshot effective_vip = 4; } // RoomHeartbeatRequest 显式刷新已经存在的房间业务 presence。 @@ -1143,6 +1158,9 @@ message SendGiftRequest { repeated SendGiftDisplayProfile target_display_profiles = 17; // display_mode=batch 只由新 batch-send HTTP 入口设置,用来生成单条多人展示 IM;旧 send 入口保持空值。 string display_mode = 18; + // combo_session_id + batch_seq 只描述 Flutter 微批会话,command_id 仍是跨服务唯一幂等键。 + string combo_session_id = 19; + int64 batch_seq = 20; } // SendGiftResponse 返回扣费成功并落到房间后的状态结果。 @@ -1163,6 +1181,8 @@ message SendGiftResponse { int64 gift_income_balance_after = 10; // batch_display 是新 batch-send 接口的多人展示快照;旧 send 入口保持空值。 SendGiftBatchDisplay batch_display = 11; + // v2 保存并重放首次成功的原始结算结果;追加字段不改变旧 Flutter 对顶层 V1 字段的读取。 + SendGiftResultV2 v2 = 12; } // CheckSpeakPermissionRequest 让腾讯云 IM 发言回调或 gateway 在公屏前同步问房间业务态。 @@ -1195,8 +1215,20 @@ message VerifyRoomPresenceResponse { int64 room_version = 3; } -// ListRoomsRequest 查询当前用户所在区域可见的房间列表。 -// visible_region_id 必须来自 gateway 对 user-service 的 GetUser 结果,客户端不能直接提交。 +// ResolveRoomAppCodeRequest 仅供已验签的三方回调按全局 room_id 解析租户。 +// 该入口不接受客户端 app_code,避免固定 URL 参数把 Fami 事件错误路由到 Lalu。 +message ResolveRoomAppCodeRequest { + string request_id = 1; + string room_id = 2; +} + +message ResolveRoomAppCodeResponse { + string app_code = 1; +} + +// ListRoomsRequest 查询首页公共房间列表。 +// visible_region_id 是 gateway 解析的实际查询区域:默认取 viewer 区域,显式筛选时取已校验的 filter_region_id。 +// gateway 同时写 filter_region_id 绑定游标;客户端不能直接构造任一内部字段。 message ListRoomsRequest { RequestMeta meta = 1; int64 viewer_user_id = 2; @@ -1205,12 +1237,14 @@ message ListRoomsRequest { string cursor = 5; int32 limit = 6; string query = 7; - // country_code 是可选国家筛选值;gateway 只能传当前用户区域国家列表内的国家码。 + // country_code 是可选国家筛选值;gateway 必须先确认它属于 active 区域。 string country_code = 8; - // viewer_country_code 是当前用户国家,用于区域内国家优先排序,不作为客户端可选筛选条件。 + // viewer_country_code 是当前用户国家,用于 All/区域列表的本国家优先排序。 string viewer_country_code = 9; - // all_visible_regions 只能由 gateway 根据后台白名单配置注入;为 true 时列表不按 visible_region_id 过滤。 + // all_visible_regions 只能由 gateway 根据 App 策略或后台白名单注入;为 true 时列表不按 visible_region_id 过滤。 bool all_visible_regions = 10; + // filter_region_id 是 gateway 校验过的 active 区域筛选;它与 all_visible_regions/country_code 互斥。 + int64 filter_region_id = 11; } // ListRoomsByOwnersRequest 查询一组房主名下 active 房间。 @@ -1500,6 +1534,7 @@ service RoomCommandService { service RoomGuardService { rpc CheckSpeakPermission(CheckSpeakPermissionRequest) returns (CheckSpeakPermissionResponse); rpc VerifyRoomPresence(VerifyRoomPresenceRequest) returns (VerifyRoomPresenceResponse); + rpc ResolveRoomAppCode(ResolveRoomAppCodeRequest) returns (ResolveRoomAppCodeResponse); } // RoomQueryService 承载不会改变 Room Cell 状态的读模型查询。 @@ -1525,3 +1560,41 @@ service RoomQueryService { rpc ListRoomOnlineUsers(ListRoomOnlineUsersRequest) returns (ListRoomOnlineUsersResponse); rpc ListRoomBannedUsers(ListRoomBannedUsersRequest) returns (ListRoomBannedUsersResponse); } + +// SendGiftLuckyHit 是 V2 微批中需要逐次播放的精简中奖命中。 +// 声明追加在文件尾以保持既有 protobuf message index 稳定;字段可在前面的消息中正常引用。 +message SendGiftLuckyHit { + int32 gift_index = 1; + string draw_id = 2; + int64 target_user_id = 3; + string selected_tier_id = 4; + int64 multiplier_ppm = 5; + int64 reward_coins = 6; +} + +// SendGiftResultV2 是可持久化、可原样重放的送礼结算结果。 +// 旧客户端继续读取 SendGiftResponse 顶层 V1 字段;V2 客户端只以该对象判断幂等、 +// 提交顺序和实际结算数量,避免网络重试时拿“当前房间快照”冒充原始结算结果。 +message SendGiftResultV2 { + int32 api_version = 1; + string command_id = 2; + string combo_session_id = 3; + int64 batch_seq = 4; + bool idempotent_replay = 5; + int64 committed_room_version = 6; + string billing_receipt_id = 7; + int64 coin_balance_after = 8; + int64 gift_income_balance_after = 9; + int32 settled_gift_count = 10; + repeated int64 settled_target_user_ids = 11; + // balance_version 是 sender 被扣费资产在该批次完成后的 wallet 账户版本;幂等重放保持首次值。 + int64 balance_version = 12; + LuckyGiftDrawResult lucky_gift = 13; + repeated LuckyGiftDrawResult lucky_gifts = 14; + repeated SendGiftLuckyHit lucky_hits = 15; + SendGiftBatchDisplay batch_display = 16; + int64 room_heat = 17; + repeated RankItem gift_rank = 18; + // operation_status 当前成功响应固定为 committed;该字段让 V2 契约可以明确区分未来的异步受理态。 + string operation_status = 19; +} diff --git a/api/proto/room/v1/room_grpc.pb.go b/api/proto/room/v1/room_grpc.pb.go index 21838daa..653e7f90 100644 --- a/api/proto/room/v1/room_grpc.pb.go +++ b/api/proto/room/v1/room_grpc.pb.go @@ -1457,6 +1457,7 @@ var RoomCommandService_ServiceDesc = grpc.ServiceDesc{ const ( RoomGuardService_CheckSpeakPermission_FullMethodName = "/hyapp.room.v1.RoomGuardService/CheckSpeakPermission" RoomGuardService_VerifyRoomPresence_FullMethodName = "/hyapp.room.v1.RoomGuardService/VerifyRoomPresence" + RoomGuardService_ResolveRoomAppCode_FullMethodName = "/hyapp.room.v1.RoomGuardService/ResolveRoomAppCode" ) // RoomGuardServiceClient is the client API for RoomGuardService service. @@ -1467,6 +1468,7 @@ const ( type RoomGuardServiceClient interface { CheckSpeakPermission(ctx context.Context, in *CheckSpeakPermissionRequest, opts ...grpc.CallOption) (*CheckSpeakPermissionResponse, error) VerifyRoomPresence(ctx context.Context, in *VerifyRoomPresenceRequest, opts ...grpc.CallOption) (*VerifyRoomPresenceResponse, error) + ResolveRoomAppCode(ctx context.Context, in *ResolveRoomAppCodeRequest, opts ...grpc.CallOption) (*ResolveRoomAppCodeResponse, error) } type roomGuardServiceClient struct { @@ -1497,6 +1499,16 @@ func (c *roomGuardServiceClient) VerifyRoomPresence(ctx context.Context, in *Ver return out, nil } +func (c *roomGuardServiceClient) ResolveRoomAppCode(ctx context.Context, in *ResolveRoomAppCodeRequest, opts ...grpc.CallOption) (*ResolveRoomAppCodeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ResolveRoomAppCodeResponse) + err := c.cc.Invoke(ctx, RoomGuardService_ResolveRoomAppCode_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // RoomGuardServiceServer is the server API for RoomGuardService service. // All implementations must embed UnimplementedRoomGuardServiceServer // for forward compatibility. @@ -1505,6 +1517,7 @@ func (c *roomGuardServiceClient) VerifyRoomPresence(ctx context.Context, in *Ver type RoomGuardServiceServer interface { CheckSpeakPermission(context.Context, *CheckSpeakPermissionRequest) (*CheckSpeakPermissionResponse, error) VerifyRoomPresence(context.Context, *VerifyRoomPresenceRequest) (*VerifyRoomPresenceResponse, error) + ResolveRoomAppCode(context.Context, *ResolveRoomAppCodeRequest) (*ResolveRoomAppCodeResponse, error) mustEmbedUnimplementedRoomGuardServiceServer() } @@ -1521,6 +1534,9 @@ func (UnimplementedRoomGuardServiceServer) CheckSpeakPermission(context.Context, func (UnimplementedRoomGuardServiceServer) VerifyRoomPresence(context.Context, *VerifyRoomPresenceRequest) (*VerifyRoomPresenceResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method VerifyRoomPresence not implemented") } +func (UnimplementedRoomGuardServiceServer) ResolveRoomAppCode(context.Context, *ResolveRoomAppCodeRequest) (*ResolveRoomAppCodeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ResolveRoomAppCode not implemented") +} func (UnimplementedRoomGuardServiceServer) mustEmbedUnimplementedRoomGuardServiceServer() {} func (UnimplementedRoomGuardServiceServer) testEmbeddedByValue() {} @@ -1578,6 +1594,24 @@ func _RoomGuardService_VerifyRoomPresence_Handler(srv interface{}, ctx context.C return interceptor(ctx, in, info, handler) } +func _RoomGuardService_ResolveRoomAppCode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ResolveRoomAppCodeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoomGuardServiceServer).ResolveRoomAppCode(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoomGuardService_ResolveRoomAppCode_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoomGuardServiceServer).ResolveRoomAppCode(ctx, req.(*ResolveRoomAppCodeRequest)) + } + return interceptor(ctx, in, info, handler) +} + // RoomGuardService_ServiceDesc is the grpc.ServiceDesc for RoomGuardService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -1593,6 +1627,10 @@ var RoomGuardService_ServiceDesc = grpc.ServiceDesc{ MethodName: "VerifyRoomPresence", Handler: _RoomGuardService_VerifyRoomPresence_Handler, }, + { + MethodName: "ResolveRoomAppCode", + Handler: _RoomGuardService_ResolveRoomAppCode_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/room/v1/room.proto", diff --git a/api/proto/user/v1/host.pb.go b/api/proto/user/v1/host.pb.go index 1928c5ba..ab9a80b1 100644 --- a/api/proto/user/v1/host.pb.go +++ b/api/proto/user/v1/host.pb.go @@ -884,6 +884,8 @@ type UserRoleSummary struct { CoinSellerStatus string `protobuf:"bytes,11,opt,name=coin_seller_status,json=coinSellerStatus,proto3" json:"coin_seller_status,omitempty"` PendingRoleInvitations int64 `protobuf:"varint,12,opt,name=pending_role_invitations,json=pendingRoleInvitations,proto3" json:"pending_role_invitations,omitempty"` IsManager bool `protobuf:"varint,13,opt,name=is_manager,json=isManager,proto3" json:"is_manager,omitempty"` + // roles 给后台等需要多身份并列展示的调用方提供稳定字符串集合;原布尔字段继续兼容 App 入口显隐。 + Roles []string `protobuf:"bytes,14,rep,name=roles,proto3" json:"roles,omitempty"` } func (x *UserRoleSummary) Reset() { @@ -1007,6 +1009,13 @@ func (x *UserRoleSummary) GetIsManager() bool { return false } +func (x *UserRoleSummary) GetRoles() []string { + if x != nil { + return x.Roles + } + return nil +} + // AgencyMembership 是 host 与 Agency 的有时效关系事实。 type AgencyMembership struct { state protoimpl.MessageState @@ -4518,6 +4527,175 @@ func (x *CheckBusinessCapabilityResponse) GetReason() string { return "" } +// RoleScopePolicy 是 App 级角色拓展边界;调用方只消费 scope,不直接判断具体 app_code。 +type RoleScopePolicy struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Scene string `protobuf:"bytes,1,opt,name=scene,proto3" json:"scene,omitempty"` + BaseScope string `protobuf:"bytes,2,opt,name=base_scope,json=baseScope,proto3" json:"base_scope,omitempty"` + RegionExpansionConfigurable bool `protobuf:"varint,3,opt,name=region_expansion_configurable,json=regionExpansionConfigurable,proto3" json:"region_expansion_configurable,omitempty"` + // expanded_scope 是经理通用范围开关开启后的范围;调用方不能自行假设一定是 region。 + ExpandedScope string `protobuf:"bytes,4,opt,name=expanded_scope,json=expandedScope,proto3" json:"expanded_scope,omitempty"` +} + +func (x *RoleScopePolicy) Reset() { + *x = RoleScopePolicy{} + mi := &file_proto_user_v1_host_proto_msgTypes[64] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoleScopePolicy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoleScopePolicy) ProtoMessage() {} + +func (x *RoleScopePolicy) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_host_proto_msgTypes[64] + 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 RoleScopePolicy.ProtoReflect.Descriptor instead. +func (*RoleScopePolicy) Descriptor() ([]byte, []int) { + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{64} +} + +func (x *RoleScopePolicy) GetScene() string { + if x != nil { + return x.Scene + } + return "" +} + +func (x *RoleScopePolicy) GetBaseScope() string { + if x != nil { + return x.BaseScope + } + return "" +} + +func (x *RoleScopePolicy) GetRegionExpansionConfigurable() bool { + if x != nil { + return x.RegionExpansionConfigurable + } + return false +} + +func (x *RoleScopePolicy) GetExpandedScope() string { + if x != nil { + return x.ExpandedScope + } + return "" +} + +type GetRoleScopePolicyRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + Scene string `protobuf:"bytes,2,opt,name=scene,proto3" json:"scene,omitempty"` +} + +func (x *GetRoleScopePolicyRequest) Reset() { + *x = GetRoleScopePolicyRequest{} + mi := &file_proto_user_v1_host_proto_msgTypes[65] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRoleScopePolicyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRoleScopePolicyRequest) ProtoMessage() {} + +func (x *GetRoleScopePolicyRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_host_proto_msgTypes[65] + 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 GetRoleScopePolicyRequest.ProtoReflect.Descriptor instead. +func (*GetRoleScopePolicyRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{65} +} + +func (x *GetRoleScopePolicyRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *GetRoleScopePolicyRequest) GetScene() string { + if x != nil { + return x.Scene + } + return "" +} + +type GetRoleScopePolicyResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Policy *RoleScopePolicy `protobuf:"bytes,1,opt,name=policy,proto3" json:"policy,omitempty"` +} + +func (x *GetRoleScopePolicyResponse) Reset() { + *x = GetRoleScopePolicyResponse{} + mi := &file_proto_user_v1_host_proto_msgTypes[66] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRoleScopePolicyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRoleScopePolicyResponse) ProtoMessage() {} + +func (x *GetRoleScopePolicyResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_host_proto_msgTypes[66] + 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 GetRoleScopePolicyResponse.ProtoReflect.Descriptor instead. +func (*GetRoleScopePolicyResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{66} +} + +func (x *GetRoleScopePolicyResponse) GetPolicy() *RoleScopePolicy { + if x != nil { + return x.Policy + } + return nil +} + type GetAgencyMembersRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -4530,7 +4708,7 @@ type GetAgencyMembersRequest struct { func (x *GetAgencyMembersRequest) Reset() { *x = GetAgencyMembersRequest{} - mi := &file_proto_user_v1_host_proto_msgTypes[64] + mi := &file_proto_user_v1_host_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4542,7 +4720,7 @@ func (x *GetAgencyMembersRequest) String() string { func (*GetAgencyMembersRequest) ProtoMessage() {} func (x *GetAgencyMembersRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[64] + mi := &file_proto_user_v1_host_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4555,7 +4733,7 @@ func (x *GetAgencyMembersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAgencyMembersRequest.ProtoReflect.Descriptor instead. func (*GetAgencyMembersRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{64} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{67} } func (x *GetAgencyMembersRequest) GetMeta() *RequestMeta { @@ -4589,7 +4767,7 @@ type GetAgencyMembersResponse struct { func (x *GetAgencyMembersResponse) Reset() { *x = GetAgencyMembersResponse{} - mi := &file_proto_user_v1_host_proto_msgTypes[65] + mi := &file_proto_user_v1_host_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4601,7 +4779,7 @@ func (x *GetAgencyMembersResponse) String() string { func (*GetAgencyMembersResponse) ProtoMessage() {} func (x *GetAgencyMembersResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[65] + mi := &file_proto_user_v1_host_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4614,7 +4792,7 @@ func (x *GetAgencyMembersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAgencyMembersResponse.ProtoReflect.Descriptor instead. func (*GetAgencyMembersResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{65} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{68} } func (x *GetAgencyMembersResponse) GetMemberships() []*AgencyMembership { @@ -4636,7 +4814,7 @@ type GetAgencyApplicationsRequest struct { func (x *GetAgencyApplicationsRequest) Reset() { *x = GetAgencyApplicationsRequest{} - mi := &file_proto_user_v1_host_proto_msgTypes[66] + mi := &file_proto_user_v1_host_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4648,7 +4826,7 @@ func (x *GetAgencyApplicationsRequest) String() string { func (*GetAgencyApplicationsRequest) ProtoMessage() {} func (x *GetAgencyApplicationsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[66] + mi := &file_proto_user_v1_host_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4661,7 +4839,7 @@ func (x *GetAgencyApplicationsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAgencyApplicationsRequest.ProtoReflect.Descriptor instead. func (*GetAgencyApplicationsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{66} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{69} } func (x *GetAgencyApplicationsRequest) GetMeta() *RequestMeta { @@ -4695,7 +4873,7 @@ type GetAgencyApplicationsResponse struct { func (x *GetAgencyApplicationsResponse) Reset() { *x = GetAgencyApplicationsResponse{} - mi := &file_proto_user_v1_host_proto_msgTypes[67] + mi := &file_proto_user_v1_host_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4707,7 +4885,7 @@ func (x *GetAgencyApplicationsResponse) String() string { func (*GetAgencyApplicationsResponse) ProtoMessage() {} func (x *GetAgencyApplicationsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[67] + mi := &file_proto_user_v1_host_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4720,7 +4898,7 @@ func (x *GetAgencyApplicationsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAgencyApplicationsResponse.ProtoReflect.Descriptor instead. func (*GetAgencyApplicationsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{67} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{70} } func (x *GetAgencyApplicationsResponse) GetApplications() []*AgencyApplication { @@ -4744,7 +4922,7 @@ type CreateBDLeaderRequest struct { func (x *CreateBDLeaderRequest) Reset() { *x = CreateBDLeaderRequest{} - mi := &file_proto_user_v1_host_proto_msgTypes[68] + mi := &file_proto_user_v1_host_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4756,7 +4934,7 @@ func (x *CreateBDLeaderRequest) String() string { func (*CreateBDLeaderRequest) ProtoMessage() {} func (x *CreateBDLeaderRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[68] + mi := &file_proto_user_v1_host_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4769,7 +4947,7 @@ func (x *CreateBDLeaderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateBDLeaderRequest.ProtoReflect.Descriptor instead. func (*CreateBDLeaderRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{68} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{71} } func (x *CreateBDLeaderRequest) GetMeta() *RequestMeta { @@ -4817,7 +4995,7 @@ type CreateBDLeaderResponse struct { func (x *CreateBDLeaderResponse) Reset() { *x = CreateBDLeaderResponse{} - mi := &file_proto_user_v1_host_proto_msgTypes[69] + mi := &file_proto_user_v1_host_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4829,7 +5007,7 @@ func (x *CreateBDLeaderResponse) String() string { func (*CreateBDLeaderResponse) ProtoMessage() {} func (x *CreateBDLeaderResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[69] + mi := &file_proto_user_v1_host_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4842,7 +5020,7 @@ func (x *CreateBDLeaderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateBDLeaderResponse.ProtoReflect.Descriptor instead. func (*CreateBDLeaderResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{69} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{72} } func (x *CreateBDLeaderResponse) GetBdProfile() *BDProfile { @@ -4867,7 +5045,7 @@ type CreateBDRequest struct { func (x *CreateBDRequest) Reset() { *x = CreateBDRequest{} - mi := &file_proto_user_v1_host_proto_msgTypes[70] + mi := &file_proto_user_v1_host_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4879,7 +5057,7 @@ func (x *CreateBDRequest) String() string { func (*CreateBDRequest) ProtoMessage() {} func (x *CreateBDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[70] + mi := &file_proto_user_v1_host_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4892,7 +5070,7 @@ func (x *CreateBDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateBDRequest.ProtoReflect.Descriptor instead. func (*CreateBDRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{70} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{73} } func (x *CreateBDRequest) GetMeta() *RequestMeta { @@ -4947,7 +5125,7 @@ type CreateBDResponse struct { func (x *CreateBDResponse) Reset() { *x = CreateBDResponse{} - mi := &file_proto_user_v1_host_proto_msgTypes[71] + mi := &file_proto_user_v1_host_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4959,7 +5137,7 @@ func (x *CreateBDResponse) String() string { func (*CreateBDResponse) ProtoMessage() {} func (x *CreateBDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[71] + mi := &file_proto_user_v1_host_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4972,7 +5150,7 @@ func (x *CreateBDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateBDResponse.ProtoReflect.Descriptor instead. func (*CreateBDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{71} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{74} } func (x *CreateBDResponse) GetBdProfile() *BDProfile { @@ -4998,7 +5176,7 @@ type SetBDStatusRequest struct { func (x *SetBDStatusRequest) Reset() { *x = SetBDStatusRequest{} - mi := &file_proto_user_v1_host_proto_msgTypes[72] + mi := &file_proto_user_v1_host_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5010,7 +5188,7 @@ func (x *SetBDStatusRequest) String() string { func (*SetBDStatusRequest) ProtoMessage() {} func (x *SetBDStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[72] + mi := &file_proto_user_v1_host_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5023,7 +5201,7 @@ func (x *SetBDStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetBDStatusRequest.ProtoReflect.Descriptor instead. func (*SetBDStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{72} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{75} } func (x *SetBDStatusRequest) GetMeta() *RequestMeta { @@ -5085,7 +5263,7 @@ type SetBDStatusResponse struct { func (x *SetBDStatusResponse) Reset() { *x = SetBDStatusResponse{} - mi := &file_proto_user_v1_host_proto_msgTypes[73] + mi := &file_proto_user_v1_host_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5097,7 +5275,7 @@ func (x *SetBDStatusResponse) String() string { func (*SetBDStatusResponse) ProtoMessage() {} func (x *SetBDStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[73] + mi := &file_proto_user_v1_host_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5110,7 +5288,7 @@ func (x *SetBDStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetBDStatusResponse.ProtoReflect.Descriptor instead. func (*SetBDStatusResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{73} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{76} } func (x *SetBDStatusResponse) GetBdProfile() *BDProfile { @@ -5135,7 +5313,7 @@ type CreateCoinSellerRequest struct { func (x *CreateCoinSellerRequest) Reset() { *x = CreateCoinSellerRequest{} - mi := &file_proto_user_v1_host_proto_msgTypes[74] + mi := &file_proto_user_v1_host_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5147,7 +5325,7 @@ func (x *CreateCoinSellerRequest) String() string { func (*CreateCoinSellerRequest) ProtoMessage() {} func (x *CreateCoinSellerRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[74] + mi := &file_proto_user_v1_host_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5160,7 +5338,7 @@ func (x *CreateCoinSellerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateCoinSellerRequest.ProtoReflect.Descriptor instead. func (*CreateCoinSellerRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{74} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{77} } func (x *CreateCoinSellerRequest) GetMeta() *RequestMeta { @@ -5215,7 +5393,7 @@ type CreateCoinSellerResponse struct { func (x *CreateCoinSellerResponse) Reset() { *x = CreateCoinSellerResponse{} - mi := &file_proto_user_v1_host_proto_msgTypes[75] + mi := &file_proto_user_v1_host_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5227,7 +5405,7 @@ func (x *CreateCoinSellerResponse) String() string { func (*CreateCoinSellerResponse) ProtoMessage() {} func (x *CreateCoinSellerResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[75] + mi := &file_proto_user_v1_host_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5240,7 +5418,7 @@ func (x *CreateCoinSellerResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateCoinSellerResponse.ProtoReflect.Descriptor instead. func (*CreateCoinSellerResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{75} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{78} } func (x *CreateCoinSellerResponse) GetCoinSellerProfile() *CoinSellerProfile { @@ -5266,7 +5444,7 @@ type SetCoinSellerStatusRequest struct { func (x *SetCoinSellerStatusRequest) Reset() { *x = SetCoinSellerStatusRequest{} - mi := &file_proto_user_v1_host_proto_msgTypes[76] + mi := &file_proto_user_v1_host_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5278,7 +5456,7 @@ func (x *SetCoinSellerStatusRequest) String() string { func (*SetCoinSellerStatusRequest) ProtoMessage() {} func (x *SetCoinSellerStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[76] + mi := &file_proto_user_v1_host_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5291,7 +5469,7 @@ func (x *SetCoinSellerStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetCoinSellerStatusRequest.ProtoReflect.Descriptor instead. func (*SetCoinSellerStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{76} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{79} } func (x *SetCoinSellerStatusRequest) GetMeta() *RequestMeta { @@ -5353,7 +5531,7 @@ type SetCoinSellerStatusResponse struct { func (x *SetCoinSellerStatusResponse) Reset() { *x = SetCoinSellerStatusResponse{} - mi := &file_proto_user_v1_host_proto_msgTypes[77] + mi := &file_proto_user_v1_host_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5365,7 +5543,7 @@ func (x *SetCoinSellerStatusResponse) String() string { func (*SetCoinSellerStatusResponse) ProtoMessage() {} func (x *SetCoinSellerStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[77] + mi := &file_proto_user_v1_host_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5378,7 +5556,7 @@ func (x *SetCoinSellerStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetCoinSellerStatusResponse.ProtoReflect.Descriptor instead. func (*SetCoinSellerStatusResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{77} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{80} } func (x *SetCoinSellerStatusResponse) GetCoinSellerProfile() *CoinSellerProfile { @@ -5403,7 +5581,7 @@ type ReviewSubCoinSellerApplicationRequest struct { func (x *ReviewSubCoinSellerApplicationRequest) Reset() { *x = ReviewSubCoinSellerApplicationRequest{} - mi := &file_proto_user_v1_host_proto_msgTypes[78] + mi := &file_proto_user_v1_host_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5415,7 +5593,7 @@ func (x *ReviewSubCoinSellerApplicationRequest) String() string { func (*ReviewSubCoinSellerApplicationRequest) ProtoMessage() {} func (x *ReviewSubCoinSellerApplicationRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[78] + mi := &file_proto_user_v1_host_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5428,7 +5606,7 @@ func (x *ReviewSubCoinSellerApplicationRequest) ProtoReflect() protoreflect.Mess // Deprecated: Use ReviewSubCoinSellerApplicationRequest.ProtoReflect.Descriptor instead. func (*ReviewSubCoinSellerApplicationRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{78} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{81} } func (x *ReviewSubCoinSellerApplicationRequest) GetMeta() *RequestMeta { @@ -5485,7 +5663,7 @@ type ReviewSubCoinSellerApplicationResponse struct { func (x *ReviewSubCoinSellerApplicationResponse) Reset() { *x = ReviewSubCoinSellerApplicationResponse{} - mi := &file_proto_user_v1_host_proto_msgTypes[79] + mi := &file_proto_user_v1_host_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5497,7 +5675,7 @@ func (x *ReviewSubCoinSellerApplicationResponse) String() string { func (*ReviewSubCoinSellerApplicationResponse) ProtoMessage() {} func (x *ReviewSubCoinSellerApplicationResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[79] + mi := &file_proto_user_v1_host_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5510,7 +5688,7 @@ func (x *ReviewSubCoinSellerApplicationResponse) ProtoReflect() protoreflect.Mes // Deprecated: Use ReviewSubCoinSellerApplicationResponse.ProtoReflect.Descriptor instead. func (*ReviewSubCoinSellerApplicationResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{79} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{82} } func (x *ReviewSubCoinSellerApplicationResponse) GetApplication() *CoinSellerSubApplication { @@ -5553,7 +5731,7 @@ type CreateAgencyRequest struct { func (x *CreateAgencyRequest) Reset() { *x = CreateAgencyRequest{} - mi := &file_proto_user_v1_host_proto_msgTypes[80] + mi := &file_proto_user_v1_host_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5565,7 +5743,7 @@ func (x *CreateAgencyRequest) String() string { func (*CreateAgencyRequest) ProtoMessage() {} func (x *CreateAgencyRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[80] + mi := &file_proto_user_v1_host_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5578,7 +5756,7 @@ func (x *CreateAgencyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateAgencyRequest.ProtoReflect.Descriptor instead. func (*CreateAgencyRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{80} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{83} } func (x *CreateAgencyRequest) GetMeta() *RequestMeta { @@ -5656,7 +5834,7 @@ type CreateAgencyResponse struct { func (x *CreateAgencyResponse) Reset() { *x = CreateAgencyResponse{} - mi := &file_proto_user_v1_host_proto_msgTypes[81] + mi := &file_proto_user_v1_host_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5668,7 +5846,7 @@ func (x *CreateAgencyResponse) String() string { func (*CreateAgencyResponse) ProtoMessage() {} func (x *CreateAgencyResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[81] + mi := &file_proto_user_v1_host_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5681,7 +5859,7 @@ func (x *CreateAgencyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateAgencyResponse.ProtoReflect.Descriptor instead. func (*CreateAgencyResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{81} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{84} } func (x *CreateAgencyResponse) GetAgency() *Agency { @@ -5720,7 +5898,7 @@ type AdminAddAgencyHostRequest struct { func (x *AdminAddAgencyHostRequest) Reset() { *x = AdminAddAgencyHostRequest{} - mi := &file_proto_user_v1_host_proto_msgTypes[82] + mi := &file_proto_user_v1_host_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5732,7 +5910,7 @@ func (x *AdminAddAgencyHostRequest) String() string { func (*AdminAddAgencyHostRequest) ProtoMessage() {} func (x *AdminAddAgencyHostRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[82] + mi := &file_proto_user_v1_host_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5745,7 +5923,7 @@ func (x *AdminAddAgencyHostRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminAddAgencyHostRequest.ProtoReflect.Descriptor instead. func (*AdminAddAgencyHostRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{82} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{85} } func (x *AdminAddAgencyHostRequest) GetMeta() *RequestMeta { @@ -5802,7 +5980,7 @@ type AdminAddAgencyHostResponse struct { func (x *AdminAddAgencyHostResponse) Reset() { *x = AdminAddAgencyHostResponse{} - mi := &file_proto_user_v1_host_proto_msgTypes[83] + mi := &file_proto_user_v1_host_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5814,7 +5992,7 @@ func (x *AdminAddAgencyHostResponse) String() string { func (*AdminAddAgencyHostResponse) ProtoMessage() {} func (x *AdminAddAgencyHostResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[83] + mi := &file_proto_user_v1_host_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5827,7 +6005,7 @@ func (x *AdminAddAgencyHostResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminAddAgencyHostResponse.ProtoReflect.Descriptor instead. func (*AdminAddAgencyHostResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{83} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{86} } func (x *AdminAddAgencyHostResponse) GetAgency() *Agency { @@ -5865,7 +6043,7 @@ type CloseAgencyRequest struct { func (x *CloseAgencyRequest) Reset() { *x = CloseAgencyRequest{} - mi := &file_proto_user_v1_host_proto_msgTypes[84] + mi := &file_proto_user_v1_host_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5877,7 +6055,7 @@ func (x *CloseAgencyRequest) String() string { func (*CloseAgencyRequest) ProtoMessage() {} func (x *CloseAgencyRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[84] + mi := &file_proto_user_v1_host_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5890,7 +6068,7 @@ func (x *CloseAgencyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CloseAgencyRequest.ProtoReflect.Descriptor instead. func (*CloseAgencyRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{84} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{87} } func (x *CloseAgencyRequest) GetMeta() *RequestMeta { @@ -5938,7 +6116,7 @@ type CloseAgencyResponse struct { func (x *CloseAgencyResponse) Reset() { *x = CloseAgencyResponse{} - mi := &file_proto_user_v1_host_proto_msgTypes[85] + mi := &file_proto_user_v1_host_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5950,7 +6128,7 @@ func (x *CloseAgencyResponse) String() string { func (*CloseAgencyResponse) ProtoMessage() {} func (x *CloseAgencyResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[85] + mi := &file_proto_user_v1_host_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5963,7 +6141,7 @@ func (x *CloseAgencyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CloseAgencyResponse.ProtoReflect.Descriptor instead. func (*CloseAgencyResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{85} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{88} } func (x *CloseAgencyResponse) GetAgency() *Agency { @@ -5987,7 +6165,7 @@ type DeleteAgencyRequest struct { func (x *DeleteAgencyRequest) Reset() { *x = DeleteAgencyRequest{} - mi := &file_proto_user_v1_host_proto_msgTypes[86] + mi := &file_proto_user_v1_host_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5999,7 +6177,7 @@ func (x *DeleteAgencyRequest) String() string { func (*DeleteAgencyRequest) ProtoMessage() {} func (x *DeleteAgencyRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[86] + mi := &file_proto_user_v1_host_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6012,7 +6190,7 @@ func (x *DeleteAgencyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAgencyRequest.ProtoReflect.Descriptor instead. func (*DeleteAgencyRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{86} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{89} } func (x *DeleteAgencyRequest) GetMeta() *RequestMeta { @@ -6060,7 +6238,7 @@ type DeleteAgencyResponse struct { func (x *DeleteAgencyResponse) Reset() { *x = DeleteAgencyResponse{} - mi := &file_proto_user_v1_host_proto_msgTypes[87] + mi := &file_proto_user_v1_host_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6072,7 +6250,7 @@ func (x *DeleteAgencyResponse) String() string { func (*DeleteAgencyResponse) ProtoMessage() {} func (x *DeleteAgencyResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[87] + mi := &file_proto_user_v1_host_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6085,7 +6263,7 @@ func (x *DeleteAgencyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAgencyResponse.ProtoReflect.Descriptor instead. func (*DeleteAgencyResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{87} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{90} } func (x *DeleteAgencyResponse) GetAgency() *Agency { @@ -6110,7 +6288,7 @@ type SetAgencyStatusRequest struct { func (x *SetAgencyStatusRequest) Reset() { *x = SetAgencyStatusRequest{} - mi := &file_proto_user_v1_host_proto_msgTypes[88] + mi := &file_proto_user_v1_host_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6122,7 +6300,7 @@ func (x *SetAgencyStatusRequest) String() string { func (*SetAgencyStatusRequest) ProtoMessage() {} func (x *SetAgencyStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[88] + mi := &file_proto_user_v1_host_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6135,7 +6313,7 @@ func (x *SetAgencyStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetAgencyStatusRequest.ProtoReflect.Descriptor instead. func (*SetAgencyStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{88} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{91} } func (x *SetAgencyStatusRequest) GetMeta() *RequestMeta { @@ -6190,7 +6368,7 @@ type SetAgencyStatusResponse struct { func (x *SetAgencyStatusResponse) Reset() { *x = SetAgencyStatusResponse{} - mi := &file_proto_user_v1_host_proto_msgTypes[89] + mi := &file_proto_user_v1_host_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6202,7 +6380,7 @@ func (x *SetAgencyStatusResponse) String() string { func (*SetAgencyStatusResponse) ProtoMessage() {} func (x *SetAgencyStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[89] + mi := &file_proto_user_v1_host_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6215,7 +6393,7 @@ func (x *SetAgencyStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetAgencyStatusResponse.ProtoReflect.Descriptor instead. func (*SetAgencyStatusResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{89} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{92} } func (x *SetAgencyStatusResponse) GetAgency() *Agency { @@ -6240,7 +6418,7 @@ type SetAgencyJoinEnabledRequest struct { func (x *SetAgencyJoinEnabledRequest) Reset() { *x = SetAgencyJoinEnabledRequest{} - mi := &file_proto_user_v1_host_proto_msgTypes[90] + mi := &file_proto_user_v1_host_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6252,7 +6430,7 @@ func (x *SetAgencyJoinEnabledRequest) String() string { func (*SetAgencyJoinEnabledRequest) ProtoMessage() {} func (x *SetAgencyJoinEnabledRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[90] + mi := &file_proto_user_v1_host_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6265,7 +6443,7 @@ func (x *SetAgencyJoinEnabledRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetAgencyJoinEnabledRequest.ProtoReflect.Descriptor instead. func (*SetAgencyJoinEnabledRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{90} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{93} } func (x *SetAgencyJoinEnabledRequest) GetMeta() *RequestMeta { @@ -6320,7 +6498,7 @@ type SetAgencyJoinEnabledResponse struct { func (x *SetAgencyJoinEnabledResponse) Reset() { *x = SetAgencyJoinEnabledResponse{} - mi := &file_proto_user_v1_host_proto_msgTypes[91] + mi := &file_proto_user_v1_host_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6332,7 +6510,7 @@ func (x *SetAgencyJoinEnabledResponse) String() string { func (*SetAgencyJoinEnabledResponse) ProtoMessage() {} func (x *SetAgencyJoinEnabledResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[91] + mi := &file_proto_user_v1_host_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6345,7 +6523,7 @@ func (x *SetAgencyJoinEnabledResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetAgencyJoinEnabledResponse.ProtoReflect.Descriptor instead. func (*SetAgencyJoinEnabledResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{91} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{94} } func (x *SetAgencyJoinEnabledResponse) GetAgency() *Agency { @@ -6370,7 +6548,7 @@ type UpdateAgencyProfileRequest struct { func (x *UpdateAgencyProfileRequest) Reset() { *x = UpdateAgencyProfileRequest{} - mi := &file_proto_user_v1_host_proto_msgTypes[92] + mi := &file_proto_user_v1_host_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6382,7 +6560,7 @@ func (x *UpdateAgencyProfileRequest) String() string { func (*UpdateAgencyProfileRequest) ProtoMessage() {} func (x *UpdateAgencyProfileRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[92] + mi := &file_proto_user_v1_host_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6395,7 +6573,7 @@ func (x *UpdateAgencyProfileRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateAgencyProfileRequest.ProtoReflect.Descriptor instead. func (*UpdateAgencyProfileRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{92} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{95} } func (x *UpdateAgencyProfileRequest) GetMeta() *RequestMeta { @@ -6450,7 +6628,7 @@ type UpdateAgencyProfileResponse struct { func (x *UpdateAgencyProfileResponse) Reset() { *x = UpdateAgencyProfileResponse{} - mi := &file_proto_user_v1_host_proto_msgTypes[93] + mi := &file_proto_user_v1_host_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6462,7 +6640,7 @@ func (x *UpdateAgencyProfileResponse) String() string { func (*UpdateAgencyProfileResponse) ProtoMessage() {} func (x *UpdateAgencyProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_host_proto_msgTypes[93] + mi := &file_proto_user_v1_host_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6475,7 +6653,7 @@ func (x *UpdateAgencyProfileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateAgencyProfileResponse.ProtoReflect.Descriptor instead. func (*UpdateAgencyProfileResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_host_proto_rawDescGZIP(), []int{93} + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{96} } func (x *UpdateAgencyProfileResponse) GetAgency() *Agency { @@ -6485,6 +6663,321 @@ func (x *UpdateAgencyProfileResponse) GetAgency() *Agency { return nil } +// HostEngagementStats 是 Fami 等公会中心使用的用户侧统计聚合;金额/礼物流水由 wallet-service 单独拥有。 +type HostEngagementStats struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OnlineDurationMs int64 `protobuf:"varint,1,opt,name=online_duration_ms,json=onlineDurationMs,proto3" json:"online_duration_ms,omitempty"` + ValidMicDurationMs int64 `protobuf:"varint,2,opt,name=valid_mic_duration_ms,json=validMicDurationMs,proto3" json:"valid_mic_duration_ms,omitempty"` + ValidMicDays int64 `protobuf:"varint,3,opt,name=valid_mic_days,json=validMicDays,proto3" json:"valid_mic_days,omitempty"` + PrivateMessageSenders int64 `protobuf:"varint,4,opt,name=private_message_senders,json=privateMessageSenders,proto3" json:"private_message_senders,omitempty"` + NewFollowers int64 `protobuf:"varint,5,opt,name=new_followers,json=newFollowers,proto3" json:"new_followers,omitempty"` +} + +func (x *HostEngagementStats) Reset() { + *x = HostEngagementStats{} + mi := &file_proto_user_v1_host_proto_msgTypes[97] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HostEngagementStats) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HostEngagementStats) ProtoMessage() {} + +func (x *HostEngagementStats) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_host_proto_msgTypes[97] + 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 HostEngagementStats.ProtoReflect.Descriptor instead. +func (*HostEngagementStats) Descriptor() ([]byte, []int) { + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{97} +} + +func (x *HostEngagementStats) GetOnlineDurationMs() int64 { + if x != nil { + return x.OnlineDurationMs + } + return 0 +} + +func (x *HostEngagementStats) GetValidMicDurationMs() int64 { + if x != nil { + return x.ValidMicDurationMs + } + return 0 +} + +func (x *HostEngagementStats) GetValidMicDays() int64 { + if x != nil { + return x.ValidMicDays + } + return 0 +} + +func (x *HostEngagementStats) GetPrivateMessageSenders() int64 { + if x != nil { + return x.PrivateMessageSenders + } + return 0 +} + +func (x *HostEngagementStats) GetNewFollowers() int64 { + if x != nil { + return x.NewFollowers + } + return 0 +} + +type GetHostEngagementStatsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + HostUserId int64 `protobuf:"varint,2,opt,name=host_user_id,json=hostUserId,proto3" json:"host_user_id,omitempty"` + StartAtMs int64 `protobuf:"varint,3,opt,name=start_at_ms,json=startAtMs,proto3" json:"start_at_ms,omitempty"` + EndAtMs int64 `protobuf:"varint,4,opt,name=end_at_ms,json=endAtMs,proto3" json:"end_at_ms,omitempty"` +} + +func (x *GetHostEngagementStatsRequest) Reset() { + *x = GetHostEngagementStatsRequest{} + mi := &file_proto_user_v1_host_proto_msgTypes[98] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetHostEngagementStatsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetHostEngagementStatsRequest) ProtoMessage() {} + +func (x *GetHostEngagementStatsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_host_proto_msgTypes[98] + 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 GetHostEngagementStatsRequest.ProtoReflect.Descriptor instead. +func (*GetHostEngagementStatsRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{98} +} + +func (x *GetHostEngagementStatsRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *GetHostEngagementStatsRequest) GetHostUserId() int64 { + if x != nil { + return x.HostUserId + } + return 0 +} + +func (x *GetHostEngagementStatsRequest) GetStartAtMs() int64 { + if x != nil { + return x.StartAtMs + } + return 0 +} + +func (x *GetHostEngagementStatsRequest) GetEndAtMs() int64 { + if x != nil { + return x.EndAtMs + } + return 0 +} + +type GetHostEngagementStatsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Stats *HostEngagementStats `protobuf:"bytes,1,opt,name=stats,proto3" json:"stats,omitempty"` +} + +func (x *GetHostEngagementStatsResponse) Reset() { + *x = GetHostEngagementStatsResponse{} + mi := &file_proto_user_v1_host_proto_msgTypes[99] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetHostEngagementStatsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetHostEngagementStatsResponse) ProtoMessage() {} + +func (x *GetHostEngagementStatsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_host_proto_msgTypes[99] + 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 GetHostEngagementStatsResponse.ProtoReflect.Descriptor instead. +func (*GetHostEngagementStatsResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{99} +} + +func (x *GetHostEngagementStatsResponse) GetStats() *HostEngagementStats { + if x != nil { + return x.Stats + } + return nil +} + +// RecordPrivateMessageEvent 由经过腾讯回调鉴权的 gateway 写入私信事实;重复 event_id 必须幂等。 +type RecordPrivateMessageEventRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + EventId string `protobuf:"bytes,2,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"` + SenderUserId int64 `protobuf:"varint,3,opt,name=sender_user_id,json=senderUserId,proto3" json:"sender_user_id,omitempty"` + TargetUserId int64 `protobuf:"varint,4,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + OccurredAtMs int64 `protobuf:"varint,5,opt,name=occurred_at_ms,json=occurredAtMs,proto3" json:"occurred_at_ms,omitempty"` +} + +func (x *RecordPrivateMessageEventRequest) Reset() { + *x = RecordPrivateMessageEventRequest{} + mi := &file_proto_user_v1_host_proto_msgTypes[100] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RecordPrivateMessageEventRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RecordPrivateMessageEventRequest) ProtoMessage() {} + +func (x *RecordPrivateMessageEventRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_host_proto_msgTypes[100] + 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 RecordPrivateMessageEventRequest.ProtoReflect.Descriptor instead. +func (*RecordPrivateMessageEventRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{100} +} + +func (x *RecordPrivateMessageEventRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *RecordPrivateMessageEventRequest) GetEventId() string { + if x != nil { + return x.EventId + } + return "" +} + +func (x *RecordPrivateMessageEventRequest) GetSenderUserId() int64 { + if x != nil { + return x.SenderUserId + } + return 0 +} + +func (x *RecordPrivateMessageEventRequest) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *RecordPrivateMessageEventRequest) GetOccurredAtMs() int64 { + if x != nil { + return x.OccurredAtMs + } + return 0 +} + +type RecordPrivateMessageEventResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Created bool `protobuf:"varint,1,opt,name=created,proto3" json:"created,omitempty"` +} + +func (x *RecordPrivateMessageEventResponse) Reset() { + *x = RecordPrivateMessageEventResponse{} + mi := &file_proto_user_v1_host_proto_msgTypes[101] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RecordPrivateMessageEventResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RecordPrivateMessageEventResponse) ProtoMessage() {} + +func (x *RecordPrivateMessageEventResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_host_proto_msgTypes[101] + 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 RecordPrivateMessageEventResponse.ProtoReflect.Descriptor instead. +func (*RecordPrivateMessageEventResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_host_proto_rawDescGZIP(), []int{101} +} + +func (x *RecordPrivateMessageEventResponse) GetCreated() bool { + if x != nil { + return x.Created + } + return false +} + var File_proto_user_v1_host_proto protoreflect.FileDescriptor var file_proto_user_v1_host_proto_rawDesc = []byte{ @@ -6661,7 +7154,7 @@ var file_proto_user_v1_host_proto_rawDesc = []byte{ 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x4d, 0x73, 0x22, 0xb4, 0x03, 0x0a, 0x0f, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x53, + 0x4d, 0x73, 0x22, 0xca, 0x03, 0x0a, 0x0f, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, @@ -6688,219 +7181,204 @@ var file_proto_user_v1_host_proto_rawDesc = []byte{ 0x28, 0x03, 0x52, 0x16, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, - 0x69, 0x73, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x22, 0xaa, 0x03, 0x0a, 0x10, 0x41, 0x67, - 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x12, 0x23, - 0x0a, 0x0d, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, - 0x70, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, - 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, - 0x27, 0x0a, 0x0f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, - 0x73, 0x68, 0x69, 0x70, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x20, 0x0a, 0x0c, 0x6a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x41, 0x74, - 0x4d, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, - 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x41, 0x74, - 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x10, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x65, 0x6e, - 0x64, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x65, - 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x22, - 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x5f, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x99, 0x03, 0x0a, 0x11, 0x41, 0x67, 0x65, 0x6e, 0x63, - 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, - 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x61, - 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, - 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, - 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x2d, 0x0a, 0x13, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x72, - 0x65, 0x76, 0x69, 0x65, 0x77, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x65, - 0x76, 0x69, 0x65, 0x77, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, - 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x4d, 0x73, 0x22, 0xf4, 0x04, 0x0a, 0x0e, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x69, 0x6e, - 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x76, - 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0e, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x6e, - 0x76, 0x69, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x12, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x64, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, - 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x42, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x64, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, - 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x31, - 0x0a, 0x15, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x70, - 0x61, 0x72, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x62, - 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x11, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x72, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x63, - 0x65, 0x73, 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x72, 0x6f, - 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x41, 0x74, 0x4d, - 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, - 0x6d, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x97, 0x01, 0x0a, 0x15, 0x53, 0x65, - 0x61, 0x72, 0x63, 0x68, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, - 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, - 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, - 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, - 0x69, 0x7a, 0x65, 0x22, 0x4b, 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x41, 0x67, 0x65, - 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, - 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, - 0x22, 0x9b, 0x01, 0x0a, 0x14, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x6f, 0x41, 0x67, 0x65, 0x6e, - 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, + 0x69, 0x73, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x6c, + 0x65, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x22, + 0xaa, 0x03, 0x0a, 0x10, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, + 0x73, 0x68, 0x69, 0x70, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, + 0x69, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, + 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, + 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x68, 0x6f, + 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, + 0x68, 0x69, 0x70, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x6a, 0x6f, 0x69, 0x6e, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6a, 0x6f, + 0x69, 0x6e, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x65, 0x6e, 0x64, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, + 0x6e, 0x64, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x10, 0x65, 0x6e, 0x64, 0x65, + 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0d, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x52, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x99, 0x03, 0x0a, + 0x11, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x61, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, + 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x61, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x6e, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, + 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, + 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x10, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x65, 0x64, 0x42, 0x79, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x5f, + 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, + 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, + 0x76, 0x69, 0x65, 0x77, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0c, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, + 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, + 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xf4, 0x04, 0x0a, 0x0e, 0x52, 0x6f, 0x6c, + 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, + 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0c, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, + 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x76, 0x69, 0x74, + 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x12, 0x69, 0x6e, 0x76, 0x69, + 0x74, 0x65, 0x72, 0x5f, 0x62, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x42, 0x64, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, + 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x67, 0x65, 0x6e, + 0x63, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, + 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x11, 0x70, 0x61, 0x72, + 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x64, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x15, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x6c, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x12, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x70, 0x72, 0x6f, 0x63, 0x65, + 0x73, 0x73, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, + 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x63, + 0x65, 0x73, 0x73, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, + 0x26, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, + 0x73, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x10, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, + 0x97, 0x01, 0x0a, 0x15, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, - 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x22, 0x5b, - 0x0a, 0x15, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x6f, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, - 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, - 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xf4, 0x01, 0x0a, 0x1e, - 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, - 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, - 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x28, 0x0a, - 0x10, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x65, - 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x70, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0d, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1a, - 0x0a, 0x08, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, - 0x6f, 0x6e, 0x22, 0xe5, 0x01, 0x0a, 0x1f, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x41, 0x67, 0x65, - 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, - 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, - 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x0c, 0x68, 0x6f, - 0x73, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x0b, 0x68, 0x6f, - 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x3f, 0x0a, 0x0a, 0x6d, 0x65, 0x6d, - 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, - 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x52, 0x0a, - 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x22, 0xe7, 0x01, 0x0a, 0x15, 0x4b, - 0x69, 0x63, 0x6b, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, + 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1b, 0x0a, 0x09, + 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x4b, 0x0a, 0x16, 0x53, 0x65, 0x61, + 0x72, 0x63, 0x68, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x08, 0x61, 0x67, + 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x22, 0x9b, 0x01, 0x0a, 0x14, 0x41, 0x70, 0x70, 0x6c, 0x79, + 0x54, 0x6f, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, + 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, + 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, + 0x79, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, + 0x63, 0x79, 0x49, 0x64, 0x22, 0x5b, 0x0a, 0x15, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x6f, 0x41, + 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, + 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x22, 0xf4, 0x01, 0x0a, 0x1e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x41, 0x67, 0x65, 0x6e, + 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, - 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x6f, - 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x98, 0x01, 0x0a, 0x16, 0x4b, 0x69, 0x63, 0x6b, 0x41, 0x67, 0x65, - 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x3f, 0x0a, 0x0a, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, - 0x73, 0x68, 0x69, 0x70, 0x52, 0x0a, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, + 0x64, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, + 0x65, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, + 0x0e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xe5, 0x01, 0x0a, 0x1f, 0x52, 0x65, 0x76, + 0x69, 0x65, 0x77, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0b, + 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x52, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, - 0xd3, 0x01, 0x0a, 0x13, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, - 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, - 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0d, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, - 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x67, 0x65, 0x6e, 0x63, - 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x55, 0x0a, 0x14, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, - 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, - 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xae, 0x01, 0x0a, - 0x0f, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, - 0x26, 0x0a, 0x0f, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, - 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x51, 0x0a, - 0x10, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x22, 0xb0, 0x01, 0x0a, 0x11, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, + 0x6c, 0x65, 0x52, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, + 0x3f, 0x0a, 0x0a, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, + 0x73, 0x68, 0x69, 0x70, 0x52, 0x0a, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, + 0x22, 0xe7, 0x01, 0x0a, 0x15, 0x4b, 0x69, 0x63, 0x6b, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, + 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, + 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x98, 0x01, 0x0a, 0x16, 0x4b, + 0x69, 0x63, 0x6b, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0a, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, + 0x68, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, + 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x52, 0x0a, 0x6d, 0x65, 0x6d, 0x62, + 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x12, 0x3d, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x70, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, + 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0xd3, 0x01, 0x0a, 0x13, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, + 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, + 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, + 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x67, + 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x55, 0x0a, 0x14, 0x49, + 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x22, 0xae, 0x01, 0x0a, 0x0f, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, @@ -6911,517 +7389,633 @@ var file_proto_user_v1_host_proto_rawDesc = []byte{ 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x22, 0x53, 0x0a, 0x12, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x48, 0x6f, 0x73, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x69, 0x6e, 0x76, - 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, - 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x69, 0x6e, - 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xa3, 0x01, 0x0a, 0x16, 0x4c, 0x69, 0x73, - 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x54, - 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x44, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x62, 0x64, 0x5f, - 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, - 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x0a, 0x62, 0x64, 0x50, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x73, 0x22, 0xa8, 0x01, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, - 0x51, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, - 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x31, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x69, - 0x65, 0x73, 0x22, 0x95, 0x01, 0x0a, 0x1e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x72, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, - 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, - 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x7f, 0x0a, 0x11, 0x4d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x72, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, - 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, - 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x29, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x64, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x70, 0x61, 0x72, - 0x65, 0x6e, 0x74, 0x42, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xc4, 0x01, 0x0a, 0x1f, - 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x54, 0x65, 0x61, 0x6d, 0x41, - 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x3c, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x67, 0x65, - 0x6e, 0x63, 0x79, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x28, 0x0a, - 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x64, 0x5f, 0x6c, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x64, - 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x5f, 0x62, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x42, 0x64, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, - 0x65, 0x64, 0x22, 0x9a, 0x01, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x41, 0x67, 0x65, - 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1c, 0x0a, 0x0a, - 0x62, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x08, 0x62, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, - 0x4b, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x61, 0x67, 0x65, - 0x6e, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, - 0x63, 0x79, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x22, 0xa7, 0x01, 0x0a, - 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, - 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x5e, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, - 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x49, - 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x93, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x52, 0x6f, - 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, - 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x76, 0x69, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x5a, 0x0a, 0x19, - 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x69, 0x6e, 0x76, - 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, - 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x69, 0x6e, - 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xe6, 0x01, 0x0a, 0x1c, 0x50, 0x72, 0x6f, - 0x63, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, - 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, - 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, - 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0c, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, - 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x22, 0xc6, 0x02, 0x0a, 0x1d, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x6c, - 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, - 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x52, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, - 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, - 0x12, 0x3f, 0x0a, 0x0a, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, - 0x72, 0x73, 0x68, 0x69, 0x70, 0x52, 0x0a, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, - 0x70, 0x12, 0x37, 0x0a, 0x0a, 0x62, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, - 0x09, 0x62, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x60, 0x0a, 0x15, 0x47, 0x65, - 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x57, 0x0a, 0x16, - 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x70, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, - 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x50, 0x72, - 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x68, 0x0a, 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, - 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, - 0xdf, 0x01, 0x0a, 0x1c, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, - 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x62, 0x0a, 0x0d, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, - 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x73, 0x1a, 0x5b, 0x0a, 0x11, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x50, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x72, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x22, 0x4f, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x42, 0x44, 0x50, 0x72, - 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, - 0x0a, 0x62, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x09, 0x62, 0x64, 0x50, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x66, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x69, - 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x70, - 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, - 0x0a, 0x13, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x72, - 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, - 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x11, 0x63, - 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x22, 0x71, 0x0a, 0x26, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, - 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x49, 0x6e, 0x4d, 0x79, 0x52, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x22, 0x6f, 0x0a, 0x27, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, - 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x49, 0x6e, 0x4d, 0x79, - 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, - 0x0a, 0x0c, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x4c, - 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, - 0x6c, 0x65, 0x72, 0x73, 0x22, 0xb5, 0x01, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, - 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x61, 0x72, 0x65, - 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x68, 0x69, 0x6c, - 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x98, 0x01, 0x0a, - 0x1b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, - 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x08, - 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x75, 0x62, 0x52, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, - 0x0a, 0x05, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, - 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, - 0x52, 0x05, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x22, 0xc2, 0x01, 0x0a, 0x25, 0x53, 0x75, 0x62, 0x6d, - 0x69, 0x74, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x41, - 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, - 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x73, 0x0a, 0x26, - 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, - 0x6c, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, - 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x75, 0x62, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x22, 0xa2, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, - 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, - 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, - 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0xa2, 0x01, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x53, - 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, - 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, - 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x08, 0x63, 0x68, 0x69, 0x6c, - 0x64, 0x72, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, - 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, - 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x9d, 0x01, 0x0a, 0x21, - 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, - 0x75, 0x62, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x61, 0x72, 0x65, 0x6e, - 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x68, 0x69, 0x6c, 0x64, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, - 0x63, 0x68, 0x69, 0x6c, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x8f, 0x01, 0x0a, 0x22, - 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, - 0x75, 0x62, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x07, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x05, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x4c, 0x69, - 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x22, 0x64, 0x0a, - 0x19, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x6d, - 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x22, 0x56, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, - 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x38, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, - 0x72, 0x79, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x5f, 0x0a, 0x10, 0x47, - 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, - 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x22, 0x42, 0x0a, 0x11, - 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, - 0x22, 0x94, 0x01, 0x0a, 0x1e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, - 0x73, 0x73, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, - 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61, 0x70, 0x61, 0x62, - 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x61, 0x70, - 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x22, 0x53, 0x0a, 0x1f, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, - 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x6c, - 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x61, 0x6c, 0x6c, - 0x6f, 0x77, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x7e, 0x0a, 0x17, - 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, - 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, - 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, - 0x63, 0x79, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x5d, 0x0a, 0x18, - 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x6d, 0x65, 0x6d, 0x62, - 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, - 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x52, 0x0b, - 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x73, 0x22, 0x83, 0x01, 0x0a, 0x1c, - 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, - 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x22, 0x65, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, - 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0c, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, - 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x61, 0x70, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xc8, 0x01, 0x0a, 0x15, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, - 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x22, 0x51, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x4c, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, - 0x0a, 0x62, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x09, 0x62, 0x64, 0x50, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0xf5, 0x01, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x42, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x64, 0x6d, - 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, - 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x15, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x12, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x4b, - 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x62, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x52, 0x09, 0x62, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0xf1, 0x01, 0x0a, 0x12, - 0x53, 0x65, 0x74, 0x42, 0x44, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, - 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x72, - 0x6f, 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x22, - 0x4e, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x42, 0x44, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x62, 0x64, 0x5f, 0x70, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x44, 0x50, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x52, 0x09, 0x62, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, - 0xad, 0x02, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, - 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x64, - 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, - 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x1b, - 0x63, 0x61, 0x6e, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x75, 0x62, 0x5f, 0x63, - 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x08, 0x48, 0x00, 0x52, 0x17, 0x63, 0x61, 0x6e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x53, 0x75, - 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x88, 0x01, 0x01, 0x42, - 0x1e, 0x0a, 0x1c, 0x5f, 0x63, 0x61, 0x6e, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x5f, 0x73, - 0x75, 0x62, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x22, - 0x6c, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, - 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x13, 0x63, - 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, - 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x11, 0x63, 0x6f, 0x69, 0x6e, - 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0xc8, 0x02, - 0x0a, 0x1a, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, + 0x72, 0x49, 0x64, 0x22, 0x51, 0x0a, 0x10, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x42, 0x44, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, + 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb0, 0x01, 0x0a, 0x11, 0x49, 0x6e, 0x76, 0x69, 0x74, + 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, - 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, - 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x1b, 0x63, 0x61, 0x6e, 0x5f, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x5f, 0x73, 0x75, 0x62, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, - 0x6c, 0x65, 0x72, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x17, 0x63, 0x61, - 0x6e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, - 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x88, 0x01, 0x01, 0x42, 0x1e, 0x0a, 0x1c, 0x5f, 0x63, 0x61, 0x6e, - 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x75, 0x62, 0x5f, 0x63, 0x6f, 0x69, 0x6e, - 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x22, 0x6f, 0x0a, 0x1b, 0x53, 0x65, 0x74, 0x43, - 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x13, 0x63, 0x6f, 0x69, 0x6e, 0x5f, - 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x11, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, - 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0xf5, 0x01, 0x0a, 0x25, 0x52, 0x65, - 0x76, 0x69, 0x65, 0x77, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, - 0x72, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, - 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1a, 0x0a, - 0x08, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x22, 0xee, 0x01, 0x0a, 0x26, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x53, 0x75, 0x62, 0x43, + 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x69, + 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x53, 0x0a, 0x12, 0x49, 0x6e, 0x76, + 0x69, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3d, 0x0a, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xa3, + 0x01, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, + 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x6c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, + 0x53, 0x69, 0x7a, 0x65, 0x22, 0x54, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x42, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x39, 0x0a, 0x0b, 0x62, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x0a, + 0x62, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x22, 0xa8, 0x01, 0x0a, 0x1b, 0x4c, + 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x63, + 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, + 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x51, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x08, + 0x61, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x22, 0x95, 0x01, 0x0a, 0x1e, 0x4c, 0x69, 0x73, + 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x67, 0x65, 0x6e, + 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0f, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, + 0x22, 0x7f, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x54, 0x65, 0x61, 0x6d, 0x41, + 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, + 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, + 0x5f, 0x62, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x22, 0xc4, 0x01, 0x0a, 0x1f, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x72, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x54, + 0x65, 0x61, 0x6d, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, + 0x69, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x64, 0x5f, + 0x6c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x64, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x1b, 0x0a, + 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x08, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x64, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x72, + 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x74, + 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x22, 0x9a, 0x01, 0x0a, 0x15, 0x4c, 0x69, 0x73, + 0x74, 0x42, 0x44, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x12, 0x1c, 0x0a, 0x0a, 0x62, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x62, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, + 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x4b, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x41, + 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x31, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x69, + 0x65, 0x73, 0x22, 0xa7, 0x01, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, + 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x5e, 0x0a, 0x1b, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x69, + 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x93, 0x01, 0x0a, + 0x18, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, + 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, + 0x0d, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x49, 0x64, 0x22, 0x5a, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, + 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3d, 0x0a, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xe6, + 0x01, 0x0a, 0x1c, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, + 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, + 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, + 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x69, 0x6e, 0x76, 0x69, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xc6, 0x02, 0x0a, 0x1d, 0x50, 0x72, 0x6f, 0x63, + 0x65, 0x73, 0x73, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x69, 0x6e, 0x76, + 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, + 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x69, 0x6e, + 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, + 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x48, + 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x0b, 0x68, 0x6f, 0x73, 0x74, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x06, + 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x3f, 0x0a, 0x0a, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, + 0x73, 0x68, 0x69, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, + 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x52, 0x0a, 0x6d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x12, 0x37, 0x0a, 0x0a, 0x62, 0x64, 0x5f, 0x70, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x44, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x09, 0x62, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x22, 0x60, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x22, 0x57, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0c, + 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x0b, + 0x68, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x68, 0x0a, 0x1b, 0x42, + 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, 0xdf, 0x01, 0x0a, 0x1c, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, + 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x0d, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x70, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, + 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x50, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x68, 0x6f, + 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x1a, 0x5b, 0x0a, 0x11, 0x48, 0x6f, + 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x72, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x42, 0x44, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, + 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, + 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x22, 0x4f, 0x0a, 0x14, 0x47, + 0x65, 0x74, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x62, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x52, 0x09, 0x62, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x66, 0x0a, 0x1b, + 0x47, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x22, 0x70, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, + 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x13, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, + 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x52, 0x11, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x71, 0x0a, 0x26, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, + 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x49, + 0x6e, 0x4d, 0x79, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x6f, 0x0a, 0x27, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, + 0x72, 0x73, 0x49, 0x6e, 0x4d, 0x79, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0c, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, + 0x6c, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, + 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x0b, 0x63, + 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x22, 0xb5, 0x01, 0x0a, 0x1a, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, + 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x65, + 0x6e, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0c, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, + 0x0a, 0x0d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x22, 0x98, 0x01, 0x0a, 0x1b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, + 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x40, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, + 0x75, 0x62, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x05, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x4c, 0x69, + 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x22, 0xc2, 0x01, + 0x0a, 0x25, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, + 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, + 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x22, 0x73, 0x0a, 0x26, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x75, 0x62, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x40, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xa2, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, + 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, + 0x61, 0x72, 0x65, 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, + 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, + 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0xa2, 0x01, 0x0a, + 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, + 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x08, 0x63, + 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, + 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, + 0x52, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, + 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, + 0x65, 0x22, 0x9d, 0x01, 0x0a, 0x21, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x6f, 0x69, 0x6e, 0x53, + 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x75, 0x62, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, + 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, + 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0c, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, + 0x0d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x22, 0x8f, 0x01, 0x0a, 0x22, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x6f, 0x69, 0x6e, 0x53, + 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x75, 0x62, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x6c, 0x6c, 0x6f, + 0x77, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x61, 0x6c, 0x6c, 0x6f, 0x77, + 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x05, 0x63, 0x68, + 0x69, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, - 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x75, 0x62, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x05, 0x63, 0x68, 0x69, - 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, - 0x6c, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x63, 0x68, 0x69, - 0x6c, 0x64, 0x22, 0xc3, 0x02, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, + 0x6c, 0x6c, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x63, 0x68, + 0x69, 0x6c, 0x64, 0x22, 0x64, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, + 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x56, 0x0a, 0x1a, 0x47, 0x65, 0x74, + 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, + 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, + 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, + 0x79, 0x22, 0x5f, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, + 0x49, 0x64, 0x22, 0x42, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x06, + 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x22, 0x94, 0x01, 0x0a, 0x1e, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, + 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, + 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1e, 0x0a, + 0x0a, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x22, 0x53, 0x0a, + 0x1f, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x61, + 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x22, 0xb1, 0x01, 0x0a, 0x0f, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x62, 0x61, 0x73, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x42, 0x0a, 0x1d, 0x72, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x5f, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x1b, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x73, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x12, + 0x25, 0x0a, 0x0e, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x63, 0x6f, 0x70, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x65, + 0x64, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x22, 0x61, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, + 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x22, 0x54, 0x0a, 0x1a, 0x47, 0x65, 0x74, + 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x63, 0x6f, 0x70, + 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, + 0x7e, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, + 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, + 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, + 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, + 0x5d, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, + 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x6d, + 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, + 0x70, 0x52, 0x0b, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x73, 0x22, 0x83, + 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, + 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x22, 0x65, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, + 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0c, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, + 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x61, + 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xc8, 0x01, 0x0a, 0x15, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, + 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x51, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x37, 0x0a, 0x0a, 0x62, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x09, + 0x62, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0xf5, 0x01, 0x0a, 0x0f, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, + 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x15, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, + 0x5f, 0x6c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x22, 0x4b, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x62, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x52, 0x09, 0x62, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0xf1, + 0x01, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x42, 0x44, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, + 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x12, + 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, + 0x6c, 0x65, 0x22, 0x4e, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x42, 0x44, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x62, 0x64, 0x5f, + 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x44, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x09, 0x62, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x22, 0xad, 0x02, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x69, + 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, + 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, + 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, + 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, + 0x41, 0x0a, 0x1b, 0x63, 0x61, 0x6e, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x75, + 0x62, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x17, 0x63, 0x61, 0x6e, 0x4d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x88, + 0x01, 0x01, 0x42, 0x1e, 0x0a, 0x1c, 0x5f, 0x63, 0x61, 0x6e, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x5f, 0x73, 0x75, 0x62, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x65, + 0x72, 0x73, 0x22, 0x6c, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x69, 0x6e, + 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, + 0x0a, 0x13, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, + 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x11, 0x63, + 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x22, 0xc8, 0x02, 0x0a, 0x1a, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, + 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, + 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, + 0x0a, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x1b, 0x63, 0x61, 0x6e, 0x5f, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x75, 0x62, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, + 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, + 0x17, 0x63, 0x61, 0x6e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, + 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x88, 0x01, 0x01, 0x42, 0x1e, 0x0a, 0x1c, 0x5f, + 0x63, 0x61, 0x6e, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x75, 0x62, 0x5f, 0x63, + 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x22, 0x6f, 0x0a, 0x1b, 0x53, + 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x13, 0x63, 0x6f, + 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, + 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x11, 0x63, 0x6f, 0x69, 0x6e, 0x53, + 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0xf5, 0x01, 0x0a, + 0x25, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, + 0x6c, 0x6c, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, + 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, + 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x70, 0x70, + 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0d, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, + 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xee, 0x01, 0x0a, 0x26, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x53, + 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x49, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, + 0x75, 0x62, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, + 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x40, 0x0a, 0x08, 0x72, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, + 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x75, 0x62, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x05, + 0x63, 0x68, 0x69, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, + 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, + 0x63, 0x68, 0x69, 0x6c, 0x64, 0x22, 0xc3, 0x02, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, + 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x62, + 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x65, 0x6e, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6a, 0x6f, 0x69, 0x6e, 0x45, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x6f, + 0x73, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x48, 0x6f, + 0x73, 0x74, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xc5, 0x01, 0x0a, 0x14, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x06, 0x61, 0x67, 0x65, + 0x6e, 0x63, 0x79, 0x12, 0x3d, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x12, 0x3f, 0x0a, 0x0a, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x52, 0x0a, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, + 0x68, 0x69, 0x70, 0x22, 0xe9, 0x01, 0x0a, 0x19, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x41, 0x64, 0x64, + 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, + 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, + 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, + 0xcb, 0x01, 0x0a, 0x1a, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x41, 0x64, 0x64, 0x41, 0x67, 0x65, 0x6e, + 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, + 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, + 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x3d, 0x0a, + 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, + 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x3f, 0x0a, 0x0a, + 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, + 0x70, 0x52, 0x0a, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x22, 0xbc, 0x01, + 0x0a, 0x12, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, + 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, + 0x79, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, + 0x63, 0x79, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x44, 0x0a, 0x13, + 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, + 0x63, 0x79, 0x22, 0xbd, 0x01, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, @@ -7429,33 +8023,35 @@ var file_proto_user_v1_host_proto_rawDesc = []byte{ 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, - 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x29, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x64, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x70, 0x61, - 0x72, 0x65, 0x6e, 0x74, 0x42, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x21, 0x0a, 0x0c, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6a, 0x6f, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, - 0x6c, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x73, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x48, 0x6f, 0x73, 0x74, 0x73, - 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xc5, 0x01, 0x0a, 0x14, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, - 0x12, 0x3d, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x52, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, - 0x3f, 0x0a, 0x0a, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, - 0x73, 0x68, 0x69, 0x70, 0x52, 0x0a, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, - 0x22, 0xe9, 0x01, 0x0a, 0x19, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x41, 0x64, 0x64, 0x41, 0x67, 0x65, - 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, + 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, + 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x22, 0x45, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, + 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x67, + 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, + 0x79, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x22, 0xd8, 0x01, 0x0a, 0x16, 0x53, 0x65, + 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, + 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, + 0x79, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, + 0x63, 0x79, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, + 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x48, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, + 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2d, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x22, 0xe8, + 0x01, 0x0a, 0x1b, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x6f, 0x69, 0x6e, + 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, @@ -7464,390 +8060,384 @@ var file_proto_user_v1_host_proto_rawDesc = []byte{ 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x12, 0x24, - 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xcb, 0x01, 0x0a, - 0x1a, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x41, 0x64, 0x64, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, - 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x61, - 0x67, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, - 0x63, 0x79, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x3d, 0x0a, 0x0c, 0x68, 0x6f, - 0x73, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x0b, 0x68, 0x6f, - 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x3f, 0x0a, 0x0a, 0x6d, 0x65, 0x6d, - 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, - 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x52, 0x0a, - 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x22, 0xbc, 0x01, 0x0a, 0x12, 0x43, - 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, - 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, - 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x44, 0x0a, 0x13, 0x43, 0x6c, 0x6f, - 0x73, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x22, - 0xbd, 0x01, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x12, 0x21, + 0x0a, 0x0c, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6a, 0x6f, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x4d, 0x0a, 0x1c, 0x53, 0x65, 0x74, + 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x67, 0x65, + 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, + 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x22, 0xfc, 0x01, 0x0a, 0x1a, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, - 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, - 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, - 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, - 0x45, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x06, - 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x22, 0xd8, 0x01, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x41, 0x67, - 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, - 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, - 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x22, 0x48, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, - 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, - 0x6e, 0x63, 0x79, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x22, 0xe8, 0x01, 0x0a, 0x1b, - 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x64, - 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, - 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6a, - 0x6f, 0x69, 0x6e, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0b, 0x6a, 0x6f, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, - 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x4d, 0x0a, 0x1c, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, - 0x6e, 0x63, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, + 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x12, 0x17, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, + 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x09, 0x0a, 0x07, + 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x22, 0x4c, 0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x06, 0x61, - 0x67, 0x65, 0x6e, 0x63, 0x79, 0x22, 0xfc, 0x01, 0x0a, 0x1a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, - 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x88, 0x01, 0x01, - 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x61, 0x76, - 0x61, 0x74, 0x61, 0x72, 0x22, 0x4c, 0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x67, - 0x65, 0x6e, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, - 0x63, 0x79, 0x32, 0xaa, 0x18, 0x0a, 0x0f, 0x55, 0x73, 0x65, 0x72, 0x48, 0x6f, 0x73, 0x74, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, - 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x41, - 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x65, 0x61, 0x72, 0x63, 0x68, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x6f, - 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x6f, 0x41, 0x67, - 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, - 0x79, 0x54, 0x6f, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x78, 0x0a, 0x17, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x41, 0x67, 0x65, 0x6e, 0x63, - 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, - 0x69, 0x65, 0x77, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x69, - 0x65, 0x77, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x4b, - 0x69, 0x63, 0x6b, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x24, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x69, - 0x63, 0x6b, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x69, 0x63, 0x6b, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, - 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x49, 0x6e, - 0x76, 0x69, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, + 0x67, 0x65, 0x6e, 0x63, 0x79, 0x22, 0xf9, 0x01, 0x0a, 0x13, 0x48, 0x6f, 0x73, 0x74, 0x45, 0x6e, + 0x67, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x2c, 0x0a, + 0x12, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, + 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x31, 0x0a, 0x15, 0x76, + 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x6d, 0x69, 0x63, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x4d, 0x69, 0x63, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x24, + 0x0a, 0x0e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x6d, 0x69, 0x63, 0x5f, 0x64, 0x61, 0x79, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x4d, 0x69, 0x63, + 0x44, 0x61, 0x79, 0x73, 0x12, 0x36, 0x0a, 0x17, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x12, 0x23, 0x0a, 0x0d, + 0x6e, 0x65, 0x77, 0x5f, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6e, 0x65, 0x77, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, + 0x73, 0x22, 0xad, 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x45, 0x6e, 0x67, + 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1a, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x41, 0x74, 0x4d, + 0x73, 0x22, 0x5a, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x45, 0x6e, 0x67, 0x61, + 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x45, 0x6e, 0x67, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0xdf, 0x01, + 0x0a, 0x20, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x24, 0x0a, + 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x63, 0x63, + 0x75, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x6f, 0x63, 0x63, 0x75, 0x72, 0x72, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, + 0x3d, 0x0a, 0x21, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x32, 0x8c, + 0x1b, 0x0a, 0x0f, 0x55, 0x73, 0x65, 0x72, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x41, 0x67, 0x65, 0x6e, + 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x41, 0x67, 0x65, 0x6e, 0x63, + 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, + 0x68, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x6f, 0x41, 0x67, 0x65, 0x6e, + 0x63, 0x79, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x6f, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x6f, 0x41, + 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, + 0x17, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, + 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x41, + 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x41, 0x67, + 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x4b, 0x69, 0x63, 0x6b, 0x41, + 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x69, 0x63, 0x6b, 0x41, 0x67, + 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x4b, 0x69, 0x63, 0x6b, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, + 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x67, 0x65, + 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, - 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, - 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x42, 0x44, 0x12, - 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x51, 0x0a, 0x0a, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x20, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, - 0x6e, 0x76, 0x69, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x42, 0x44, 0x73, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x42, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x44, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x2a, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x4b, 0x0a, 0x08, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x42, 0x44, 0x12, 0x1e, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x69, + 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x69, + 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0a, + 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x20, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, + 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x76, + 0x69, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x60, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, + 0x44, 0x73, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, + 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, + 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, - 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x41, - 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x42, 0x44, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x72, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, - 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x54, 0x65, 0x61, 0x6d, - 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x54, 0x65, 0x61, 0x6d, 0x41, - 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x6c, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, - 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, - 0x11, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x41, 0x67, 0x65, 0x6e, + 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x41, 0x67, 0x65, 0x6e, 0x63, + 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, + 0x44, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x78, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, + 0x54, 0x65, 0x61, 0x6d, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x2d, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x67, 0x65, 0x6e, + 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x67, 0x65, 0x6e, 0x63, + 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x11, 0x47, 0x65, 0x74, + 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, + 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x72, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x6c, 0x65, + 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, + 0x73, 0x73, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, - 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2b, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, - 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x63, - 0x65, 0x73, 0x73, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x47, 0x65, 0x74, - 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x24, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, - 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, - 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, + 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x47, 0x65, 0x74, - 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, + 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x42, 0x44, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x44, 0x50, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, - 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, - 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, + 0x0a, 0x14, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, - 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x90, 0x01, 0x0a, 0x1f, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, - 0x76, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x49, 0x6e, 0x4d, - 0x79, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, - 0x76, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x49, 0x6e, 0x4d, - 0x79, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, - 0x6c, 0x65, 0x72, 0x73, 0x49, 0x6e, 0x4d, 0x79, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x29, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, - 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, - 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8d, 0x01, 0x0a, 0x1e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, - 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x75, - 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, - 0x62, 0x6d, 0x69, 0x74, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, - 0x72, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x43, - 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, - 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, + 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x90, 0x01, 0x0a, 0x1f, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, + 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x49, 0x6e, 0x4d, 0x79, 0x52, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, + 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x49, 0x6e, 0x4d, 0x79, 0x52, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, + 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, + 0x49, 0x6e, 0x4d, 0x79, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x43, + 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x43, 0x6f, + 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x8d, 0x01, 0x0a, 0x1e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x75, 0x62, 0x43, 0x6f, + 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, + 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, + 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x41, 0x70, 0x70, + 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x69, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, + 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x43, 0x6f, + 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, + 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x81, 0x01, 0x0a, 0x1a, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, + 0x75, 0x62, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x75, 0x62, 0x52, 0x65, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x75, 0x62, 0x52, + 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x69, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x75, + 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, + 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, + 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x47, 0x65, + 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, + 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, 0x17, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x61, 0x70, 0x61, 0x62, + 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x75, 0x73, 0x69, 0x6e, + 0x65, 0x73, 0x73, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, + 0x73, 0x73, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x53, + 0x63, 0x6f, 0x70, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, + 0x6c, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, - 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x81, 0x01, 0x0a, 0x1a, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, - 0x6c, 0x65, 0x72, 0x53, 0x75, 0x62, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x30, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x68, 0x65, 0x63, 0x6b, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x75, - 0x62, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, - 0x53, 0x75, 0x62, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, - 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x53, - 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, - 0x0a, 0x09, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1f, 0x2e, 0x68, 0x79, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x63, 0x6f, 0x70, + 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x63, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, + 0x65, 0x72, 0x73, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, - 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, - 0x0a, 0x17, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x43, - 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, - 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x75, - 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, - 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x26, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, - 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, - 0x15, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, - 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, - 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, - 0x67, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, - 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, - 0xc1, 0x09, 0x0a, 0x14, 0x55, 0x73, 0x65, 0x72, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, - 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x42, 0x44, 0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x42, 0x44, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x42, 0x44, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x42, 0x44, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x10, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x26, + 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, + 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2b, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, + 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, + 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x75, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, + 0x74, 0x45, 0x6e, 0x67, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, + 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x45, 0x6e, 0x67, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x45, 0x6e, 0x67, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, + 0x19, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc1, 0x09, + 0x0a, 0x14, 0x55, 0x73, 0x65, 0x72, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, + 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x69, - 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x6c, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, - 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8d, 0x01, - 0x0a, 0x1e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, - 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, - 0x6c, 0x6c, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x53, 0x75, 0x62, - 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, - 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x22, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x41, - 0x64, 0x64, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x28, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, - 0x69, 0x6e, 0x41, 0x64, 0x64, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x41, 0x64, 0x64, 0x41, - 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, - 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x67, - 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x60, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x67, - 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4a, - 0x6f, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x67, - 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, - 0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, - 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, - 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, + 0x44, 0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x42, 0x44, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x65, 0x74, 0x42, 0x44, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x42, 0x44, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x26, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, + 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, + 0x13, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, + 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8d, 0x01, 0x0a, 0x1e, + 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, + 0x6c, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x76, 0x69, 0x65, 0x77, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, + 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x53, 0x75, 0x62, 0x43, 0x6f, + 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x22, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x41, 0x64, 0x64, + 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x41, 0x64, 0x64, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x41, 0x64, 0x64, 0x41, 0x67, 0x65, + 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x54, 0x0a, 0x0b, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x21, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, + 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, + 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, + 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, + 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x6f, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x6f, 0x69, + 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, + 0x63, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x6f, + 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x42, 0x26, 0x5a, 0x24, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, + 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -7862,7 +8452,7 @@ func file_proto_user_v1_host_proto_rawDescGZIP() []byte { return file_proto_user_v1_host_proto_rawDescData } -var file_proto_user_v1_host_proto_msgTypes = make([]protoimpl.MessageInfo, 95) +var file_proto_user_v1_host_proto_msgTypes = make([]protoimpl.MessageInfo, 103) var file_proto_user_v1_host_proto_goTypes = []any{ (*HostProfile)(nil), // 0: hyapp.user.v1.HostProfile (*Agency)(nil), // 1: hyapp.user.v1.Agency @@ -7928,223 +8518,242 @@ var file_proto_user_v1_host_proto_goTypes = []any{ (*GetAgencyResponse)(nil), // 61: hyapp.user.v1.GetAgencyResponse (*CheckBusinessCapabilityRequest)(nil), // 62: hyapp.user.v1.CheckBusinessCapabilityRequest (*CheckBusinessCapabilityResponse)(nil), // 63: hyapp.user.v1.CheckBusinessCapabilityResponse - (*GetAgencyMembersRequest)(nil), // 64: hyapp.user.v1.GetAgencyMembersRequest - (*GetAgencyMembersResponse)(nil), // 65: hyapp.user.v1.GetAgencyMembersResponse - (*GetAgencyApplicationsRequest)(nil), // 66: hyapp.user.v1.GetAgencyApplicationsRequest - (*GetAgencyApplicationsResponse)(nil), // 67: hyapp.user.v1.GetAgencyApplicationsResponse - (*CreateBDLeaderRequest)(nil), // 68: hyapp.user.v1.CreateBDLeaderRequest - (*CreateBDLeaderResponse)(nil), // 69: hyapp.user.v1.CreateBDLeaderResponse - (*CreateBDRequest)(nil), // 70: hyapp.user.v1.CreateBDRequest - (*CreateBDResponse)(nil), // 71: hyapp.user.v1.CreateBDResponse - (*SetBDStatusRequest)(nil), // 72: hyapp.user.v1.SetBDStatusRequest - (*SetBDStatusResponse)(nil), // 73: hyapp.user.v1.SetBDStatusResponse - (*CreateCoinSellerRequest)(nil), // 74: hyapp.user.v1.CreateCoinSellerRequest - (*CreateCoinSellerResponse)(nil), // 75: hyapp.user.v1.CreateCoinSellerResponse - (*SetCoinSellerStatusRequest)(nil), // 76: hyapp.user.v1.SetCoinSellerStatusRequest - (*SetCoinSellerStatusResponse)(nil), // 77: hyapp.user.v1.SetCoinSellerStatusResponse - (*ReviewSubCoinSellerApplicationRequest)(nil), // 78: hyapp.user.v1.ReviewSubCoinSellerApplicationRequest - (*ReviewSubCoinSellerApplicationResponse)(nil), // 79: hyapp.user.v1.ReviewSubCoinSellerApplicationResponse - (*CreateAgencyRequest)(nil), // 80: hyapp.user.v1.CreateAgencyRequest - (*CreateAgencyResponse)(nil), // 81: hyapp.user.v1.CreateAgencyResponse - (*AdminAddAgencyHostRequest)(nil), // 82: hyapp.user.v1.AdminAddAgencyHostRequest - (*AdminAddAgencyHostResponse)(nil), // 83: hyapp.user.v1.AdminAddAgencyHostResponse - (*CloseAgencyRequest)(nil), // 84: hyapp.user.v1.CloseAgencyRequest - (*CloseAgencyResponse)(nil), // 85: hyapp.user.v1.CloseAgencyResponse - (*DeleteAgencyRequest)(nil), // 86: hyapp.user.v1.DeleteAgencyRequest - (*DeleteAgencyResponse)(nil), // 87: hyapp.user.v1.DeleteAgencyResponse - (*SetAgencyStatusRequest)(nil), // 88: hyapp.user.v1.SetAgencyStatusRequest - (*SetAgencyStatusResponse)(nil), // 89: hyapp.user.v1.SetAgencyStatusResponse - (*SetAgencyJoinEnabledRequest)(nil), // 90: hyapp.user.v1.SetAgencyJoinEnabledRequest - (*SetAgencyJoinEnabledResponse)(nil), // 91: hyapp.user.v1.SetAgencyJoinEnabledResponse - (*UpdateAgencyProfileRequest)(nil), // 92: hyapp.user.v1.UpdateAgencyProfileRequest - (*UpdateAgencyProfileResponse)(nil), // 93: hyapp.user.v1.UpdateAgencyProfileResponse - nil, // 94: hyapp.user.v1.BatchGetHostProfilesResponse.HostProfilesEntry - (*RequestMeta)(nil), // 95: hyapp.user.v1.RequestMeta + (*RoleScopePolicy)(nil), // 64: hyapp.user.v1.RoleScopePolicy + (*GetRoleScopePolicyRequest)(nil), // 65: hyapp.user.v1.GetRoleScopePolicyRequest + (*GetRoleScopePolicyResponse)(nil), // 66: hyapp.user.v1.GetRoleScopePolicyResponse + (*GetAgencyMembersRequest)(nil), // 67: hyapp.user.v1.GetAgencyMembersRequest + (*GetAgencyMembersResponse)(nil), // 68: hyapp.user.v1.GetAgencyMembersResponse + (*GetAgencyApplicationsRequest)(nil), // 69: hyapp.user.v1.GetAgencyApplicationsRequest + (*GetAgencyApplicationsResponse)(nil), // 70: hyapp.user.v1.GetAgencyApplicationsResponse + (*CreateBDLeaderRequest)(nil), // 71: hyapp.user.v1.CreateBDLeaderRequest + (*CreateBDLeaderResponse)(nil), // 72: hyapp.user.v1.CreateBDLeaderResponse + (*CreateBDRequest)(nil), // 73: hyapp.user.v1.CreateBDRequest + (*CreateBDResponse)(nil), // 74: hyapp.user.v1.CreateBDResponse + (*SetBDStatusRequest)(nil), // 75: hyapp.user.v1.SetBDStatusRequest + (*SetBDStatusResponse)(nil), // 76: hyapp.user.v1.SetBDStatusResponse + (*CreateCoinSellerRequest)(nil), // 77: hyapp.user.v1.CreateCoinSellerRequest + (*CreateCoinSellerResponse)(nil), // 78: hyapp.user.v1.CreateCoinSellerResponse + (*SetCoinSellerStatusRequest)(nil), // 79: hyapp.user.v1.SetCoinSellerStatusRequest + (*SetCoinSellerStatusResponse)(nil), // 80: hyapp.user.v1.SetCoinSellerStatusResponse + (*ReviewSubCoinSellerApplicationRequest)(nil), // 81: hyapp.user.v1.ReviewSubCoinSellerApplicationRequest + (*ReviewSubCoinSellerApplicationResponse)(nil), // 82: hyapp.user.v1.ReviewSubCoinSellerApplicationResponse + (*CreateAgencyRequest)(nil), // 83: hyapp.user.v1.CreateAgencyRequest + (*CreateAgencyResponse)(nil), // 84: hyapp.user.v1.CreateAgencyResponse + (*AdminAddAgencyHostRequest)(nil), // 85: hyapp.user.v1.AdminAddAgencyHostRequest + (*AdminAddAgencyHostResponse)(nil), // 86: hyapp.user.v1.AdminAddAgencyHostResponse + (*CloseAgencyRequest)(nil), // 87: hyapp.user.v1.CloseAgencyRequest + (*CloseAgencyResponse)(nil), // 88: hyapp.user.v1.CloseAgencyResponse + (*DeleteAgencyRequest)(nil), // 89: hyapp.user.v1.DeleteAgencyRequest + (*DeleteAgencyResponse)(nil), // 90: hyapp.user.v1.DeleteAgencyResponse + (*SetAgencyStatusRequest)(nil), // 91: hyapp.user.v1.SetAgencyStatusRequest + (*SetAgencyStatusResponse)(nil), // 92: hyapp.user.v1.SetAgencyStatusResponse + (*SetAgencyJoinEnabledRequest)(nil), // 93: hyapp.user.v1.SetAgencyJoinEnabledRequest + (*SetAgencyJoinEnabledResponse)(nil), // 94: hyapp.user.v1.SetAgencyJoinEnabledResponse + (*UpdateAgencyProfileRequest)(nil), // 95: hyapp.user.v1.UpdateAgencyProfileRequest + (*UpdateAgencyProfileResponse)(nil), // 96: hyapp.user.v1.UpdateAgencyProfileResponse + (*HostEngagementStats)(nil), // 97: hyapp.user.v1.HostEngagementStats + (*GetHostEngagementStatsRequest)(nil), // 98: hyapp.user.v1.GetHostEngagementStatsRequest + (*GetHostEngagementStatsResponse)(nil), // 99: hyapp.user.v1.GetHostEngagementStatsResponse + (*RecordPrivateMessageEventRequest)(nil), // 100: hyapp.user.v1.RecordPrivateMessageEventRequest + (*RecordPrivateMessageEventResponse)(nil), // 101: hyapp.user.v1.RecordPrivateMessageEventResponse + nil, // 102: hyapp.user.v1.BatchGetHostProfilesResponse.HostProfilesEntry + (*RequestMeta)(nil), // 103: hyapp.user.v1.RequestMeta } var file_proto_user_v1_host_proto_depIdxs = []int32{ - 95, // 0: hyapp.user.v1.SearchAgenciesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 0: hyapp.user.v1.SearchAgenciesRequest.meta:type_name -> hyapp.user.v1.RequestMeta 1, // 1: hyapp.user.v1.SearchAgenciesResponse.agencies:type_name -> hyapp.user.v1.Agency - 95, // 2: hyapp.user.v1.ApplyToAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 2: hyapp.user.v1.ApplyToAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta 9, // 3: hyapp.user.v1.ApplyToAgencyResponse.application:type_name -> hyapp.user.v1.AgencyApplication - 95, // 4: hyapp.user.v1.ReviewAgencyApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 4: hyapp.user.v1.ReviewAgencyApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta 9, // 5: hyapp.user.v1.ReviewAgencyApplicationResponse.application:type_name -> hyapp.user.v1.AgencyApplication 0, // 6: hyapp.user.v1.ReviewAgencyApplicationResponse.host_profile:type_name -> hyapp.user.v1.HostProfile 8, // 7: hyapp.user.v1.ReviewAgencyApplicationResponse.membership:type_name -> hyapp.user.v1.AgencyMembership - 95, // 8: hyapp.user.v1.KickAgencyHostRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 8: hyapp.user.v1.KickAgencyHostRequest.meta:type_name -> hyapp.user.v1.RequestMeta 8, // 9: hyapp.user.v1.KickAgencyHostResponse.membership:type_name -> hyapp.user.v1.AgencyMembership 0, // 10: hyapp.user.v1.KickAgencyHostResponse.host_profile:type_name -> hyapp.user.v1.HostProfile - 95, // 11: hyapp.user.v1.InviteAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 11: hyapp.user.v1.InviteAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta 10, // 12: hyapp.user.v1.InviteAgencyResponse.invitation:type_name -> hyapp.user.v1.RoleInvitation - 95, // 13: hyapp.user.v1.InviteBDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 13: hyapp.user.v1.InviteBDRequest.meta:type_name -> hyapp.user.v1.RequestMeta 10, // 14: hyapp.user.v1.InviteBDResponse.invitation:type_name -> hyapp.user.v1.RoleInvitation - 95, // 15: hyapp.user.v1.InviteHostRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 15: hyapp.user.v1.InviteHostRequest.meta:type_name -> hyapp.user.v1.RequestMeta 10, // 16: hyapp.user.v1.InviteHostResponse.invitation:type_name -> hyapp.user.v1.RoleInvitation - 95, // 17: hyapp.user.v1.ListBDLeaderBDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 17: hyapp.user.v1.ListBDLeaderBDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta 2, // 18: hyapp.user.v1.ListBDLeaderBDsResponse.bd_profiles:type_name -> hyapp.user.v1.BDProfile - 95, // 19: hyapp.user.v1.ListBDLeaderAgenciesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 19: hyapp.user.v1.ListBDLeaderAgenciesRequest.meta:type_name -> hyapp.user.v1.RequestMeta 1, // 20: hyapp.user.v1.ListBDLeaderAgenciesResponse.agencies:type_name -> hyapp.user.v1.Agency - 95, // 21: hyapp.user.v1.ListManagerTeamAgenciesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 21: hyapp.user.v1.ListManagerTeamAgenciesRequest.meta:type_name -> hyapp.user.v1.RequestMeta 30, // 22: hyapp.user.v1.ListManagerTeamAgenciesResponse.agencies:type_name -> hyapp.user.v1.ManagerTeamAgency - 95, // 23: hyapp.user.v1.ListBDAgenciesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 23: hyapp.user.v1.ListBDAgenciesRequest.meta:type_name -> hyapp.user.v1.RequestMeta 1, // 24: hyapp.user.v1.ListBDAgenciesResponse.agencies:type_name -> hyapp.user.v1.Agency - 95, // 25: hyapp.user.v1.ListRoleInvitationsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 25: hyapp.user.v1.ListRoleInvitationsRequest.meta:type_name -> hyapp.user.v1.RequestMeta 10, // 26: hyapp.user.v1.ListRoleInvitationsResponse.invitations:type_name -> hyapp.user.v1.RoleInvitation - 95, // 27: hyapp.user.v1.GetRoleInvitationRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 27: hyapp.user.v1.GetRoleInvitationRequest.meta:type_name -> hyapp.user.v1.RequestMeta 10, // 28: hyapp.user.v1.GetRoleInvitationResponse.invitation:type_name -> hyapp.user.v1.RoleInvitation - 95, // 29: hyapp.user.v1.ProcessRoleInvitationRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 29: hyapp.user.v1.ProcessRoleInvitationRequest.meta:type_name -> hyapp.user.v1.RequestMeta 10, // 30: hyapp.user.v1.ProcessRoleInvitationResponse.invitation:type_name -> hyapp.user.v1.RoleInvitation 0, // 31: hyapp.user.v1.ProcessRoleInvitationResponse.host_profile:type_name -> hyapp.user.v1.HostProfile 1, // 32: hyapp.user.v1.ProcessRoleInvitationResponse.agency:type_name -> hyapp.user.v1.Agency 8, // 33: hyapp.user.v1.ProcessRoleInvitationResponse.membership:type_name -> hyapp.user.v1.AgencyMembership 2, // 34: hyapp.user.v1.ProcessRoleInvitationResponse.bd_profile:type_name -> hyapp.user.v1.BDProfile - 95, // 35: hyapp.user.v1.GetHostProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 35: hyapp.user.v1.GetHostProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta 0, // 36: hyapp.user.v1.GetHostProfileResponse.host_profile:type_name -> hyapp.user.v1.HostProfile - 95, // 37: hyapp.user.v1.BatchGetHostProfilesRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 94, // 38: hyapp.user.v1.BatchGetHostProfilesResponse.host_profiles:type_name -> hyapp.user.v1.BatchGetHostProfilesResponse.HostProfilesEntry - 95, // 39: hyapp.user.v1.GetBDProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 37: hyapp.user.v1.BatchGetHostProfilesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 102, // 38: hyapp.user.v1.BatchGetHostProfilesResponse.host_profiles:type_name -> hyapp.user.v1.BatchGetHostProfilesResponse.HostProfilesEntry + 103, // 39: hyapp.user.v1.GetBDProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta 2, // 40: hyapp.user.v1.GetBDProfileResponse.bd_profile:type_name -> hyapp.user.v1.BDProfile - 95, // 41: hyapp.user.v1.GetCoinSellerProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 41: hyapp.user.v1.GetCoinSellerProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta 3, // 42: hyapp.user.v1.GetCoinSellerProfileResponse.coin_seller_profile:type_name -> hyapp.user.v1.CoinSellerProfile - 95, // 43: hyapp.user.v1.ListActiveCoinSellersInMyRegionRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 43: hyapp.user.v1.ListActiveCoinSellersInMyRegionRequest.meta:type_name -> hyapp.user.v1.RequestMeta 4, // 44: hyapp.user.v1.ListActiveCoinSellersInMyRegionResponse.coin_sellers:type_name -> hyapp.user.v1.CoinSellerListItem - 95, // 45: hyapp.user.v1.CreateSubCoinSellerRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 45: hyapp.user.v1.CreateSubCoinSellerRequest.meta:type_name -> hyapp.user.v1.RequestMeta 5, // 46: hyapp.user.v1.CreateSubCoinSellerResponse.relation:type_name -> hyapp.user.v1.CoinSellerSubRelation 4, // 47: hyapp.user.v1.CreateSubCoinSellerResponse.child:type_name -> hyapp.user.v1.CoinSellerListItem - 95, // 48: hyapp.user.v1.SubmitSubCoinSellerApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 48: hyapp.user.v1.SubmitSubCoinSellerApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta 6, // 49: hyapp.user.v1.SubmitSubCoinSellerApplicationResponse.application:type_name -> hyapp.user.v1.CoinSellerSubApplication - 95, // 50: hyapp.user.v1.ListSubCoinSellersRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 50: hyapp.user.v1.ListSubCoinSellersRequest.meta:type_name -> hyapp.user.v1.RequestMeta 4, // 51: hyapp.user.v1.ListSubCoinSellersResponse.children:type_name -> hyapp.user.v1.CoinSellerListItem - 95, // 52: hyapp.user.v1.CheckCoinSellerSubRelationRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 52: hyapp.user.v1.CheckCoinSellerSubRelationRequest.meta:type_name -> hyapp.user.v1.RequestMeta 4, // 53: hyapp.user.v1.CheckCoinSellerSubRelationResponse.child:type_name -> hyapp.user.v1.CoinSellerListItem - 95, // 54: hyapp.user.v1.GetUserRoleSummaryRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 54: hyapp.user.v1.GetUserRoleSummaryRequest.meta:type_name -> hyapp.user.v1.RequestMeta 7, // 55: hyapp.user.v1.GetUserRoleSummaryResponse.summary:type_name -> hyapp.user.v1.UserRoleSummary - 95, // 56: hyapp.user.v1.GetAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 56: hyapp.user.v1.GetAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta 1, // 57: hyapp.user.v1.GetAgencyResponse.agency:type_name -> hyapp.user.v1.Agency - 95, // 58: hyapp.user.v1.CheckBusinessCapabilityRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 95, // 59: hyapp.user.v1.GetAgencyMembersRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 8, // 60: hyapp.user.v1.GetAgencyMembersResponse.memberships:type_name -> hyapp.user.v1.AgencyMembership - 95, // 61: hyapp.user.v1.GetAgencyApplicationsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 9, // 62: hyapp.user.v1.GetAgencyApplicationsResponse.applications:type_name -> hyapp.user.v1.AgencyApplication - 95, // 63: hyapp.user.v1.CreateBDLeaderRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 2, // 64: hyapp.user.v1.CreateBDLeaderResponse.bd_profile:type_name -> hyapp.user.v1.BDProfile - 95, // 65: hyapp.user.v1.CreateBDRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 2, // 66: hyapp.user.v1.CreateBDResponse.bd_profile:type_name -> hyapp.user.v1.BDProfile - 95, // 67: hyapp.user.v1.SetBDStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 2, // 68: hyapp.user.v1.SetBDStatusResponse.bd_profile:type_name -> hyapp.user.v1.BDProfile - 95, // 69: hyapp.user.v1.CreateCoinSellerRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 3, // 70: hyapp.user.v1.CreateCoinSellerResponse.coin_seller_profile:type_name -> hyapp.user.v1.CoinSellerProfile - 95, // 71: hyapp.user.v1.SetCoinSellerStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 3, // 72: hyapp.user.v1.SetCoinSellerStatusResponse.coin_seller_profile:type_name -> hyapp.user.v1.CoinSellerProfile - 95, // 73: hyapp.user.v1.ReviewSubCoinSellerApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 6, // 74: hyapp.user.v1.ReviewSubCoinSellerApplicationResponse.application:type_name -> hyapp.user.v1.CoinSellerSubApplication - 5, // 75: hyapp.user.v1.ReviewSubCoinSellerApplicationResponse.relation:type_name -> hyapp.user.v1.CoinSellerSubRelation - 4, // 76: hyapp.user.v1.ReviewSubCoinSellerApplicationResponse.child:type_name -> hyapp.user.v1.CoinSellerListItem - 95, // 77: hyapp.user.v1.CreateAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 78: hyapp.user.v1.CreateAgencyResponse.agency:type_name -> hyapp.user.v1.Agency - 0, // 79: hyapp.user.v1.CreateAgencyResponse.host_profile:type_name -> hyapp.user.v1.HostProfile - 8, // 80: hyapp.user.v1.CreateAgencyResponse.membership:type_name -> hyapp.user.v1.AgencyMembership - 95, // 81: hyapp.user.v1.AdminAddAgencyHostRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 82: hyapp.user.v1.AdminAddAgencyHostResponse.agency:type_name -> hyapp.user.v1.Agency - 0, // 83: hyapp.user.v1.AdminAddAgencyHostResponse.host_profile:type_name -> hyapp.user.v1.HostProfile - 8, // 84: hyapp.user.v1.AdminAddAgencyHostResponse.membership:type_name -> hyapp.user.v1.AgencyMembership - 95, // 85: hyapp.user.v1.CloseAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 86: hyapp.user.v1.CloseAgencyResponse.agency:type_name -> hyapp.user.v1.Agency - 95, // 87: hyapp.user.v1.DeleteAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 88: hyapp.user.v1.DeleteAgencyResponse.agency:type_name -> hyapp.user.v1.Agency - 95, // 89: hyapp.user.v1.SetAgencyStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 90: hyapp.user.v1.SetAgencyStatusResponse.agency:type_name -> hyapp.user.v1.Agency - 95, // 91: hyapp.user.v1.SetAgencyJoinEnabledRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 92: hyapp.user.v1.SetAgencyJoinEnabledResponse.agency:type_name -> hyapp.user.v1.Agency - 95, // 93: hyapp.user.v1.UpdateAgencyProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 94: hyapp.user.v1.UpdateAgencyProfileResponse.agency:type_name -> hyapp.user.v1.Agency - 0, // 95: hyapp.user.v1.BatchGetHostProfilesResponse.HostProfilesEntry.value:type_name -> hyapp.user.v1.HostProfile - 11, // 96: hyapp.user.v1.UserHostService.SearchAgencies:input_type -> hyapp.user.v1.SearchAgenciesRequest - 13, // 97: hyapp.user.v1.UserHostService.ApplyToAgency:input_type -> hyapp.user.v1.ApplyToAgencyRequest - 15, // 98: hyapp.user.v1.UserHostService.ReviewAgencyApplication:input_type -> hyapp.user.v1.ReviewAgencyApplicationRequest - 17, // 99: hyapp.user.v1.UserHostService.KickAgencyHost:input_type -> hyapp.user.v1.KickAgencyHostRequest - 19, // 100: hyapp.user.v1.UserHostService.InviteAgency:input_type -> hyapp.user.v1.InviteAgencyRequest - 21, // 101: hyapp.user.v1.UserHostService.InviteBD:input_type -> hyapp.user.v1.InviteBDRequest - 23, // 102: hyapp.user.v1.UserHostService.InviteHost:input_type -> hyapp.user.v1.InviteHostRequest - 25, // 103: hyapp.user.v1.UserHostService.ListBDLeaderBDs:input_type -> hyapp.user.v1.ListBDLeaderBDsRequest - 27, // 104: hyapp.user.v1.UserHostService.ListBDLeaderAgencies:input_type -> hyapp.user.v1.ListBDLeaderAgenciesRequest - 32, // 105: hyapp.user.v1.UserHostService.ListBDAgencies:input_type -> hyapp.user.v1.ListBDAgenciesRequest - 29, // 106: hyapp.user.v1.UserHostService.ListManagerTeamAgencies:input_type -> hyapp.user.v1.ListManagerTeamAgenciesRequest - 34, // 107: hyapp.user.v1.UserHostService.ListRoleInvitations:input_type -> hyapp.user.v1.ListRoleInvitationsRequest - 36, // 108: hyapp.user.v1.UserHostService.GetRoleInvitation:input_type -> hyapp.user.v1.GetRoleInvitationRequest - 38, // 109: hyapp.user.v1.UserHostService.ProcessRoleInvitation:input_type -> hyapp.user.v1.ProcessRoleInvitationRequest - 40, // 110: hyapp.user.v1.UserHostService.GetHostProfile:input_type -> hyapp.user.v1.GetHostProfileRequest - 42, // 111: hyapp.user.v1.UserHostService.BatchGetHostProfiles:input_type -> hyapp.user.v1.BatchGetHostProfilesRequest - 44, // 112: hyapp.user.v1.UserHostService.GetBDProfile:input_type -> hyapp.user.v1.GetBDProfileRequest - 46, // 113: hyapp.user.v1.UserHostService.GetCoinSellerProfile:input_type -> hyapp.user.v1.GetCoinSellerProfileRequest - 48, // 114: hyapp.user.v1.UserHostService.ListActiveCoinSellersInMyRegion:input_type -> hyapp.user.v1.ListActiveCoinSellersInMyRegionRequest - 50, // 115: hyapp.user.v1.UserHostService.CreateSubCoinSeller:input_type -> hyapp.user.v1.CreateSubCoinSellerRequest - 52, // 116: hyapp.user.v1.UserHostService.SubmitSubCoinSellerApplication:input_type -> hyapp.user.v1.SubmitSubCoinSellerApplicationRequest - 54, // 117: hyapp.user.v1.UserHostService.ListSubCoinSellers:input_type -> hyapp.user.v1.ListSubCoinSellersRequest - 56, // 118: hyapp.user.v1.UserHostService.CheckCoinSellerSubRelation:input_type -> hyapp.user.v1.CheckCoinSellerSubRelationRequest - 58, // 119: hyapp.user.v1.UserHostService.GetUserRoleSummary:input_type -> hyapp.user.v1.GetUserRoleSummaryRequest - 60, // 120: hyapp.user.v1.UserHostService.GetAgency:input_type -> hyapp.user.v1.GetAgencyRequest - 62, // 121: hyapp.user.v1.UserHostService.CheckBusinessCapability:input_type -> hyapp.user.v1.CheckBusinessCapabilityRequest - 64, // 122: hyapp.user.v1.UserHostService.GetAgencyMembers:input_type -> hyapp.user.v1.GetAgencyMembersRequest - 66, // 123: hyapp.user.v1.UserHostService.GetAgencyApplications:input_type -> hyapp.user.v1.GetAgencyApplicationsRequest - 92, // 124: hyapp.user.v1.UserHostService.UpdateAgencyProfile:input_type -> hyapp.user.v1.UpdateAgencyProfileRequest - 68, // 125: hyapp.user.v1.UserHostAdminService.CreateBDLeader:input_type -> hyapp.user.v1.CreateBDLeaderRequest - 70, // 126: hyapp.user.v1.UserHostAdminService.CreateBD:input_type -> hyapp.user.v1.CreateBDRequest - 72, // 127: hyapp.user.v1.UserHostAdminService.SetBDStatus:input_type -> hyapp.user.v1.SetBDStatusRequest - 74, // 128: hyapp.user.v1.UserHostAdminService.CreateCoinSeller:input_type -> hyapp.user.v1.CreateCoinSellerRequest - 76, // 129: hyapp.user.v1.UserHostAdminService.SetCoinSellerStatus:input_type -> hyapp.user.v1.SetCoinSellerStatusRequest - 78, // 130: hyapp.user.v1.UserHostAdminService.ReviewSubCoinSellerApplication:input_type -> hyapp.user.v1.ReviewSubCoinSellerApplicationRequest - 80, // 131: hyapp.user.v1.UserHostAdminService.CreateAgency:input_type -> hyapp.user.v1.CreateAgencyRequest - 82, // 132: hyapp.user.v1.UserHostAdminService.AdminAddAgencyHost:input_type -> hyapp.user.v1.AdminAddAgencyHostRequest - 84, // 133: hyapp.user.v1.UserHostAdminService.CloseAgency:input_type -> hyapp.user.v1.CloseAgencyRequest - 86, // 134: hyapp.user.v1.UserHostAdminService.DeleteAgency:input_type -> hyapp.user.v1.DeleteAgencyRequest - 88, // 135: hyapp.user.v1.UserHostAdminService.SetAgencyStatus:input_type -> hyapp.user.v1.SetAgencyStatusRequest - 90, // 136: hyapp.user.v1.UserHostAdminService.SetAgencyJoinEnabled:input_type -> hyapp.user.v1.SetAgencyJoinEnabledRequest - 12, // 137: hyapp.user.v1.UserHostService.SearchAgencies:output_type -> hyapp.user.v1.SearchAgenciesResponse - 14, // 138: hyapp.user.v1.UserHostService.ApplyToAgency:output_type -> hyapp.user.v1.ApplyToAgencyResponse - 16, // 139: hyapp.user.v1.UserHostService.ReviewAgencyApplication:output_type -> hyapp.user.v1.ReviewAgencyApplicationResponse - 18, // 140: hyapp.user.v1.UserHostService.KickAgencyHost:output_type -> hyapp.user.v1.KickAgencyHostResponse - 20, // 141: hyapp.user.v1.UserHostService.InviteAgency:output_type -> hyapp.user.v1.InviteAgencyResponse - 22, // 142: hyapp.user.v1.UserHostService.InviteBD:output_type -> hyapp.user.v1.InviteBDResponse - 24, // 143: hyapp.user.v1.UserHostService.InviteHost:output_type -> hyapp.user.v1.InviteHostResponse - 26, // 144: hyapp.user.v1.UserHostService.ListBDLeaderBDs:output_type -> hyapp.user.v1.ListBDLeaderBDsResponse - 28, // 145: hyapp.user.v1.UserHostService.ListBDLeaderAgencies:output_type -> hyapp.user.v1.ListBDLeaderAgenciesResponse - 33, // 146: hyapp.user.v1.UserHostService.ListBDAgencies:output_type -> hyapp.user.v1.ListBDAgenciesResponse - 31, // 147: hyapp.user.v1.UserHostService.ListManagerTeamAgencies:output_type -> hyapp.user.v1.ListManagerTeamAgenciesResponse - 35, // 148: hyapp.user.v1.UserHostService.ListRoleInvitations:output_type -> hyapp.user.v1.ListRoleInvitationsResponse - 37, // 149: hyapp.user.v1.UserHostService.GetRoleInvitation:output_type -> hyapp.user.v1.GetRoleInvitationResponse - 39, // 150: hyapp.user.v1.UserHostService.ProcessRoleInvitation:output_type -> hyapp.user.v1.ProcessRoleInvitationResponse - 41, // 151: hyapp.user.v1.UserHostService.GetHostProfile:output_type -> hyapp.user.v1.GetHostProfileResponse - 43, // 152: hyapp.user.v1.UserHostService.BatchGetHostProfiles:output_type -> hyapp.user.v1.BatchGetHostProfilesResponse - 45, // 153: hyapp.user.v1.UserHostService.GetBDProfile:output_type -> hyapp.user.v1.GetBDProfileResponse - 47, // 154: hyapp.user.v1.UserHostService.GetCoinSellerProfile:output_type -> hyapp.user.v1.GetCoinSellerProfileResponse - 49, // 155: hyapp.user.v1.UserHostService.ListActiveCoinSellersInMyRegion:output_type -> hyapp.user.v1.ListActiveCoinSellersInMyRegionResponse - 51, // 156: hyapp.user.v1.UserHostService.CreateSubCoinSeller:output_type -> hyapp.user.v1.CreateSubCoinSellerResponse - 53, // 157: hyapp.user.v1.UserHostService.SubmitSubCoinSellerApplication:output_type -> hyapp.user.v1.SubmitSubCoinSellerApplicationResponse - 55, // 158: hyapp.user.v1.UserHostService.ListSubCoinSellers:output_type -> hyapp.user.v1.ListSubCoinSellersResponse - 57, // 159: hyapp.user.v1.UserHostService.CheckCoinSellerSubRelation:output_type -> hyapp.user.v1.CheckCoinSellerSubRelationResponse - 59, // 160: hyapp.user.v1.UserHostService.GetUserRoleSummary:output_type -> hyapp.user.v1.GetUserRoleSummaryResponse - 61, // 161: hyapp.user.v1.UserHostService.GetAgency:output_type -> hyapp.user.v1.GetAgencyResponse - 63, // 162: hyapp.user.v1.UserHostService.CheckBusinessCapability:output_type -> hyapp.user.v1.CheckBusinessCapabilityResponse - 65, // 163: hyapp.user.v1.UserHostService.GetAgencyMembers:output_type -> hyapp.user.v1.GetAgencyMembersResponse - 67, // 164: hyapp.user.v1.UserHostService.GetAgencyApplications:output_type -> hyapp.user.v1.GetAgencyApplicationsResponse - 93, // 165: hyapp.user.v1.UserHostService.UpdateAgencyProfile:output_type -> hyapp.user.v1.UpdateAgencyProfileResponse - 69, // 166: hyapp.user.v1.UserHostAdminService.CreateBDLeader:output_type -> hyapp.user.v1.CreateBDLeaderResponse - 71, // 167: hyapp.user.v1.UserHostAdminService.CreateBD:output_type -> hyapp.user.v1.CreateBDResponse - 73, // 168: hyapp.user.v1.UserHostAdminService.SetBDStatus:output_type -> hyapp.user.v1.SetBDStatusResponse - 75, // 169: hyapp.user.v1.UserHostAdminService.CreateCoinSeller:output_type -> hyapp.user.v1.CreateCoinSellerResponse - 77, // 170: hyapp.user.v1.UserHostAdminService.SetCoinSellerStatus:output_type -> hyapp.user.v1.SetCoinSellerStatusResponse - 79, // 171: hyapp.user.v1.UserHostAdminService.ReviewSubCoinSellerApplication:output_type -> hyapp.user.v1.ReviewSubCoinSellerApplicationResponse - 81, // 172: hyapp.user.v1.UserHostAdminService.CreateAgency:output_type -> hyapp.user.v1.CreateAgencyResponse - 83, // 173: hyapp.user.v1.UserHostAdminService.AdminAddAgencyHost:output_type -> hyapp.user.v1.AdminAddAgencyHostResponse - 85, // 174: hyapp.user.v1.UserHostAdminService.CloseAgency:output_type -> hyapp.user.v1.CloseAgencyResponse - 87, // 175: hyapp.user.v1.UserHostAdminService.DeleteAgency:output_type -> hyapp.user.v1.DeleteAgencyResponse - 89, // 176: hyapp.user.v1.UserHostAdminService.SetAgencyStatus:output_type -> hyapp.user.v1.SetAgencyStatusResponse - 91, // 177: hyapp.user.v1.UserHostAdminService.SetAgencyJoinEnabled:output_type -> hyapp.user.v1.SetAgencyJoinEnabledResponse - 137, // [137:178] is the sub-list for method output_type - 96, // [96:137] is the sub-list for method input_type - 96, // [96:96] is the sub-list for extension type_name - 96, // [96:96] is the sub-list for extension extendee - 0, // [0:96] is the sub-list for field type_name + 103, // 58: hyapp.user.v1.CheckBusinessCapabilityRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 59: hyapp.user.v1.GetRoleScopePolicyRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 64, // 60: hyapp.user.v1.GetRoleScopePolicyResponse.policy:type_name -> hyapp.user.v1.RoleScopePolicy + 103, // 61: hyapp.user.v1.GetAgencyMembersRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 8, // 62: hyapp.user.v1.GetAgencyMembersResponse.memberships:type_name -> hyapp.user.v1.AgencyMembership + 103, // 63: hyapp.user.v1.GetAgencyApplicationsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 9, // 64: hyapp.user.v1.GetAgencyApplicationsResponse.applications:type_name -> hyapp.user.v1.AgencyApplication + 103, // 65: hyapp.user.v1.CreateBDLeaderRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 2, // 66: hyapp.user.v1.CreateBDLeaderResponse.bd_profile:type_name -> hyapp.user.v1.BDProfile + 103, // 67: hyapp.user.v1.CreateBDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 2, // 68: hyapp.user.v1.CreateBDResponse.bd_profile:type_name -> hyapp.user.v1.BDProfile + 103, // 69: hyapp.user.v1.SetBDStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 2, // 70: hyapp.user.v1.SetBDStatusResponse.bd_profile:type_name -> hyapp.user.v1.BDProfile + 103, // 71: hyapp.user.v1.CreateCoinSellerRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 3, // 72: hyapp.user.v1.CreateCoinSellerResponse.coin_seller_profile:type_name -> hyapp.user.v1.CoinSellerProfile + 103, // 73: hyapp.user.v1.SetCoinSellerStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 3, // 74: hyapp.user.v1.SetCoinSellerStatusResponse.coin_seller_profile:type_name -> hyapp.user.v1.CoinSellerProfile + 103, // 75: hyapp.user.v1.ReviewSubCoinSellerApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 6, // 76: hyapp.user.v1.ReviewSubCoinSellerApplicationResponse.application:type_name -> hyapp.user.v1.CoinSellerSubApplication + 5, // 77: hyapp.user.v1.ReviewSubCoinSellerApplicationResponse.relation:type_name -> hyapp.user.v1.CoinSellerSubRelation + 4, // 78: hyapp.user.v1.ReviewSubCoinSellerApplicationResponse.child:type_name -> hyapp.user.v1.CoinSellerListItem + 103, // 79: hyapp.user.v1.CreateAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 80: hyapp.user.v1.CreateAgencyResponse.agency:type_name -> hyapp.user.v1.Agency + 0, // 81: hyapp.user.v1.CreateAgencyResponse.host_profile:type_name -> hyapp.user.v1.HostProfile + 8, // 82: hyapp.user.v1.CreateAgencyResponse.membership:type_name -> hyapp.user.v1.AgencyMembership + 103, // 83: hyapp.user.v1.AdminAddAgencyHostRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 84: hyapp.user.v1.AdminAddAgencyHostResponse.agency:type_name -> hyapp.user.v1.Agency + 0, // 85: hyapp.user.v1.AdminAddAgencyHostResponse.host_profile:type_name -> hyapp.user.v1.HostProfile + 8, // 86: hyapp.user.v1.AdminAddAgencyHostResponse.membership:type_name -> hyapp.user.v1.AgencyMembership + 103, // 87: hyapp.user.v1.CloseAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 88: hyapp.user.v1.CloseAgencyResponse.agency:type_name -> hyapp.user.v1.Agency + 103, // 89: hyapp.user.v1.DeleteAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 90: hyapp.user.v1.DeleteAgencyResponse.agency:type_name -> hyapp.user.v1.Agency + 103, // 91: hyapp.user.v1.SetAgencyStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 92: hyapp.user.v1.SetAgencyStatusResponse.agency:type_name -> hyapp.user.v1.Agency + 103, // 93: hyapp.user.v1.SetAgencyJoinEnabledRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 94: hyapp.user.v1.SetAgencyJoinEnabledResponse.agency:type_name -> hyapp.user.v1.Agency + 103, // 95: hyapp.user.v1.UpdateAgencyProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 96: hyapp.user.v1.UpdateAgencyProfileResponse.agency:type_name -> hyapp.user.v1.Agency + 103, // 97: hyapp.user.v1.GetHostEngagementStatsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 97, // 98: hyapp.user.v1.GetHostEngagementStatsResponse.stats:type_name -> hyapp.user.v1.HostEngagementStats + 103, // 99: hyapp.user.v1.RecordPrivateMessageEventRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 0, // 100: hyapp.user.v1.BatchGetHostProfilesResponse.HostProfilesEntry.value:type_name -> hyapp.user.v1.HostProfile + 11, // 101: hyapp.user.v1.UserHostService.SearchAgencies:input_type -> hyapp.user.v1.SearchAgenciesRequest + 13, // 102: hyapp.user.v1.UserHostService.ApplyToAgency:input_type -> hyapp.user.v1.ApplyToAgencyRequest + 15, // 103: hyapp.user.v1.UserHostService.ReviewAgencyApplication:input_type -> hyapp.user.v1.ReviewAgencyApplicationRequest + 17, // 104: hyapp.user.v1.UserHostService.KickAgencyHost:input_type -> hyapp.user.v1.KickAgencyHostRequest + 19, // 105: hyapp.user.v1.UserHostService.InviteAgency:input_type -> hyapp.user.v1.InviteAgencyRequest + 21, // 106: hyapp.user.v1.UserHostService.InviteBD:input_type -> hyapp.user.v1.InviteBDRequest + 23, // 107: hyapp.user.v1.UserHostService.InviteHost:input_type -> hyapp.user.v1.InviteHostRequest + 25, // 108: hyapp.user.v1.UserHostService.ListBDLeaderBDs:input_type -> hyapp.user.v1.ListBDLeaderBDsRequest + 27, // 109: hyapp.user.v1.UserHostService.ListBDLeaderAgencies:input_type -> hyapp.user.v1.ListBDLeaderAgenciesRequest + 32, // 110: hyapp.user.v1.UserHostService.ListBDAgencies:input_type -> hyapp.user.v1.ListBDAgenciesRequest + 29, // 111: hyapp.user.v1.UserHostService.ListManagerTeamAgencies:input_type -> hyapp.user.v1.ListManagerTeamAgenciesRequest + 34, // 112: hyapp.user.v1.UserHostService.ListRoleInvitations:input_type -> hyapp.user.v1.ListRoleInvitationsRequest + 36, // 113: hyapp.user.v1.UserHostService.GetRoleInvitation:input_type -> hyapp.user.v1.GetRoleInvitationRequest + 38, // 114: hyapp.user.v1.UserHostService.ProcessRoleInvitation:input_type -> hyapp.user.v1.ProcessRoleInvitationRequest + 40, // 115: hyapp.user.v1.UserHostService.GetHostProfile:input_type -> hyapp.user.v1.GetHostProfileRequest + 42, // 116: hyapp.user.v1.UserHostService.BatchGetHostProfiles:input_type -> hyapp.user.v1.BatchGetHostProfilesRequest + 44, // 117: hyapp.user.v1.UserHostService.GetBDProfile:input_type -> hyapp.user.v1.GetBDProfileRequest + 46, // 118: hyapp.user.v1.UserHostService.GetCoinSellerProfile:input_type -> hyapp.user.v1.GetCoinSellerProfileRequest + 48, // 119: hyapp.user.v1.UserHostService.ListActiveCoinSellersInMyRegion:input_type -> hyapp.user.v1.ListActiveCoinSellersInMyRegionRequest + 50, // 120: hyapp.user.v1.UserHostService.CreateSubCoinSeller:input_type -> hyapp.user.v1.CreateSubCoinSellerRequest + 52, // 121: hyapp.user.v1.UserHostService.SubmitSubCoinSellerApplication:input_type -> hyapp.user.v1.SubmitSubCoinSellerApplicationRequest + 54, // 122: hyapp.user.v1.UserHostService.ListSubCoinSellers:input_type -> hyapp.user.v1.ListSubCoinSellersRequest + 56, // 123: hyapp.user.v1.UserHostService.CheckCoinSellerSubRelation:input_type -> hyapp.user.v1.CheckCoinSellerSubRelationRequest + 58, // 124: hyapp.user.v1.UserHostService.GetUserRoleSummary:input_type -> hyapp.user.v1.GetUserRoleSummaryRequest + 60, // 125: hyapp.user.v1.UserHostService.GetAgency:input_type -> hyapp.user.v1.GetAgencyRequest + 62, // 126: hyapp.user.v1.UserHostService.CheckBusinessCapability:input_type -> hyapp.user.v1.CheckBusinessCapabilityRequest + 65, // 127: hyapp.user.v1.UserHostService.GetRoleScopePolicy:input_type -> hyapp.user.v1.GetRoleScopePolicyRequest + 67, // 128: hyapp.user.v1.UserHostService.GetAgencyMembers:input_type -> hyapp.user.v1.GetAgencyMembersRequest + 69, // 129: hyapp.user.v1.UserHostService.GetAgencyApplications:input_type -> hyapp.user.v1.GetAgencyApplicationsRequest + 95, // 130: hyapp.user.v1.UserHostService.UpdateAgencyProfile:input_type -> hyapp.user.v1.UpdateAgencyProfileRequest + 98, // 131: hyapp.user.v1.UserHostService.GetHostEngagementStats:input_type -> hyapp.user.v1.GetHostEngagementStatsRequest + 100, // 132: hyapp.user.v1.UserHostService.RecordPrivateMessageEvent:input_type -> hyapp.user.v1.RecordPrivateMessageEventRequest + 71, // 133: hyapp.user.v1.UserHostAdminService.CreateBDLeader:input_type -> hyapp.user.v1.CreateBDLeaderRequest + 73, // 134: hyapp.user.v1.UserHostAdminService.CreateBD:input_type -> hyapp.user.v1.CreateBDRequest + 75, // 135: hyapp.user.v1.UserHostAdminService.SetBDStatus:input_type -> hyapp.user.v1.SetBDStatusRequest + 77, // 136: hyapp.user.v1.UserHostAdminService.CreateCoinSeller:input_type -> hyapp.user.v1.CreateCoinSellerRequest + 79, // 137: hyapp.user.v1.UserHostAdminService.SetCoinSellerStatus:input_type -> hyapp.user.v1.SetCoinSellerStatusRequest + 81, // 138: hyapp.user.v1.UserHostAdminService.ReviewSubCoinSellerApplication:input_type -> hyapp.user.v1.ReviewSubCoinSellerApplicationRequest + 83, // 139: hyapp.user.v1.UserHostAdminService.CreateAgency:input_type -> hyapp.user.v1.CreateAgencyRequest + 85, // 140: hyapp.user.v1.UserHostAdminService.AdminAddAgencyHost:input_type -> hyapp.user.v1.AdminAddAgencyHostRequest + 87, // 141: hyapp.user.v1.UserHostAdminService.CloseAgency:input_type -> hyapp.user.v1.CloseAgencyRequest + 89, // 142: hyapp.user.v1.UserHostAdminService.DeleteAgency:input_type -> hyapp.user.v1.DeleteAgencyRequest + 91, // 143: hyapp.user.v1.UserHostAdminService.SetAgencyStatus:input_type -> hyapp.user.v1.SetAgencyStatusRequest + 93, // 144: hyapp.user.v1.UserHostAdminService.SetAgencyJoinEnabled:input_type -> hyapp.user.v1.SetAgencyJoinEnabledRequest + 12, // 145: hyapp.user.v1.UserHostService.SearchAgencies:output_type -> hyapp.user.v1.SearchAgenciesResponse + 14, // 146: hyapp.user.v1.UserHostService.ApplyToAgency:output_type -> hyapp.user.v1.ApplyToAgencyResponse + 16, // 147: hyapp.user.v1.UserHostService.ReviewAgencyApplication:output_type -> hyapp.user.v1.ReviewAgencyApplicationResponse + 18, // 148: hyapp.user.v1.UserHostService.KickAgencyHost:output_type -> hyapp.user.v1.KickAgencyHostResponse + 20, // 149: hyapp.user.v1.UserHostService.InviteAgency:output_type -> hyapp.user.v1.InviteAgencyResponse + 22, // 150: hyapp.user.v1.UserHostService.InviteBD:output_type -> hyapp.user.v1.InviteBDResponse + 24, // 151: hyapp.user.v1.UserHostService.InviteHost:output_type -> hyapp.user.v1.InviteHostResponse + 26, // 152: hyapp.user.v1.UserHostService.ListBDLeaderBDs:output_type -> hyapp.user.v1.ListBDLeaderBDsResponse + 28, // 153: hyapp.user.v1.UserHostService.ListBDLeaderAgencies:output_type -> hyapp.user.v1.ListBDLeaderAgenciesResponse + 33, // 154: hyapp.user.v1.UserHostService.ListBDAgencies:output_type -> hyapp.user.v1.ListBDAgenciesResponse + 31, // 155: hyapp.user.v1.UserHostService.ListManagerTeamAgencies:output_type -> hyapp.user.v1.ListManagerTeamAgenciesResponse + 35, // 156: hyapp.user.v1.UserHostService.ListRoleInvitations:output_type -> hyapp.user.v1.ListRoleInvitationsResponse + 37, // 157: hyapp.user.v1.UserHostService.GetRoleInvitation:output_type -> hyapp.user.v1.GetRoleInvitationResponse + 39, // 158: hyapp.user.v1.UserHostService.ProcessRoleInvitation:output_type -> hyapp.user.v1.ProcessRoleInvitationResponse + 41, // 159: hyapp.user.v1.UserHostService.GetHostProfile:output_type -> hyapp.user.v1.GetHostProfileResponse + 43, // 160: hyapp.user.v1.UserHostService.BatchGetHostProfiles:output_type -> hyapp.user.v1.BatchGetHostProfilesResponse + 45, // 161: hyapp.user.v1.UserHostService.GetBDProfile:output_type -> hyapp.user.v1.GetBDProfileResponse + 47, // 162: hyapp.user.v1.UserHostService.GetCoinSellerProfile:output_type -> hyapp.user.v1.GetCoinSellerProfileResponse + 49, // 163: hyapp.user.v1.UserHostService.ListActiveCoinSellersInMyRegion:output_type -> hyapp.user.v1.ListActiveCoinSellersInMyRegionResponse + 51, // 164: hyapp.user.v1.UserHostService.CreateSubCoinSeller:output_type -> hyapp.user.v1.CreateSubCoinSellerResponse + 53, // 165: hyapp.user.v1.UserHostService.SubmitSubCoinSellerApplication:output_type -> hyapp.user.v1.SubmitSubCoinSellerApplicationResponse + 55, // 166: hyapp.user.v1.UserHostService.ListSubCoinSellers:output_type -> hyapp.user.v1.ListSubCoinSellersResponse + 57, // 167: hyapp.user.v1.UserHostService.CheckCoinSellerSubRelation:output_type -> hyapp.user.v1.CheckCoinSellerSubRelationResponse + 59, // 168: hyapp.user.v1.UserHostService.GetUserRoleSummary:output_type -> hyapp.user.v1.GetUserRoleSummaryResponse + 61, // 169: hyapp.user.v1.UserHostService.GetAgency:output_type -> hyapp.user.v1.GetAgencyResponse + 63, // 170: hyapp.user.v1.UserHostService.CheckBusinessCapability:output_type -> hyapp.user.v1.CheckBusinessCapabilityResponse + 66, // 171: hyapp.user.v1.UserHostService.GetRoleScopePolicy:output_type -> hyapp.user.v1.GetRoleScopePolicyResponse + 68, // 172: hyapp.user.v1.UserHostService.GetAgencyMembers:output_type -> hyapp.user.v1.GetAgencyMembersResponse + 70, // 173: hyapp.user.v1.UserHostService.GetAgencyApplications:output_type -> hyapp.user.v1.GetAgencyApplicationsResponse + 96, // 174: hyapp.user.v1.UserHostService.UpdateAgencyProfile:output_type -> hyapp.user.v1.UpdateAgencyProfileResponse + 99, // 175: hyapp.user.v1.UserHostService.GetHostEngagementStats:output_type -> hyapp.user.v1.GetHostEngagementStatsResponse + 101, // 176: hyapp.user.v1.UserHostService.RecordPrivateMessageEvent:output_type -> hyapp.user.v1.RecordPrivateMessageEventResponse + 72, // 177: hyapp.user.v1.UserHostAdminService.CreateBDLeader:output_type -> hyapp.user.v1.CreateBDLeaderResponse + 74, // 178: hyapp.user.v1.UserHostAdminService.CreateBD:output_type -> hyapp.user.v1.CreateBDResponse + 76, // 179: hyapp.user.v1.UserHostAdminService.SetBDStatus:output_type -> hyapp.user.v1.SetBDStatusResponse + 78, // 180: hyapp.user.v1.UserHostAdminService.CreateCoinSeller:output_type -> hyapp.user.v1.CreateCoinSellerResponse + 80, // 181: hyapp.user.v1.UserHostAdminService.SetCoinSellerStatus:output_type -> hyapp.user.v1.SetCoinSellerStatusResponse + 82, // 182: hyapp.user.v1.UserHostAdminService.ReviewSubCoinSellerApplication:output_type -> hyapp.user.v1.ReviewSubCoinSellerApplicationResponse + 84, // 183: hyapp.user.v1.UserHostAdminService.CreateAgency:output_type -> hyapp.user.v1.CreateAgencyResponse + 86, // 184: hyapp.user.v1.UserHostAdminService.AdminAddAgencyHost:output_type -> hyapp.user.v1.AdminAddAgencyHostResponse + 88, // 185: hyapp.user.v1.UserHostAdminService.CloseAgency:output_type -> hyapp.user.v1.CloseAgencyResponse + 90, // 186: hyapp.user.v1.UserHostAdminService.DeleteAgency:output_type -> hyapp.user.v1.DeleteAgencyResponse + 92, // 187: hyapp.user.v1.UserHostAdminService.SetAgencyStatus:output_type -> hyapp.user.v1.SetAgencyStatusResponse + 94, // 188: hyapp.user.v1.UserHostAdminService.SetAgencyJoinEnabled:output_type -> hyapp.user.v1.SetAgencyJoinEnabledResponse + 145, // [145:189] is the sub-list for method output_type + 101, // [101:145] is the sub-list for method input_type + 101, // [101:101] is the sub-list for extension type_name + 101, // [101:101] is the sub-list for extension extendee + 0, // [0:101] is the sub-list for field type_name } func init() { file_proto_user_v1_host_proto_init() } @@ -8153,16 +8762,16 @@ func file_proto_user_v1_host_proto_init() { return } file_proto_user_v1_user_proto_init() - file_proto_user_v1_host_proto_msgTypes[74].OneofWrappers = []any{} - file_proto_user_v1_host_proto_msgTypes[76].OneofWrappers = []any{} - file_proto_user_v1_host_proto_msgTypes[92].OneofWrappers = []any{} + file_proto_user_v1_host_proto_msgTypes[77].OneofWrappers = []any{} + file_proto_user_v1_host_proto_msgTypes[79].OneofWrappers = []any{} + file_proto_user_v1_host_proto_msgTypes[95].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_user_v1_host_proto_rawDesc, NumEnums: 0, - NumMessages: 95, + NumMessages: 103, NumExtensions: 0, NumServices: 2, }, diff --git a/api/proto/user/v1/host.proto b/api/proto/user/v1/host.proto index e15fd8a4..aa3d6060 100644 --- a/api/proto/user/v1/host.proto +++ b/api/proto/user/v1/host.proto @@ -124,6 +124,8 @@ message UserRoleSummary { string coin_seller_status = 11; int64 pending_role_invitations = 12; bool is_manager = 13; + // roles 给后台等需要多身份并列展示的调用方提供稳定字符串集合;原布尔字段继续兼容 App 入口显隐。 + repeated string roles = 14; } // AgencyMembership 是 host 与 Agency 的有时效关系事实。 @@ -480,6 +482,24 @@ message CheckBusinessCapabilityResponse { string reason = 2; } +// RoleScopePolicy 是 App 级角色拓展边界;调用方只消费 scope,不直接判断具体 app_code。 +message RoleScopePolicy { + string scene = 1; + string base_scope = 2; + bool region_expansion_configurable = 3; + // expanded_scope 是经理通用范围开关开启后的范围;调用方不能自行假设一定是 region。 + string expanded_scope = 4; +} + +message GetRoleScopePolicyRequest { + RequestMeta meta = 1; + string scene = 2; +} + +message GetRoleScopePolicyResponse { + RoleScopePolicy policy = 1; +} + message GetAgencyMembersRequest { RequestMeta meta = 1; int64 agency_id = 2; @@ -678,6 +698,39 @@ message UpdateAgencyProfileResponse { Agency agency = 1; } +// HostEngagementStats 是 Fami 等公会中心使用的用户侧统计聚合;金额/礼物流水由 wallet-service 单独拥有。 +message HostEngagementStats { + int64 online_duration_ms = 1; + int64 valid_mic_duration_ms = 2; + int64 valid_mic_days = 3; + int64 private_message_senders = 4; + int64 new_followers = 5; +} + +message GetHostEngagementStatsRequest { + RequestMeta meta = 1; + int64 host_user_id = 2; + int64 start_at_ms = 3; + int64 end_at_ms = 4; +} + +message GetHostEngagementStatsResponse { + HostEngagementStats stats = 1; +} + +// RecordPrivateMessageEvent 由经过腾讯回调鉴权的 gateway 写入私信事实;重复 event_id 必须幂等。 +message RecordPrivateMessageEventRequest { + RequestMeta meta = 1; + string event_id = 2; + int64 sender_user_id = 3; + int64 target_user_id = 4; + int64 occurred_at_ms = 5; +} + +message RecordPrivateMessageEventResponse { + bool created = 1; +} + // UserHostService 是 user-service 内部 host domain 的 gRPC 面,不是独立微服务。 service UserHostService { rpc SearchAgencies(SearchAgenciesRequest) returns (SearchAgenciesResponse); @@ -707,9 +760,12 @@ service UserHostService { rpc GetUserRoleSummary(GetUserRoleSummaryRequest) returns (GetUserRoleSummaryResponse); rpc GetAgency(GetAgencyRequest) returns (GetAgencyResponse); rpc CheckBusinessCapability(CheckBusinessCapabilityRequest) returns (CheckBusinessCapabilityResponse); + rpc GetRoleScopePolicy(GetRoleScopePolicyRequest) returns (GetRoleScopePolicyResponse); rpc GetAgencyMembers(GetAgencyMembersRequest) returns (GetAgencyMembersResponse); rpc GetAgencyApplications(GetAgencyApplicationsRequest) returns (GetAgencyApplicationsResponse); rpc UpdateAgencyProfile(UpdateAgencyProfileRequest) returns (UpdateAgencyProfileResponse); + rpc GetHostEngagementStats(GetHostEngagementStatsRequest) returns (GetHostEngagementStatsResponse); + rpc RecordPrivateMessageEvent(RecordPrivateMessageEventRequest) returns (RecordPrivateMessageEventResponse); } // UserHostAdminService 是后台关系管理入口;公网 admin 鉴权由 admin-server 承担。 diff --git a/api/proto/user/v1/host_grpc.pb.go b/api/proto/user/v1/host_grpc.pb.go index 86d51e95..0eabb231 100644 --- a/api/proto/user/v1/host_grpc.pb.go +++ b/api/proto/user/v1/host_grpc.pb.go @@ -45,9 +45,12 @@ const ( UserHostService_GetUserRoleSummary_FullMethodName = "/hyapp.user.v1.UserHostService/GetUserRoleSummary" UserHostService_GetAgency_FullMethodName = "/hyapp.user.v1.UserHostService/GetAgency" UserHostService_CheckBusinessCapability_FullMethodName = "/hyapp.user.v1.UserHostService/CheckBusinessCapability" + UserHostService_GetRoleScopePolicy_FullMethodName = "/hyapp.user.v1.UserHostService/GetRoleScopePolicy" UserHostService_GetAgencyMembers_FullMethodName = "/hyapp.user.v1.UserHostService/GetAgencyMembers" UserHostService_GetAgencyApplications_FullMethodName = "/hyapp.user.v1.UserHostService/GetAgencyApplications" UserHostService_UpdateAgencyProfile_FullMethodName = "/hyapp.user.v1.UserHostService/UpdateAgencyProfile" + UserHostService_GetHostEngagementStats_FullMethodName = "/hyapp.user.v1.UserHostService/GetHostEngagementStats" + UserHostService_RecordPrivateMessageEvent_FullMethodName = "/hyapp.user.v1.UserHostService/RecordPrivateMessageEvent" ) // UserHostServiceClient is the client API for UserHostService service. @@ -83,9 +86,12 @@ type UserHostServiceClient interface { GetUserRoleSummary(ctx context.Context, in *GetUserRoleSummaryRequest, opts ...grpc.CallOption) (*GetUserRoleSummaryResponse, error) GetAgency(ctx context.Context, in *GetAgencyRequest, opts ...grpc.CallOption) (*GetAgencyResponse, error) CheckBusinessCapability(ctx context.Context, in *CheckBusinessCapabilityRequest, opts ...grpc.CallOption) (*CheckBusinessCapabilityResponse, error) + GetRoleScopePolicy(ctx context.Context, in *GetRoleScopePolicyRequest, opts ...grpc.CallOption) (*GetRoleScopePolicyResponse, error) GetAgencyMembers(ctx context.Context, in *GetAgencyMembersRequest, opts ...grpc.CallOption) (*GetAgencyMembersResponse, error) GetAgencyApplications(ctx context.Context, in *GetAgencyApplicationsRequest, opts ...grpc.CallOption) (*GetAgencyApplicationsResponse, error) UpdateAgencyProfile(ctx context.Context, in *UpdateAgencyProfileRequest, opts ...grpc.CallOption) (*UpdateAgencyProfileResponse, error) + GetHostEngagementStats(ctx context.Context, in *GetHostEngagementStatsRequest, opts ...grpc.CallOption) (*GetHostEngagementStatsResponse, error) + RecordPrivateMessageEvent(ctx context.Context, in *RecordPrivateMessageEventRequest, opts ...grpc.CallOption) (*RecordPrivateMessageEventResponse, error) } type userHostServiceClient struct { @@ -356,6 +362,16 @@ func (c *userHostServiceClient) CheckBusinessCapability(ctx context.Context, in return out, nil } +func (c *userHostServiceClient) GetRoleScopePolicy(ctx context.Context, in *GetRoleScopePolicyRequest, opts ...grpc.CallOption) (*GetRoleScopePolicyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetRoleScopePolicyResponse) + err := c.cc.Invoke(ctx, UserHostService_GetRoleScopePolicy_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *userHostServiceClient) GetAgencyMembers(ctx context.Context, in *GetAgencyMembersRequest, opts ...grpc.CallOption) (*GetAgencyMembersResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetAgencyMembersResponse) @@ -386,6 +402,26 @@ func (c *userHostServiceClient) UpdateAgencyProfile(ctx context.Context, in *Upd return out, nil } +func (c *userHostServiceClient) GetHostEngagementStats(ctx context.Context, in *GetHostEngagementStatsRequest, opts ...grpc.CallOption) (*GetHostEngagementStatsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetHostEngagementStatsResponse) + err := c.cc.Invoke(ctx, UserHostService_GetHostEngagementStats_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *userHostServiceClient) RecordPrivateMessageEvent(ctx context.Context, in *RecordPrivateMessageEventRequest, opts ...grpc.CallOption) (*RecordPrivateMessageEventResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RecordPrivateMessageEventResponse) + err := c.cc.Invoke(ctx, UserHostService_RecordPrivateMessageEvent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // UserHostServiceServer is the server API for UserHostService service. // All implementations must embed UnimplementedUserHostServiceServer // for forward compatibility. @@ -419,9 +455,12 @@ type UserHostServiceServer interface { GetUserRoleSummary(context.Context, *GetUserRoleSummaryRequest) (*GetUserRoleSummaryResponse, error) GetAgency(context.Context, *GetAgencyRequest) (*GetAgencyResponse, error) CheckBusinessCapability(context.Context, *CheckBusinessCapabilityRequest) (*CheckBusinessCapabilityResponse, error) + GetRoleScopePolicy(context.Context, *GetRoleScopePolicyRequest) (*GetRoleScopePolicyResponse, error) GetAgencyMembers(context.Context, *GetAgencyMembersRequest) (*GetAgencyMembersResponse, error) GetAgencyApplications(context.Context, *GetAgencyApplicationsRequest) (*GetAgencyApplicationsResponse, error) UpdateAgencyProfile(context.Context, *UpdateAgencyProfileRequest) (*UpdateAgencyProfileResponse, error) + GetHostEngagementStats(context.Context, *GetHostEngagementStatsRequest) (*GetHostEngagementStatsResponse, error) + RecordPrivateMessageEvent(context.Context, *RecordPrivateMessageEventRequest) (*RecordPrivateMessageEventResponse, error) mustEmbedUnimplementedUserHostServiceServer() } @@ -510,6 +549,9 @@ func (UnimplementedUserHostServiceServer) GetAgency(context.Context, *GetAgencyR func (UnimplementedUserHostServiceServer) CheckBusinessCapability(context.Context, *CheckBusinessCapabilityRequest) (*CheckBusinessCapabilityResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CheckBusinessCapability not implemented") } +func (UnimplementedUserHostServiceServer) GetRoleScopePolicy(context.Context, *GetRoleScopePolicyRequest) (*GetRoleScopePolicyResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetRoleScopePolicy not implemented") +} func (UnimplementedUserHostServiceServer) GetAgencyMembers(context.Context, *GetAgencyMembersRequest) (*GetAgencyMembersResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetAgencyMembers not implemented") } @@ -519,6 +561,12 @@ func (UnimplementedUserHostServiceServer) GetAgencyApplications(context.Context, func (UnimplementedUserHostServiceServer) UpdateAgencyProfile(context.Context, *UpdateAgencyProfileRequest) (*UpdateAgencyProfileResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateAgencyProfile not implemented") } +func (UnimplementedUserHostServiceServer) GetHostEngagementStats(context.Context, *GetHostEngagementStatsRequest) (*GetHostEngagementStatsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetHostEngagementStats not implemented") +} +func (UnimplementedUserHostServiceServer) RecordPrivateMessageEvent(context.Context, *RecordPrivateMessageEventRequest) (*RecordPrivateMessageEventResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RecordPrivateMessageEvent not implemented") +} func (UnimplementedUserHostServiceServer) mustEmbedUnimplementedUserHostServiceServer() {} func (UnimplementedUserHostServiceServer) testEmbeddedByValue() {} @@ -1008,6 +1056,24 @@ func _UserHostService_CheckBusinessCapability_Handler(srv interface{}, ctx conte return interceptor(ctx, in, info, handler) } +func _UserHostService_GetRoleScopePolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRoleScopePolicyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserHostServiceServer).GetRoleScopePolicy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserHostService_GetRoleScopePolicy_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserHostServiceServer).GetRoleScopePolicy(ctx, req.(*GetRoleScopePolicyRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _UserHostService_GetAgencyMembers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetAgencyMembersRequest) if err := dec(in); err != nil { @@ -1062,6 +1128,42 @@ func _UserHostService_UpdateAgencyProfile_Handler(srv interface{}, ctx context.C return interceptor(ctx, in, info, handler) } +func _UserHostService_GetHostEngagementStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetHostEngagementStatsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserHostServiceServer).GetHostEngagementStats(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserHostService_GetHostEngagementStats_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserHostServiceServer).GetHostEngagementStats(ctx, req.(*GetHostEngagementStatsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _UserHostService_RecordPrivateMessageEvent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RecordPrivateMessageEventRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserHostServiceServer).RecordPrivateMessageEvent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserHostService_RecordPrivateMessageEvent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserHostServiceServer).RecordPrivateMessageEvent(ctx, req.(*RecordPrivateMessageEventRequest)) + } + return interceptor(ctx, in, info, handler) +} + // UserHostService_ServiceDesc is the grpc.ServiceDesc for UserHostService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -1173,6 +1275,10 @@ var UserHostService_ServiceDesc = grpc.ServiceDesc{ MethodName: "CheckBusinessCapability", Handler: _UserHostService_CheckBusinessCapability_Handler, }, + { + MethodName: "GetRoleScopePolicy", + Handler: _UserHostService_GetRoleScopePolicy_Handler, + }, { MethodName: "GetAgencyMembers", Handler: _UserHostService_GetAgencyMembers_Handler, @@ -1185,6 +1291,14 @@ var UserHostService_ServiceDesc = grpc.ServiceDesc{ MethodName: "UpdateAgencyProfile", Handler: _UserHostService_UpdateAgencyProfile_Handler, }, + { + MethodName: "GetHostEngagementStats", + Handler: _UserHostService_GetHostEngagementStats_Handler, + }, + { + MethodName: "RecordPrivateMessageEvent", + Handler: _UserHostService_RecordPrivateMessageEvent_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/user/v1/host.proto", diff --git a/api/proto/user/v1/user.pb.go b/api/proto/user/v1/user.pb.go index ead5a038..8ca4ec2e 100644 --- a/api/proto/user/v1/user.pb.go +++ b/api/proto/user/v1/user.pb.go @@ -92,6 +92,16 @@ type RequestMeta struct { Platform string `protobuf:"bytes,11,opt,name=platform,proto3" json:"platform,omitempty"` Language string `protobuf:"bytes,12,opt,name=language,proto3" json:"language,omitempty"` Timezone string `protobuf:"bytes,13,opt,name=timezone,proto3" json:"timezone,omitempty"` + // app_version/build_number 记录发起本次 RPC 的客户端构建;认证链路依赖它们把每次成功登录归档到 login_audit。 + AppVersion string `protobuf:"bytes,14,opt,name=app_version,json=appVersion,proto3" json:"app_version,omitempty"` + BuildNumber string `protobuf:"bytes,15,opt,name=build_number,json=buildNumber,proto3" json:"build_number,omitempty"` + // device_model/device_manufacturer/os_version/imei 是客户端设备资料快照,来自 gateway 的 + // X-Device-Model/X-Device-Manufacturer/X-OS-Version/X-Device-IMEI 头;认证成功后按 device_id + // 聚合进 user_devices,支撑后台用户详情的设备 tab。非认证链路可留空,IMEI 在 Android 10+ 通常为空。 + DeviceModel string `protobuf:"bytes,16,opt,name=device_model,json=deviceModel,proto3" json:"device_model,omitempty"` + DeviceManufacturer string `protobuf:"bytes,17,opt,name=device_manufacturer,json=deviceManufacturer,proto3" json:"device_manufacturer,omitempty"` + OsVersion string `protobuf:"bytes,18,opt,name=os_version,json=osVersion,proto3" json:"os_version,omitempty"` + Imei string `protobuf:"bytes,19,opt,name=imei,proto3" json:"imei,omitempty"` } func (x *RequestMeta) Reset() { @@ -215,6 +225,48 @@ func (x *RequestMeta) GetTimezone() string { return "" } +func (x *RequestMeta) GetAppVersion() string { + if x != nil { + return x.AppVersion + } + return "" +} + +func (x *RequestMeta) GetBuildNumber() string { + if x != nil { + return x.BuildNumber + } + return "" +} + +func (x *RequestMeta) GetDeviceModel() string { + if x != nil { + return x.DeviceModel + } + return "" +} + +func (x *RequestMeta) GetDeviceManufacturer() string { + if x != nil { + return x.DeviceManufacturer + } + return "" +} + +func (x *RequestMeta) GetOsVersion() string { + if x != nil { + return x.OsVersion + } + return "" +} + +func (x *RequestMeta) GetImei() string { + if x != nil { + return x.Imei + } + return "" +} + // App 是 user-service 维护的 App 注册表投影,用于把客户端包名映射到内部租户键。 type App struct { state protoimpl.MessageState @@ -1304,6 +1356,7 @@ type CronBatchRequest struct { LockTtlMs int64 `protobuf:"varint,5,opt,name=lock_ttl_ms,json=lockTtlMs,proto3" json:"lock_ttl_ms,omitempty"` PendingPublishMaxAgeMs int64 `protobuf:"varint,6,opt,name=pending_publish_max_age_ms,json=pendingPublishMaxAgeMs,proto3" json:"pending_publish_max_age_ms,omitempty"` PublishingSessionMaxAgeMs int64 `protobuf:"varint,7,opt,name=publishing_session_max_age_ms,json=publishingSessionMaxAgeMs,proto3" json:"publishing_session_max_age_ms,omitempty"` + OpenSessionMaxAgeMs int64 `protobuf:"varint,8,opt,name=open_session_max_age_ms,json=openSessionMaxAgeMs,proto3" json:"open_session_max_age_ms,omitempty"` } func (x *CronBatchRequest) Reset() { @@ -1385,6 +1438,13 @@ func (x *CronBatchRequest) GetPublishingSessionMaxAgeMs() int64 { return 0 } +func (x *CronBatchRequest) GetOpenSessionMaxAgeMs() int64 { + if x != nil { + return x.OpenSessionMaxAgeMs + } + return 0 +} + // CronBatchResponse 返回一个批处理 run 的执行摘要。 type CronBatchResponse struct { state protoimpl.MessageState @@ -6322,6 +6382,501 @@ func (x *BatchGetUsersResponse) GetUsers() map[int64]*User { return nil } +// ActiveUserBanSummary 是后台用户列表读取的有效封禁摘要。 +// expires_at_ms=0 只在 permanent=true 时表示永久;source 区分 admin、manager 和无新事实表记录的 legacy。 +type ActiveUserBanSummary struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Active bool `protobuf:"varint,1,opt,name=active,proto3" json:"active,omitempty"` + Source string `protobuf:"bytes,2,opt,name=source,proto3" json:"source,omitempty"` + BanId string `protobuf:"bytes,3,opt,name=ban_id,json=banId,proto3" json:"ban_id,omitempty"` + Permanent bool `protobuf:"varint,4,opt,name=permanent,proto3" json:"permanent,omitempty"` + ExpiresAtMs int64 `protobuf:"varint,5,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` + UserStatus UserStatus `protobuf:"varint,6,opt,name=user_status,json=userStatus,proto3,enum=hyapp.user.v1.UserStatus" json:"user_status,omitempty"` + OperatorType string `protobuf:"bytes,7,opt,name=operator_type,json=operatorType,proto3" json:"operator_type,omitempty"` + OperatorUserId int64 `protobuf:"varint,8,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + Reason string `protobuf:"bytes,9,opt,name=reason,proto3" json:"reason,omitempty"` + CreatedAtMs int64 `protobuf:"varint,10,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + ActiveBanCount int32 `protobuf:"varint,11,opt,name=active_ban_count,json=activeBanCount,proto3" json:"active_ban_count,omitempty"` +} + +func (x *ActiveUserBanSummary) Reset() { + *x = ActiveUserBanSummary{} + mi := &file_proto_user_v1_user_proto_msgTypes[82] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ActiveUserBanSummary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ActiveUserBanSummary) ProtoMessage() {} + +func (x *ActiveUserBanSummary) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[82] + 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 ActiveUserBanSummary.ProtoReflect.Descriptor instead. +func (*ActiveUserBanSummary) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{82} +} + +func (x *ActiveUserBanSummary) GetActive() bool { + if x != nil { + return x.Active + } + return false +} + +func (x *ActiveUserBanSummary) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *ActiveUserBanSummary) GetBanId() string { + if x != nil { + return x.BanId + } + return "" +} + +func (x *ActiveUserBanSummary) GetPermanent() bool { + if x != nil { + return x.Permanent + } + return false +} + +func (x *ActiveUserBanSummary) GetExpiresAtMs() int64 { + if x != nil { + return x.ExpiresAtMs + } + return 0 +} + +func (x *ActiveUserBanSummary) GetUserStatus() UserStatus { + if x != nil { + return x.UserStatus + } + return UserStatus_USER_STATUS_UNSPECIFIED +} + +func (x *ActiveUserBanSummary) GetOperatorType() string { + if x != nil { + return x.OperatorType + } + return "" +} + +func (x *ActiveUserBanSummary) GetOperatorUserId() int64 { + if x != nil { + return x.OperatorUserId + } + return 0 +} + +func (x *ActiveUserBanSummary) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *ActiveUserBanSummary) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *ActiveUserBanSummary) GetActiveBanCount() int32 { + if x != nil { + return x.ActiveBanCount + } + return 0 +} + +// UserAdminProfile 聚合后台用户列表和详情需要的 user-service owner 事实。 +// roles 只返回当前有效身份:host、agency、bd、bd_leader、coin_seller、manager。 +type UserAdminProfile struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` + Birth string `protobuf:"bytes,2,opt,name=birth,proto3" json:"birth,omitempty"` + RegisterDevice string `protobuf:"bytes,3,opt,name=register_device,json=registerDevice,proto3" json:"register_device,omitempty"` + LastSuccessLoginAtMs int64 `protobuf:"varint,4,opt,name=last_success_login_at_ms,json=lastSuccessLoginAtMs,proto3" json:"last_success_login_at_ms,omitempty"` + Roles []string `protobuf:"bytes,5,rep,name=roles,proto3" json:"roles,omitempty"` + Ban *ActiveUserBanSummary `protobuf:"bytes,6,opt,name=ban,proto3" json:"ban,omitempty"` + LastOperatorType string `protobuf:"bytes,7,opt,name=last_operator_type,json=lastOperatorType,proto3" json:"last_operator_type,omitempty"` + LastOperatorUserId int64 `protobuf:"varint,8,opt,name=last_operator_user_id,json=lastOperatorUserId,proto3" json:"last_operator_user_id,omitempty"` + LastOperationReason string `protobuf:"bytes,9,opt,name=last_operation_reason,json=lastOperationReason,proto3" json:"last_operation_reason,omitempty"` + LastOperationAtMs int64 `protobuf:"varint,10,opt,name=last_operation_at_ms,json=lastOperationAtMs,proto3" json:"last_operation_at_ms,omitempty"` +} + +func (x *UserAdminProfile) Reset() { + *x = UserAdminProfile{} + mi := &file_proto_user_v1_user_proto_msgTypes[83] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UserAdminProfile) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UserAdminProfile) ProtoMessage() {} + +func (x *UserAdminProfile) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[83] + 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 UserAdminProfile.ProtoReflect.Descriptor instead. +func (*UserAdminProfile) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{83} +} + +func (x *UserAdminProfile) GetUser() *User { + if x != nil { + return x.User + } + return nil +} + +func (x *UserAdminProfile) GetBirth() string { + if x != nil { + return x.Birth + } + return "" +} + +func (x *UserAdminProfile) GetRegisterDevice() string { + if x != nil { + return x.RegisterDevice + } + return "" +} + +func (x *UserAdminProfile) GetLastSuccessLoginAtMs() int64 { + if x != nil { + return x.LastSuccessLoginAtMs + } + return 0 +} + +func (x *UserAdminProfile) GetRoles() []string { + if x != nil { + return x.Roles + } + return nil +} + +func (x *UserAdminProfile) GetBan() *ActiveUserBanSummary { + if x != nil { + return x.Ban + } + return nil +} + +func (x *UserAdminProfile) GetLastOperatorType() string { + if x != nil { + return x.LastOperatorType + } + return "" +} + +func (x *UserAdminProfile) GetLastOperatorUserId() int64 { + if x != nil { + return x.LastOperatorUserId + } + return 0 +} + +func (x *UserAdminProfile) GetLastOperationReason() string { + if x != nil { + return x.LastOperationReason + } + return "" +} + +func (x *UserAdminProfile) GetLastOperationAtMs() int64 { + if x != nil { + return x.LastOperationAtMs + } + return 0 +} + +type BatchGetUserAdminProfilesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + UserIds []int64 `protobuf:"varint,2,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"` +} + +func (x *BatchGetUserAdminProfilesRequest) Reset() { + *x = BatchGetUserAdminProfilesRequest{} + mi := &file_proto_user_v1_user_proto_msgTypes[84] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchGetUserAdminProfilesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchGetUserAdminProfilesRequest) ProtoMessage() {} + +func (x *BatchGetUserAdminProfilesRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[84] + 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 BatchGetUserAdminProfilesRequest.ProtoReflect.Descriptor instead. +func (*BatchGetUserAdminProfilesRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{84} +} + +func (x *BatchGetUserAdminProfilesRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *BatchGetUserAdminProfilesRequest) GetUserIds() []int64 { + if x != nil { + return x.UserIds + } + return nil +} + +type BatchGetUserAdminProfilesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Profiles map[int64]*UserAdminProfile `protobuf:"bytes,1,rep,name=profiles,proto3" json:"profiles,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *BatchGetUserAdminProfilesResponse) Reset() { + *x = BatchGetUserAdminProfilesResponse{} + mi := &file_proto_user_v1_user_proto_msgTypes[85] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchGetUserAdminProfilesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchGetUserAdminProfilesResponse) ProtoMessage() {} + +func (x *BatchGetUserAdminProfilesResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[85] + 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 BatchGetUserAdminProfilesResponse.ProtoReflect.Descriptor instead. +func (*BatchGetUserAdminProfilesResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{85} +} + +func (x *BatchGetUserAdminProfilesResponse) GetProfiles() map[int64]*UserAdminProfile { + if x != nil { + return x.Profiles + } + return nil +} + +// AdminIssueUserAccessTokenRequest 供后台按用户当前最新 active session 重签 access token。 +// 该链路不写 login_audit,也不修改 session 行——后台取 token 不能污染用户“最近活跃/最近登录”事实; +// 审计责任由 admin 侧操作日志承担。 +type AdminIssueUserAccessTokenRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` +} + +func (x *AdminIssueUserAccessTokenRequest) Reset() { + *x = AdminIssueUserAccessTokenRequest{} + mi := &file_proto_user_v1_user_proto_msgTypes[86] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdminIssueUserAccessTokenRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdminIssueUserAccessTokenRequest) ProtoMessage() {} + +func (x *AdminIssueUserAccessTokenRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[86] + 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 AdminIssueUserAccessTokenRequest.ProtoReflect.Descriptor instead. +func (*AdminIssueUserAccessTokenRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{86} +} + +func (x *AdminIssueUserAccessTokenRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *AdminIssueUserAccessTokenRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *AdminIssueUserAccessTokenRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +// AdminIssueUserAccessTokenResponse 返回重签的 access token 和所属 session 的定位信息。 +// 不返回 refresh token:后台没有轮换会话的理由,refresh 原文也不允许离开登录主链路。 +type AdminIssueUserAccessTokenResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` + TokenType string `protobuf:"bytes,2,opt,name=token_type,json=tokenType,proto3" json:"token_type,omitempty"` + ExpiresInSec int64 `protobuf:"varint,3,opt,name=expires_in_sec,json=expiresInSec,proto3" json:"expires_in_sec,omitempty"` + SessionId string `protobuf:"bytes,4,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + DeviceId string `protobuf:"bytes,5,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"` + SessionLastHeartbeatAtMs int64 `protobuf:"varint,6,opt,name=session_last_heartbeat_at_ms,json=sessionLastHeartbeatAtMs,proto3" json:"session_last_heartbeat_at_ms,omitempty"` +} + +func (x *AdminIssueUserAccessTokenResponse) Reset() { + *x = AdminIssueUserAccessTokenResponse{} + mi := &file_proto_user_v1_user_proto_msgTypes[87] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdminIssueUserAccessTokenResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdminIssueUserAccessTokenResponse) ProtoMessage() {} + +func (x *AdminIssueUserAccessTokenResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[87] + 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 AdminIssueUserAccessTokenResponse.ProtoReflect.Descriptor instead. +func (*AdminIssueUserAccessTokenResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{87} +} + +func (x *AdminIssueUserAccessTokenResponse) GetAccessToken() string { + if x != nil { + return x.AccessToken + } + return "" +} + +func (x *AdminIssueUserAccessTokenResponse) GetTokenType() string { + if x != nil { + return x.TokenType + } + return "" +} + +func (x *AdminIssueUserAccessTokenResponse) GetExpiresInSec() int64 { + if x != nil { + return x.ExpiresInSec + } + return 0 +} + +func (x *AdminIssueUserAccessTokenResponse) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +func (x *AdminIssueUserAccessTokenResponse) GetDeviceId() string { + if x != nil { + return x.DeviceId + } + return "" +} + +func (x *AdminIssueUserAccessTokenResponse) GetSessionLastHeartbeatAtMs() int64 { + if x != nil { + return x.SessionLastHeartbeatAtMs + } + return 0 +} + // RoomBasicUser 是房间首屏只需要的用户展示基础资料。 type RoomBasicUser struct { state protoimpl.MessageState @@ -6336,7 +6891,7 @@ type RoomBasicUser struct { func (x *RoomBasicUser) Reset() { *x = RoomBasicUser{} - mi := &file_proto_user_v1_user_proto_msgTypes[82] + mi := &file_proto_user_v1_user_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6348,7 +6903,7 @@ func (x *RoomBasicUser) String() string { func (*RoomBasicUser) ProtoMessage() {} func (x *RoomBasicUser) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[82] + mi := &file_proto_user_v1_user_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6361,7 +6916,7 @@ func (x *RoomBasicUser) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomBasicUser.ProtoReflect.Descriptor instead. func (*RoomBasicUser) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{82} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{88} } func (x *RoomBasicUser) GetUserId() int64 { @@ -6404,7 +6959,7 @@ type BatchGetRoomBasicUsersRequest struct { func (x *BatchGetRoomBasicUsersRequest) Reset() { *x = BatchGetRoomBasicUsersRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[83] + mi := &file_proto_user_v1_user_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6416,7 +6971,7 @@ func (x *BatchGetRoomBasicUsersRequest) String() string { func (*BatchGetRoomBasicUsersRequest) ProtoMessage() {} func (x *BatchGetRoomBasicUsersRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[83] + mi := &file_proto_user_v1_user_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6429,7 +6984,7 @@ func (x *BatchGetRoomBasicUsersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchGetRoomBasicUsersRequest.ProtoReflect.Descriptor instead. func (*BatchGetRoomBasicUsersRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{83} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{89} } func (x *BatchGetRoomBasicUsersRequest) GetMeta() *RequestMeta { @@ -6457,7 +7012,7 @@ type BatchGetRoomBasicUsersResponse struct { func (x *BatchGetRoomBasicUsersResponse) Reset() { *x = BatchGetRoomBasicUsersResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[84] + mi := &file_proto_user_v1_user_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6469,7 +7024,7 @@ func (x *BatchGetRoomBasicUsersResponse) String() string { func (*BatchGetRoomBasicUsersResponse) ProtoMessage() {} func (x *BatchGetRoomBasicUsersResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[84] + mi := &file_proto_user_v1_user_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6482,7 +7037,7 @@ func (x *BatchGetRoomBasicUsersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchGetRoomBasicUsersResponse.ProtoReflect.Descriptor instead. func (*BatchGetRoomBasicUsersResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{84} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{90} } func (x *BatchGetRoomBasicUsersResponse) GetUsers() map[int64]*RoomBasicUser { @@ -6508,7 +7063,7 @@ type ListUserIDsRequest struct { func (x *ListUserIDsRequest) Reset() { *x = ListUserIDsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[85] + mi := &file_proto_user_v1_user_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6520,7 +7075,7 @@ func (x *ListUserIDsRequest) String() string { func (*ListUserIDsRequest) ProtoMessage() {} func (x *ListUserIDsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[85] + mi := &file_proto_user_v1_user_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6533,7 +7088,7 @@ func (x *ListUserIDsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUserIDsRequest.ProtoReflect.Descriptor instead. func (*ListUserIDsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{85} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{91} } func (x *ListUserIDsRequest) GetMeta() *RequestMeta { @@ -6591,7 +7146,7 @@ type ListUserIDsResponse struct { func (x *ListUserIDsResponse) Reset() { *x = ListUserIDsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[86] + mi := &file_proto_user_v1_user_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6603,7 +7158,7 @@ func (x *ListUserIDsResponse) String() string { func (*ListUserIDsResponse) ProtoMessage() {} func (x *ListUserIDsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[86] + mi := &file_proto_user_v1_user_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6616,7 +7171,7 @@ func (x *ListUserIDsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUserIDsResponse.ProtoReflect.Descriptor instead. func (*ListUserIDsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{86} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{92} } func (x *ListUserIDsResponse) GetUserIds() []int64 { @@ -6656,7 +7211,7 @@ type UpdateUserProfileRequest struct { func (x *UpdateUserProfileRequest) Reset() { *x = UpdateUserProfileRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[87] + mi := &file_proto_user_v1_user_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6668,7 +7223,7 @@ func (x *UpdateUserProfileRequest) String() string { func (*UpdateUserProfileRequest) ProtoMessage() {} func (x *UpdateUserProfileRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[87] + mi := &file_proto_user_v1_user_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6681,7 +7236,7 @@ func (x *UpdateUserProfileRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateUserProfileRequest.ProtoReflect.Descriptor instead. func (*UpdateUserProfileRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{87} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{93} } func (x *UpdateUserProfileRequest) GetMeta() *RequestMeta { @@ -6737,7 +7292,7 @@ type UpdateUserProfileResponse struct { func (x *UpdateUserProfileResponse) Reset() { *x = UpdateUserProfileResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[88] + mi := &file_proto_user_v1_user_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6749,7 +7304,7 @@ func (x *UpdateUserProfileResponse) String() string { func (*UpdateUserProfileResponse) ProtoMessage() {} func (x *UpdateUserProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[88] + mi := &file_proto_user_v1_user_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6762,7 +7317,7 @@ func (x *UpdateUserProfileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateUserProfileResponse.ProtoReflect.Descriptor instead. func (*UpdateUserProfileResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{88} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{94} } func (x *UpdateUserProfileResponse) GetUser() *User { @@ -6785,7 +7340,7 @@ type UpdateUserProfileBackgroundRequest struct { func (x *UpdateUserProfileBackgroundRequest) Reset() { *x = UpdateUserProfileBackgroundRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[89] + mi := &file_proto_user_v1_user_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6797,7 +7352,7 @@ func (x *UpdateUserProfileBackgroundRequest) String() string { func (*UpdateUserProfileBackgroundRequest) ProtoMessage() {} func (x *UpdateUserProfileBackgroundRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[89] + mi := &file_proto_user_v1_user_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6810,7 +7365,7 @@ func (x *UpdateUserProfileBackgroundRequest) ProtoReflect() protoreflect.Message // Deprecated: Use UpdateUserProfileBackgroundRequest.ProtoReflect.Descriptor instead. func (*UpdateUserProfileBackgroundRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{89} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{95} } func (x *UpdateUserProfileBackgroundRequest) GetMeta() *RequestMeta { @@ -6845,7 +7400,7 @@ type UpdateUserProfileBackgroundResponse struct { func (x *UpdateUserProfileBackgroundResponse) Reset() { *x = UpdateUserProfileBackgroundResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[90] + mi := &file_proto_user_v1_user_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6857,7 +7412,7 @@ func (x *UpdateUserProfileBackgroundResponse) String() string { func (*UpdateUserProfileBackgroundResponse) ProtoMessage() {} func (x *UpdateUserProfileBackgroundResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[90] + mi := &file_proto_user_v1_user_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6870,7 +7425,7 @@ func (x *UpdateUserProfileBackgroundResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use UpdateUserProfileBackgroundResponse.ProtoReflect.Descriptor instead. func (*UpdateUserProfileBackgroundResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{90} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{96} } func (x *UpdateUserProfileBackgroundResponse) GetUser() *User { @@ -6893,7 +7448,7 @@ type UpdateUserContactInfoRequest struct { func (x *UpdateUserContactInfoRequest) Reset() { *x = UpdateUserContactInfoRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[91] + mi := &file_proto_user_v1_user_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6905,7 +7460,7 @@ func (x *UpdateUserContactInfoRequest) String() string { func (*UpdateUserContactInfoRequest) ProtoMessage() {} func (x *UpdateUserContactInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[91] + mi := &file_proto_user_v1_user_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6918,7 +7473,7 @@ func (x *UpdateUserContactInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateUserContactInfoRequest.ProtoReflect.Descriptor instead. func (*UpdateUserContactInfoRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{91} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{97} } func (x *UpdateUserContactInfoRequest) GetMeta() *RequestMeta { @@ -6953,7 +7508,7 @@ type UpdateUserContactInfoResponse struct { func (x *UpdateUserContactInfoResponse) Reset() { *x = UpdateUserContactInfoResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[92] + mi := &file_proto_user_v1_user_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6965,7 +7520,7 @@ func (x *UpdateUserContactInfoResponse) String() string { func (*UpdateUserContactInfoResponse) ProtoMessage() {} func (x *UpdateUserContactInfoResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[92] + mi := &file_proto_user_v1_user_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6978,7 +7533,7 @@ func (x *UpdateUserContactInfoResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateUserContactInfoResponse.ProtoReflect.Descriptor instead. func (*UpdateUserContactInfoResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{92} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{98} } func (x *UpdateUserContactInfoResponse) GetUser() *User { @@ -7001,7 +7556,7 @@ type UpdateUserWithdrawAddressRequest struct { func (x *UpdateUserWithdrawAddressRequest) Reset() { *x = UpdateUserWithdrawAddressRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[93] + mi := &file_proto_user_v1_user_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7013,7 +7568,7 @@ func (x *UpdateUserWithdrawAddressRequest) String() string { func (*UpdateUserWithdrawAddressRequest) ProtoMessage() {} func (x *UpdateUserWithdrawAddressRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[93] + mi := &file_proto_user_v1_user_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7026,7 +7581,7 @@ func (x *UpdateUserWithdrawAddressRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateUserWithdrawAddressRequest.ProtoReflect.Descriptor instead. func (*UpdateUserWithdrawAddressRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{93} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{99} } func (x *UpdateUserWithdrawAddressRequest) GetMeta() *RequestMeta { @@ -7061,7 +7616,7 @@ type UpdateUserWithdrawAddressResponse struct { func (x *UpdateUserWithdrawAddressResponse) Reset() { *x = UpdateUserWithdrawAddressResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[94] + mi := &file_proto_user_v1_user_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7073,7 +7628,7 @@ func (x *UpdateUserWithdrawAddressResponse) String() string { func (*UpdateUserWithdrawAddressResponse) ProtoMessage() {} func (x *UpdateUserWithdrawAddressResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[94] + mi := &file_proto_user_v1_user_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7086,7 +7641,7 @@ func (x *UpdateUserWithdrawAddressResponse) ProtoReflect() protoreflect.Message // Deprecated: Use UpdateUserWithdrawAddressResponse.ProtoReflect.Descriptor instead. func (*UpdateUserWithdrawAddressResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{94} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{100} } func (x *UpdateUserWithdrawAddressResponse) GetUser() *User { @@ -7109,7 +7664,7 @@ type ChangeUserCountryRequest struct { func (x *ChangeUserCountryRequest) Reset() { *x = ChangeUserCountryRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[95] + mi := &file_proto_user_v1_user_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7121,7 +7676,7 @@ func (x *ChangeUserCountryRequest) String() string { func (*ChangeUserCountryRequest) ProtoMessage() {} func (x *ChangeUserCountryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[95] + mi := &file_proto_user_v1_user_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7134,7 +7689,7 @@ func (x *ChangeUserCountryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeUserCountryRequest.ProtoReflect.Descriptor instead. func (*ChangeUserCountryRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{95} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{101} } func (x *ChangeUserCountryRequest) GetMeta() *RequestMeta { @@ -7173,7 +7728,7 @@ type AdminChangeUserCountryRequest struct { func (x *AdminChangeUserCountryRequest) Reset() { *x = AdminChangeUserCountryRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[96] + mi := &file_proto_user_v1_user_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7185,7 +7740,7 @@ func (x *AdminChangeUserCountryRequest) String() string { func (*AdminChangeUserCountryRequest) ProtoMessage() {} func (x *AdminChangeUserCountryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[96] + mi := &file_proto_user_v1_user_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7198,7 +7753,7 @@ func (x *AdminChangeUserCountryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminChangeUserCountryRequest.ProtoReflect.Descriptor instead. func (*AdminChangeUserCountryRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{96} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{102} } func (x *AdminChangeUserCountryRequest) GetMeta() *RequestMeta { @@ -7251,7 +7806,7 @@ type ChangeUserCountryResponse struct { func (x *ChangeUserCountryResponse) Reset() { *x = ChangeUserCountryResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[97] + mi := &file_proto_user_v1_user_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7263,7 +7818,7 @@ func (x *ChangeUserCountryResponse) String() string { func (*ChangeUserCountryResponse) ProtoMessage() {} func (x *ChangeUserCountryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[97] + mi := &file_proto_user_v1_user_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7276,7 +7831,7 @@ func (x *ChangeUserCountryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeUserCountryResponse.ProtoReflect.Descriptor instead. func (*ChangeUserCountryResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{97} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{103} } func (x *ChangeUserCountryResponse) GetUser() *User { @@ -7332,7 +7887,7 @@ type SetUserStatusRequest struct { func (x *SetUserStatusRequest) Reset() { *x = SetUserStatusRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[98] + mi := &file_proto_user_v1_user_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7344,7 +7899,7 @@ func (x *SetUserStatusRequest) String() string { func (*SetUserStatusRequest) ProtoMessage() {} func (x *SetUserStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[98] + mi := &file_proto_user_v1_user_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7357,7 +7912,7 @@ func (x *SetUserStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetUserStatusRequest.ProtoReflect.Descriptor instead. func (*SetUserStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{98} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{104} } func (x *SetUserStatusRequest) GetMeta() *RequestMeta { @@ -7424,7 +7979,7 @@ type SetUserStatusResponse struct { func (x *SetUserStatusResponse) Reset() { *x = SetUserStatusResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[99] + mi := &file_proto_user_v1_user_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7436,7 +7991,7 @@ func (x *SetUserStatusResponse) String() string { func (*SetUserStatusResponse) ProtoMessage() {} func (x *SetUserStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[99] + mi := &file_proto_user_v1_user_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7449,7 +8004,7 @@ func (x *SetUserStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetUserStatusResponse.ProtoReflect.Descriptor instead. func (*SetUserStatusResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{99} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{105} } func (x *SetUserStatusResponse) GetUser() *User { @@ -7529,6 +8084,443 @@ func (x *SetUserStatusResponse) GetAccessTokenRevokeError() string { return "" } +// AdminUserBan 是后台管理员封禁的独立事实;expires_at_ms=0 表示永久封禁。 +type AdminUserBan struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BanId string `protobuf:"bytes,1,opt,name=ban_id,json=banId,proto3" json:"ban_id,omitempty"` + CommandId string `protobuf:"bytes,2,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + TargetUserId int64 `protobuf:"varint,3,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + ExpiresAtMs int64 `protobuf:"varint,4,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` + Permanent bool `protobuf:"varint,5,opt,name=permanent,proto3" json:"permanent,omitempty"` + Status string `protobuf:"bytes,6,opt,name=status,proto3" json:"status,omitempty"` + Reason string `protobuf:"bytes,7,opt,name=reason,proto3" json:"reason,omitempty"` + OperatorAdminId int64 `protobuf:"varint,8,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"` + CreatedAtMs int64 `protobuf:"varint,9,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,10,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + ReleasedByAdminId int64 `protobuf:"varint,11,opt,name=released_by_admin_id,json=releasedByAdminId,proto3" json:"released_by_admin_id,omitempty"` + ReleasedReason string `protobuf:"bytes,12,opt,name=released_reason,json=releasedReason,proto3" json:"released_reason,omitempty"` + ReleasedAtMs int64 `protobuf:"varint,13,opt,name=released_at_ms,json=releasedAtMs,proto3" json:"released_at_ms,omitempty"` +} + +func (x *AdminUserBan) Reset() { + *x = AdminUserBan{} + mi := &file_proto_user_v1_user_proto_msgTypes[106] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdminUserBan) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdminUserBan) ProtoMessage() {} + +func (x *AdminUserBan) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[106] + 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 AdminUserBan.ProtoReflect.Descriptor instead. +func (*AdminUserBan) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{106} +} + +func (x *AdminUserBan) GetBanId() string { + if x != nil { + return x.BanId + } + return "" +} + +func (x *AdminUserBan) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *AdminUserBan) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *AdminUserBan) GetExpiresAtMs() int64 { + if x != nil { + return x.ExpiresAtMs + } + return 0 +} + +func (x *AdminUserBan) GetPermanent() bool { + if x != nil { + return x.Permanent + } + return false +} + +func (x *AdminUserBan) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *AdminUserBan) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *AdminUserBan) GetOperatorAdminId() int64 { + if x != nil { + return x.OperatorAdminId + } + return 0 +} + +func (x *AdminUserBan) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *AdminUserBan) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +func (x *AdminUserBan) GetReleasedByAdminId() int64 { + if x != nil { + return x.ReleasedByAdminId + } + return 0 +} + +func (x *AdminUserBan) GetReleasedReason() string { + if x != nil { + return x.ReleasedReason + } + return "" +} + +func (x *AdminUserBan) GetReleasedAtMs() int64 { + if x != nil { + return x.ReleasedAtMs + } + return 0 +} + +type AdminBanUserRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + CommandId string `protobuf:"bytes,2,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + TargetUserId int64 `protobuf:"varint,3,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + ExpiresAtMs int64 `protobuf:"varint,4,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` + OperatorAdminId int64 `protobuf:"varint,5,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"` + Reason string `protobuf:"bytes,6,opt,name=reason,proto3" json:"reason,omitempty"` +} + +func (x *AdminBanUserRequest) Reset() { + *x = AdminBanUserRequest{} + mi := &file_proto_user_v1_user_proto_msgTypes[107] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdminBanUserRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdminBanUserRequest) ProtoMessage() {} + +func (x *AdminBanUserRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[107] + 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 AdminBanUserRequest.ProtoReflect.Descriptor instead. +func (*AdminBanUserRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{107} +} + +func (x *AdminBanUserRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *AdminBanUserRequest) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *AdminBanUserRequest) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *AdminBanUserRequest) GetExpiresAtMs() int64 { + if x != nil { + return x.ExpiresAtMs + } + return 0 +} + +func (x *AdminBanUserRequest) GetOperatorAdminId() int64 { + if x != nil { + return x.OperatorAdminId + } + return 0 +} + +func (x *AdminBanUserRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +type AdminBanUserResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ban *AdminUserBan `protobuf:"bytes,1,opt,name=ban,proto3" json:"ban,omitempty"` + Status *SetUserStatusResponse `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *AdminBanUserResponse) Reset() { + *x = AdminBanUserResponse{} + mi := &file_proto_user_v1_user_proto_msgTypes[108] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdminBanUserResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdminBanUserResponse) ProtoMessage() {} + +func (x *AdminBanUserResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[108] + 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 AdminBanUserResponse.ProtoReflect.Descriptor instead. +func (*AdminBanUserResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{108} +} + +func (x *AdminBanUserResponse) GetBan() *AdminUserBan { + if x != nil { + return x.Ban + } + return nil +} + +func (x *AdminBanUserResponse) GetStatus() *SetUserStatusResponse { + if x != nil { + return x.Status + } + return nil +} + +// AdminUnbanUserRequest 由后台管理员强制释放目标用户全部管理员、经理和 legacy 封禁来源。 +type AdminUnbanUserRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + // ban_id 已废弃;保留字段号用于滚动发布兼容,强制解封始终按 target_user_id 处理全部有效事实。 + BanId string `protobuf:"bytes,2,opt,name=ban_id,json=banId,proto3" json:"ban_id,omitempty"` + TargetUserId int64 `protobuf:"varint,3,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + OperatorAdminId int64 `protobuf:"varint,4,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"` + Reason string `protobuf:"bytes,5,opt,name=reason,proto3" json:"reason,omitempty"` +} + +func (x *AdminUnbanUserRequest) Reset() { + *x = AdminUnbanUserRequest{} + mi := &file_proto_user_v1_user_proto_msgTypes[109] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdminUnbanUserRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdminUnbanUserRequest) ProtoMessage() {} + +func (x *AdminUnbanUserRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[109] + 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 AdminUnbanUserRequest.ProtoReflect.Descriptor instead. +func (*AdminUnbanUserRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{109} +} + +func (x *AdminUnbanUserRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *AdminUnbanUserRequest) GetBanId() string { + if x != nil { + return x.BanId + } + return "" +} + +func (x *AdminUnbanUserRequest) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *AdminUnbanUserRequest) GetOperatorAdminId() int64 { + if x != nil { + return x.OperatorAdminId + } + return 0 +} + +func (x *AdminUnbanUserRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +type AdminUnbanUserResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // ban 仅为旧客户端保留;强制解封可能只释放经理或 legacy 来源,因此新调用方不应依赖该字段。 + Ban *AdminUserBan `protobuf:"bytes,1,opt,name=ban,proto3" json:"ban,omitempty"` + Status *SetUserStatusResponse `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + ReleasedAdminBanCount int64 `protobuf:"varint,3,opt,name=released_admin_ban_count,json=releasedAdminBanCount,proto3" json:"released_admin_ban_count,omitempty"` + ReleasedManagerBlockCount int64 `protobuf:"varint,4,opt,name=released_manager_block_count,json=releasedManagerBlockCount,proto3" json:"released_manager_block_count,omitempty"` + StatusRestored bool `protobuf:"varint,5,opt,name=status_restored,json=statusRestored,proto3" json:"status_restored,omitempty"` +} + +func (x *AdminUnbanUserResponse) Reset() { + *x = AdminUnbanUserResponse{} + mi := &file_proto_user_v1_user_proto_msgTypes[110] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdminUnbanUserResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdminUnbanUserResponse) ProtoMessage() {} + +func (x *AdminUnbanUserResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[110] + 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 AdminUnbanUserResponse.ProtoReflect.Descriptor instead. +func (*AdminUnbanUserResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{110} +} + +func (x *AdminUnbanUserResponse) GetBan() *AdminUserBan { + if x != nil { + return x.Ban + } + return nil +} + +func (x *AdminUnbanUserResponse) GetStatus() *SetUserStatusResponse { + if x != nil { + return x.Status + } + return nil +} + +func (x *AdminUnbanUserResponse) GetReleasedAdminBanCount() int64 { + if x != nil { + return x.ReleasedAdminBanCount + } + return 0 +} + +func (x *AdminUnbanUserResponse) GetReleasedManagerBlockCount() int64 { + if x != nil { + return x.ReleasedManagerBlockCount + } + return 0 +} + +func (x *AdminUnbanUserResponse) GetStatusRestored() bool { + if x != nil { + return x.StatusRestored + } + return false +} + type ManagerUserBlock struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -7550,7 +8542,7 @@ type ManagerUserBlock struct { func (x *ManagerUserBlock) Reset() { *x = ManagerUserBlock{} - mi := &file_proto_user_v1_user_proto_msgTypes[100] + mi := &file_proto_user_v1_user_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7562,7 +8554,7 @@ func (x *ManagerUserBlock) String() string { func (*ManagerUserBlock) ProtoMessage() {} func (x *ManagerUserBlock) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[100] + mi := &file_proto_user_v1_user_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7575,7 +8567,7 @@ func (x *ManagerUserBlock) ProtoReflect() protoreflect.Message { // Deprecated: Use ManagerUserBlock.ProtoReflect.Descriptor instead. func (*ManagerUserBlock) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{100} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{111} } func (x *ManagerUserBlock) GetBlockId() string { @@ -7677,7 +8669,7 @@ type CreateManagerUserBlockRequest struct { func (x *CreateManagerUserBlockRequest) Reset() { *x = CreateManagerUserBlockRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[101] + mi := &file_proto_user_v1_user_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7689,7 +8681,7 @@ func (x *CreateManagerUserBlockRequest) String() string { func (*CreateManagerUserBlockRequest) ProtoMessage() {} func (x *CreateManagerUserBlockRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[101] + mi := &file_proto_user_v1_user_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7702,7 +8694,7 @@ func (x *CreateManagerUserBlockRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateManagerUserBlockRequest.ProtoReflect.Descriptor instead. func (*CreateManagerUserBlockRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{101} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{112} } func (x *CreateManagerUserBlockRequest) GetMeta() *RequestMeta { @@ -7758,7 +8750,7 @@ type CreateManagerUserBlockResponse struct { func (x *CreateManagerUserBlockResponse) Reset() { *x = CreateManagerUserBlockResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[102] + mi := &file_proto_user_v1_user_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7770,7 +8762,7 @@ func (x *CreateManagerUserBlockResponse) String() string { func (*CreateManagerUserBlockResponse) ProtoMessage() {} func (x *CreateManagerUserBlockResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[102] + mi := &file_proto_user_v1_user_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7783,7 +8775,7 @@ func (x *CreateManagerUserBlockResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateManagerUserBlockResponse.ProtoReflect.Descriptor instead. func (*CreateManagerUserBlockResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{102} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{113} } func (x *CreateManagerUserBlockResponse) GetBlock() *ManagerUserBlock { @@ -7813,7 +8805,7 @@ type ListManagerUserBlocksRequest struct { func (x *ListManagerUserBlocksRequest) Reset() { *x = ListManagerUserBlocksRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[103] + mi := &file_proto_user_v1_user_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7825,7 +8817,7 @@ func (x *ListManagerUserBlocksRequest) String() string { func (*ListManagerUserBlocksRequest) ProtoMessage() {} func (x *ListManagerUserBlocksRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[103] + mi := &file_proto_user_v1_user_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7838,7 +8830,7 @@ func (x *ListManagerUserBlocksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListManagerUserBlocksRequest.ProtoReflect.Descriptor instead. func (*ListManagerUserBlocksRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{103} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{114} } func (x *ListManagerUserBlocksRequest) GetMeta() *RequestMeta { @@ -7879,7 +8871,7 @@ type ListManagerUserBlocksResponse struct { func (x *ListManagerUserBlocksResponse) Reset() { *x = ListManagerUserBlocksResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[104] + mi := &file_proto_user_v1_user_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7891,7 +8883,7 @@ func (x *ListManagerUserBlocksResponse) String() string { func (*ListManagerUserBlocksResponse) ProtoMessage() {} func (x *ListManagerUserBlocksResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[104] + mi := &file_proto_user_v1_user_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7904,7 +8896,7 @@ func (x *ListManagerUserBlocksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListManagerUserBlocksResponse.ProtoReflect.Descriptor instead. func (*ListManagerUserBlocksResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{104} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{115} } func (x *ListManagerUserBlocksResponse) GetBlocks() []*ManagerUserBlock { @@ -7927,7 +8919,7 @@ type UnblockManagerUserRequest struct { func (x *UnblockManagerUserRequest) Reset() { *x = UnblockManagerUserRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[105] + mi := &file_proto_user_v1_user_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7939,7 +8931,7 @@ func (x *UnblockManagerUserRequest) String() string { func (*UnblockManagerUserRequest) ProtoMessage() {} func (x *UnblockManagerUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[105] + mi := &file_proto_user_v1_user_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7952,7 +8944,7 @@ func (x *UnblockManagerUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UnblockManagerUserRequest.ProtoReflect.Descriptor instead. func (*UnblockManagerUserRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{105} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{116} } func (x *UnblockManagerUserRequest) GetMeta() *RequestMeta { @@ -7994,7 +8986,7 @@ type UnblockManagerUserResponse struct { func (x *UnblockManagerUserResponse) Reset() { *x = UnblockManagerUserResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[106] + mi := &file_proto_user_v1_user_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8006,7 +8998,7 @@ func (x *UnblockManagerUserResponse) String() string { func (*UnblockManagerUserResponse) ProtoMessage() {} func (x *UnblockManagerUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[106] + mi := &file_proto_user_v1_user_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8019,7 +9011,7 @@ func (x *UnblockManagerUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UnblockManagerUserResponse.ProtoReflect.Descriptor instead. func (*UnblockManagerUserResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{106} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{117} } func (x *UnblockManagerUserResponse) GetBlock() *ManagerUserBlock { @@ -8053,7 +9045,7 @@ type CompleteOnboardingRequest struct { func (x *CompleteOnboardingRequest) Reset() { *x = CompleteOnboardingRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[107] + mi := &file_proto_user_v1_user_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8065,7 +9057,7 @@ func (x *CompleteOnboardingRequest) String() string { func (*CompleteOnboardingRequest) ProtoMessage() {} func (x *CompleteOnboardingRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[107] + mi := &file_proto_user_v1_user_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8078,7 +9070,7 @@ func (x *CompleteOnboardingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CompleteOnboardingRequest.ProtoReflect.Descriptor instead. func (*CompleteOnboardingRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{107} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{118} } func (x *CompleteOnboardingRequest) GetMeta() *RequestMeta { @@ -8146,7 +9138,7 @@ type CompleteOnboardingResponse struct { func (x *CompleteOnboardingResponse) Reset() { *x = CompleteOnboardingResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[108] + mi := &file_proto_user_v1_user_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8158,7 +9150,7 @@ func (x *CompleteOnboardingResponse) String() string { func (*CompleteOnboardingResponse) ProtoMessage() {} func (x *CompleteOnboardingResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[108] + mi := &file_proto_user_v1_user_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8171,7 +9163,7 @@ func (x *CompleteOnboardingResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CompleteOnboardingResponse.ProtoReflect.Descriptor instead. func (*CompleteOnboardingResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{108} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{119} } func (x *CompleteOnboardingResponse) GetUser() *User { @@ -8229,7 +9221,7 @@ type SearchInviteReferrerRequest struct { func (x *SearchInviteReferrerRequest) Reset() { *x = SearchInviteReferrerRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[109] + mi := &file_proto_user_v1_user_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8241,7 +9233,7 @@ func (x *SearchInviteReferrerRequest) String() string { func (*SearchInviteReferrerRequest) ProtoMessage() {} func (x *SearchInviteReferrerRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[109] + mi := &file_proto_user_v1_user_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8254,7 +9246,7 @@ func (x *SearchInviteReferrerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SearchInviteReferrerRequest.ProtoReflect.Descriptor instead. func (*SearchInviteReferrerRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{109} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{120} } func (x *SearchInviteReferrerRequest) GetMeta() *RequestMeta { @@ -8288,7 +9280,7 @@ type SearchInviteReferrerResponse struct { func (x *SearchInviteReferrerResponse) Reset() { *x = SearchInviteReferrerResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[110] + mi := &file_proto_user_v1_user_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8300,7 +9292,7 @@ func (x *SearchInviteReferrerResponse) String() string { func (*SearchInviteReferrerResponse) ProtoMessage() {} func (x *SearchInviteReferrerResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[110] + mi := &file_proto_user_v1_user_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8313,7 +9305,7 @@ func (x *SearchInviteReferrerResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SearchInviteReferrerResponse.ProtoReflect.Descriptor instead. func (*SearchInviteReferrerResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{110} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{121} } func (x *SearchInviteReferrerResponse) GetInviter() *User { @@ -8336,7 +9328,7 @@ type BindInviteReferrerRequest struct { func (x *BindInviteReferrerRequest) Reset() { *x = BindInviteReferrerRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[111] + mi := &file_proto_user_v1_user_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8348,7 +9340,7 @@ func (x *BindInviteReferrerRequest) String() string { func (*BindInviteReferrerRequest) ProtoMessage() {} func (x *BindInviteReferrerRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[111] + mi := &file_proto_user_v1_user_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8361,7 +9353,7 @@ func (x *BindInviteReferrerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BindInviteReferrerRequest.ProtoReflect.Descriptor instead. func (*BindInviteReferrerRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{111} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{122} } func (x *BindInviteReferrerRequest) GetMeta() *RequestMeta { @@ -8396,7 +9388,7 @@ type BindInviteReferrerResponse struct { func (x *BindInviteReferrerResponse) Reset() { *x = BindInviteReferrerResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[112] + mi := &file_proto_user_v1_user_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8408,7 +9400,7 @@ func (x *BindInviteReferrerResponse) String() string { func (*BindInviteReferrerResponse) ProtoMessage() {} func (x *BindInviteReferrerResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[112] + mi := &file_proto_user_v1_user_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8421,7 +9413,7 @@ func (x *BindInviteReferrerResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BindInviteReferrerResponse.ProtoReflect.Descriptor instead. func (*BindInviteReferrerResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{112} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{123} } func (x *BindInviteReferrerResponse) GetInvite() *InviteBinding { @@ -8458,7 +9450,7 @@ type BindPushTokenRequest struct { func (x *BindPushTokenRequest) Reset() { *x = BindPushTokenRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[113] + mi := &file_proto_user_v1_user_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8470,7 +9462,7 @@ func (x *BindPushTokenRequest) String() string { func (*BindPushTokenRequest) ProtoMessage() {} func (x *BindPushTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[113] + mi := &file_proto_user_v1_user_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8483,7 +9475,7 @@ func (x *BindPushTokenRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BindPushTokenRequest.ProtoReflect.Descriptor instead. func (*BindPushTokenRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{113} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{124} } func (x *BindPushTokenRequest) GetMeta() *RequestMeta { @@ -8560,7 +9552,7 @@ type BindPushTokenResponse struct { func (x *BindPushTokenResponse) Reset() { *x = BindPushTokenResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[114] + mi := &file_proto_user_v1_user_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8572,7 +9564,7 @@ func (x *BindPushTokenResponse) String() string { func (*BindPushTokenResponse) ProtoMessage() {} func (x *BindPushTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[114] + mi := &file_proto_user_v1_user_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8585,7 +9577,7 @@ func (x *BindPushTokenResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BindPushTokenResponse.ProtoReflect.Descriptor instead. func (*BindPushTokenResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{114} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{125} } func (x *BindPushTokenResponse) GetBound() bool { @@ -8617,7 +9609,7 @@ type DeletePushTokenRequest struct { func (x *DeletePushTokenRequest) Reset() { *x = DeletePushTokenRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[115] + mi := &file_proto_user_v1_user_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8629,7 +9621,7 @@ func (x *DeletePushTokenRequest) String() string { func (*DeletePushTokenRequest) ProtoMessage() {} func (x *DeletePushTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[115] + mi := &file_proto_user_v1_user_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8642,7 +9634,7 @@ func (x *DeletePushTokenRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletePushTokenRequest.ProtoReflect.Descriptor instead. func (*DeletePushTokenRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{115} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{126} } func (x *DeletePushTokenRequest) GetMeta() *RequestMeta { @@ -8684,7 +9676,7 @@ type DeletePushTokenResponse struct { func (x *DeletePushTokenResponse) Reset() { *x = DeletePushTokenResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[116] + mi := &file_proto_user_v1_user_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8696,7 +9688,7 @@ func (x *DeletePushTokenResponse) String() string { func (*DeletePushTokenResponse) ProtoMessage() {} func (x *DeletePushTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[116] + mi := &file_proto_user_v1_user_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8709,7 +9701,7 @@ func (x *DeletePushTokenResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletePushTokenResponse.ProtoReflect.Descriptor instead. func (*DeletePushTokenResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{116} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{127} } func (x *DeletePushTokenResponse) GetDeleted() bool { @@ -8749,7 +9741,7 @@ type Country struct { func (x *Country) Reset() { *x = Country{} - mi := &file_proto_user_v1_user_proto_msgTypes[117] + mi := &file_proto_user_v1_user_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8761,7 +9753,7 @@ func (x *Country) String() string { func (*Country) ProtoMessage() {} func (x *Country) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[117] + mi := &file_proto_user_v1_user_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8774,7 +9766,7 @@ func (x *Country) ProtoReflect() protoreflect.Message { // Deprecated: Use Country.ProtoReflect.Descriptor instead. func (*Country) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{117} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{128} } func (x *Country) GetCountryId() int64 { @@ -8887,7 +9879,7 @@ type Region struct { func (x *Region) Reset() { *x = Region{} - mi := &file_proto_user_v1_user_proto_msgTypes[118] + mi := &file_proto_user_v1_user_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8899,7 +9891,7 @@ func (x *Region) String() string { func (*Region) ProtoMessage() {} func (x *Region) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[118] + mi := &file_proto_user_v1_user_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8912,7 +9904,7 @@ func (x *Region) ProtoReflect() protoreflect.Message { // Deprecated: Use Region.ProtoReflect.Descriptor instead. func (*Region) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{118} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{129} } func (x *Region) GetRegionId() int64 { @@ -8989,7 +9981,7 @@ type ListCountriesRequest struct { func (x *ListCountriesRequest) Reset() { *x = ListCountriesRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[119] + mi := &file_proto_user_v1_user_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9001,7 +9993,7 @@ func (x *ListCountriesRequest) String() string { func (*ListCountriesRequest) ProtoMessage() {} func (x *ListCountriesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[119] + mi := &file_proto_user_v1_user_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9014,7 +10006,7 @@ func (x *ListCountriesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCountriesRequest.ProtoReflect.Descriptor instead. func (*ListCountriesRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{119} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{130} } func (x *ListCountriesRequest) GetMeta() *RequestMeta { @@ -9041,7 +10033,7 @@ type ListCountriesResponse struct { func (x *ListCountriesResponse) Reset() { *x = ListCountriesResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[120] + mi := &file_proto_user_v1_user_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9053,7 +10045,7 @@ func (x *ListCountriesResponse) String() string { func (*ListCountriesResponse) ProtoMessage() {} func (x *ListCountriesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[120] + mi := &file_proto_user_v1_user_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9066,7 +10058,7 @@ func (x *ListCountriesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCountriesResponse.ProtoReflect.Descriptor instead. func (*ListCountriesResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{120} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{131} } func (x *ListCountriesResponse) GetCountries() []*Country { @@ -9096,7 +10088,7 @@ type UpdateCountryRequest struct { func (x *UpdateCountryRequest) Reset() { *x = UpdateCountryRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[121] + mi := &file_proto_user_v1_user_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9108,7 +10100,7 @@ func (x *UpdateCountryRequest) String() string { func (*UpdateCountryRequest) ProtoMessage() {} func (x *UpdateCountryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[121] + mi := &file_proto_user_v1_user_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9121,7 +10113,7 @@ func (x *UpdateCountryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateCountryRequest.ProtoReflect.Descriptor instead. func (*UpdateCountryRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{121} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{132} } func (x *UpdateCountryRequest) GetMeta() *RequestMeta { @@ -9204,7 +10196,7 @@ type CountryResponse struct { func (x *CountryResponse) Reset() { *x = CountryResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[122] + mi := &file_proto_user_v1_user_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9216,7 +10208,7 @@ func (x *CountryResponse) String() string { func (*CountryResponse) ProtoMessage() {} func (x *CountryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[122] + mi := &file_proto_user_v1_user_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9229,7 +10221,7 @@ func (x *CountryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CountryResponse.ProtoReflect.Descriptor instead. func (*CountryResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{122} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{133} } func (x *CountryResponse) GetCountry() *Country { @@ -9249,7 +10241,7 @@ type ListRegistrationCountriesRequest struct { func (x *ListRegistrationCountriesRequest) Reset() { *x = ListRegistrationCountriesRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[123] + mi := &file_proto_user_v1_user_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9261,7 +10253,7 @@ func (x *ListRegistrationCountriesRequest) String() string { func (*ListRegistrationCountriesRequest) ProtoMessage() {} func (x *ListRegistrationCountriesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[123] + mi := &file_proto_user_v1_user_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9274,7 +10266,7 @@ func (x *ListRegistrationCountriesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRegistrationCountriesRequest.ProtoReflect.Descriptor instead. func (*ListRegistrationCountriesRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{123} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{134} } func (x *ListRegistrationCountriesRequest) GetMeta() *RequestMeta { @@ -9294,7 +10286,7 @@ type ListRegistrationCountriesResponse struct { func (x *ListRegistrationCountriesResponse) Reset() { *x = ListRegistrationCountriesResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[124] + mi := &file_proto_user_v1_user_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9306,7 +10298,7 @@ func (x *ListRegistrationCountriesResponse) String() string { func (*ListRegistrationCountriesResponse) ProtoMessage() {} func (x *ListRegistrationCountriesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[124] + mi := &file_proto_user_v1_user_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9319,7 +10311,7 @@ func (x *ListRegistrationCountriesResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ListRegistrationCountriesResponse.ProtoReflect.Descriptor instead. func (*ListRegistrationCountriesResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{124} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{135} } func (x *ListRegistrationCountriesResponse) GetCountries() []*Country { @@ -9341,7 +10333,7 @@ type LoginRiskBlockedCountry struct { func (x *LoginRiskBlockedCountry) Reset() { *x = LoginRiskBlockedCountry{} - mi := &file_proto_user_v1_user_proto_msgTypes[125] + mi := &file_proto_user_v1_user_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9353,7 +10345,7 @@ func (x *LoginRiskBlockedCountry) String() string { func (*LoginRiskBlockedCountry) ProtoMessage() {} func (x *LoginRiskBlockedCountry) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[125] + mi := &file_proto_user_v1_user_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9366,7 +10358,7 @@ func (x *LoginRiskBlockedCountry) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginRiskBlockedCountry.ProtoReflect.Descriptor instead. func (*LoginRiskBlockedCountry) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{125} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{136} } func (x *LoginRiskBlockedCountry) GetCountryCode() string { @@ -9400,7 +10392,7 @@ type ListLoginRiskBlockedCountriesRequest struct { func (x *ListLoginRiskBlockedCountriesRequest) Reset() { *x = ListLoginRiskBlockedCountriesRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[126] + mi := &file_proto_user_v1_user_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9412,7 +10404,7 @@ func (x *ListLoginRiskBlockedCountriesRequest) String() string { func (*ListLoginRiskBlockedCountriesRequest) ProtoMessage() {} func (x *ListLoginRiskBlockedCountriesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[126] + mi := &file_proto_user_v1_user_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9425,7 +10417,7 @@ func (x *ListLoginRiskBlockedCountriesRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use ListLoginRiskBlockedCountriesRequest.ProtoReflect.Descriptor instead. func (*ListLoginRiskBlockedCountriesRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{126} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{137} } func (x *ListLoginRiskBlockedCountriesRequest) GetMeta() *RequestMeta { @@ -9446,7 +10438,7 @@ type ListLoginRiskBlockedCountriesResponse struct { func (x *ListLoginRiskBlockedCountriesResponse) Reset() { *x = ListLoginRiskBlockedCountriesResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[127] + mi := &file_proto_user_v1_user_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9458,7 +10450,7 @@ func (x *ListLoginRiskBlockedCountriesResponse) String() string { func (*ListLoginRiskBlockedCountriesResponse) ProtoMessage() {} func (x *ListLoginRiskBlockedCountriesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[127] + mi := &file_proto_user_v1_user_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9471,7 +10463,7 @@ func (x *ListLoginRiskBlockedCountriesResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use ListLoginRiskBlockedCountriesResponse.ProtoReflect.Descriptor instead. func (*ListLoginRiskBlockedCountriesResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{127} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{138} } func (x *ListLoginRiskBlockedCountriesResponse) GetCountries() []*LoginRiskBlockedCountry { @@ -9499,7 +10491,7 @@ type ListRegionsRequest struct { func (x *ListRegionsRequest) Reset() { *x = ListRegionsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[128] + mi := &file_proto_user_v1_user_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9511,7 +10503,7 @@ func (x *ListRegionsRequest) String() string { func (*ListRegionsRequest) ProtoMessage() {} func (x *ListRegionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[128] + mi := &file_proto_user_v1_user_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9524,7 +10516,7 @@ func (x *ListRegionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRegionsRequest.ProtoReflect.Descriptor instead. func (*ListRegionsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{128} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{139} } func (x *ListRegionsRequest) GetMeta() *RequestMeta { @@ -9551,7 +10543,7 @@ type ListRegionsResponse struct { func (x *ListRegionsResponse) Reset() { *x = ListRegionsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[129] + mi := &file_proto_user_v1_user_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9563,7 +10555,7 @@ func (x *ListRegionsResponse) String() string { func (*ListRegionsResponse) ProtoMessage() {} func (x *ListRegionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[129] + mi := &file_proto_user_v1_user_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9576,7 +10568,7 @@ func (x *ListRegionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRegionsResponse.ProtoReflect.Descriptor instead. func (*ListRegionsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{129} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{140} } func (x *ListRegionsResponse) GetRegions() []*Region { @@ -9597,7 +10589,7 @@ type GetRegionRequest struct { func (x *GetRegionRequest) Reset() { *x = GetRegionRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[130] + mi := &file_proto_user_v1_user_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9609,7 +10601,7 @@ func (x *GetRegionRequest) String() string { func (*GetRegionRequest) ProtoMessage() {} func (x *GetRegionRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[130] + mi := &file_proto_user_v1_user_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9622,7 +10614,7 @@ func (x *GetRegionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRegionRequest.ProtoReflect.Descriptor instead. func (*GetRegionRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{130} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{141} } func (x *GetRegionRequest) GetMeta() *RequestMeta { @@ -9655,7 +10647,7 @@ type UpdateRegionRequest struct { func (x *UpdateRegionRequest) Reset() { *x = UpdateRegionRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[131] + mi := &file_proto_user_v1_user_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9667,7 +10659,7 @@ func (x *UpdateRegionRequest) String() string { func (*UpdateRegionRequest) ProtoMessage() {} func (x *UpdateRegionRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[131] + mi := &file_proto_user_v1_user_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9680,7 +10672,7 @@ func (x *UpdateRegionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRegionRequest.ProtoReflect.Descriptor instead. func (*UpdateRegionRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{131} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{142} } func (x *UpdateRegionRequest) GetMeta() *RequestMeta { @@ -9738,7 +10730,7 @@ type ReplaceRegionCountriesRequest struct { func (x *ReplaceRegionCountriesRequest) Reset() { *x = ReplaceRegionCountriesRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[132] + mi := &file_proto_user_v1_user_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9750,7 +10742,7 @@ func (x *ReplaceRegionCountriesRequest) String() string { func (*ReplaceRegionCountriesRequest) ProtoMessage() {} func (x *ReplaceRegionCountriesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[132] + mi := &file_proto_user_v1_user_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9763,7 +10755,7 @@ func (x *ReplaceRegionCountriesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReplaceRegionCountriesRequest.ProtoReflect.Descriptor instead. func (*ReplaceRegionCountriesRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{132} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{143} } func (x *ReplaceRegionCountriesRequest) GetMeta() *RequestMeta { @@ -9804,7 +10796,7 @@ type RegionResponse struct { func (x *RegionResponse) Reset() { *x = RegionResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[133] + mi := &file_proto_user_v1_user_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9816,7 +10808,7 @@ func (x *RegionResponse) String() string { func (*RegionResponse) ProtoMessage() {} func (x *RegionResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[133] + mi := &file_proto_user_v1_user_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9829,7 +10821,7 @@ func (x *RegionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RegionResponse.ProtoReflect.Descriptor instead. func (*RegionResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{133} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{144} } func (x *RegionResponse) GetRegion() *Region { @@ -9858,7 +10850,7 @@ type UserIdentity struct { func (x *UserIdentity) Reset() { *x = UserIdentity{} - mi := &file_proto_user_v1_user_proto_msgTypes[134] + mi := &file_proto_user_v1_user_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9870,7 +10862,7 @@ func (x *UserIdentity) String() string { func (*UserIdentity) ProtoMessage() {} func (x *UserIdentity) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[134] + mi := &file_proto_user_v1_user_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9883,7 +10875,7 @@ func (x *UserIdentity) ProtoReflect() protoreflect.Message { // Deprecated: Use UserIdentity.ProtoReflect.Descriptor instead. func (*UserIdentity) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{134} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{145} } func (x *UserIdentity) GetUserId() int64 { @@ -9961,7 +10953,7 @@ type GetUserIdentityRequest struct { func (x *GetUserIdentityRequest) Reset() { *x = GetUserIdentityRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[135] + mi := &file_proto_user_v1_user_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9973,7 +10965,7 @@ func (x *GetUserIdentityRequest) String() string { func (*GetUserIdentityRequest) ProtoMessage() {} func (x *GetUserIdentityRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[135] + mi := &file_proto_user_v1_user_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9986,7 +10978,7 @@ func (x *GetUserIdentityRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserIdentityRequest.ProtoReflect.Descriptor instead. func (*GetUserIdentityRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{135} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{146} } func (x *GetUserIdentityRequest) GetMeta() *RequestMeta { @@ -10014,7 +11006,7 @@ type GetUserIdentityResponse struct { func (x *GetUserIdentityResponse) Reset() { *x = GetUserIdentityResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[136] + mi := &file_proto_user_v1_user_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10026,7 +11018,7 @@ func (x *GetUserIdentityResponse) String() string { func (*GetUserIdentityResponse) ProtoMessage() {} func (x *GetUserIdentityResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[136] + mi := &file_proto_user_v1_user_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10039,7 +11031,7 @@ func (x *GetUserIdentityResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserIdentityResponse.ProtoReflect.Descriptor instead. func (*GetUserIdentityResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{136} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{147} } func (x *GetUserIdentityResponse) GetIdentity() *UserIdentity { @@ -10061,7 +11053,7 @@ type ResolveDisplayUserIDRequest struct { func (x *ResolveDisplayUserIDRequest) Reset() { *x = ResolveDisplayUserIDRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[137] + mi := &file_proto_user_v1_user_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10073,7 +11065,7 @@ func (x *ResolveDisplayUserIDRequest) String() string { func (*ResolveDisplayUserIDRequest) ProtoMessage() {} func (x *ResolveDisplayUserIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[137] + mi := &file_proto_user_v1_user_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10086,7 +11078,7 @@ func (x *ResolveDisplayUserIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ResolveDisplayUserIDRequest.ProtoReflect.Descriptor instead. func (*ResolveDisplayUserIDRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{137} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{148} } func (x *ResolveDisplayUserIDRequest) GetMeta() *RequestMeta { @@ -10114,7 +11106,7 @@ type ResolveDisplayUserIDResponse struct { func (x *ResolveDisplayUserIDResponse) Reset() { *x = ResolveDisplayUserIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[138] + mi := &file_proto_user_v1_user_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10126,7 +11118,7 @@ func (x *ResolveDisplayUserIDResponse) String() string { func (*ResolveDisplayUserIDResponse) ProtoMessage() {} func (x *ResolveDisplayUserIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[138] + mi := &file_proto_user_v1_user_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10139,7 +11131,7 @@ func (x *ResolveDisplayUserIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ResolveDisplayUserIDResponse.ProtoReflect.Descriptor instead. func (*ResolveDisplayUserIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{138} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{149} } func (x *ResolveDisplayUserIDResponse) GetIdentity() *UserIdentity { @@ -10164,7 +11156,7 @@ type ChangeDisplayUserIDRequest struct { func (x *ChangeDisplayUserIDRequest) Reset() { *x = ChangeDisplayUserIDRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[139] + mi := &file_proto_user_v1_user_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10176,7 +11168,7 @@ func (x *ChangeDisplayUserIDRequest) String() string { func (*ChangeDisplayUserIDRequest) ProtoMessage() {} func (x *ChangeDisplayUserIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[139] + mi := &file_proto_user_v1_user_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10189,7 +11181,7 @@ func (x *ChangeDisplayUserIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeDisplayUserIDRequest.ProtoReflect.Descriptor instead. func (*ChangeDisplayUserIDRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{139} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{150} } func (x *ChangeDisplayUserIDRequest) GetMeta() *RequestMeta { @@ -10238,7 +11230,7 @@ type ChangeDisplayUserIDResponse struct { func (x *ChangeDisplayUserIDResponse) Reset() { *x = ChangeDisplayUserIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[140] + mi := &file_proto_user_v1_user_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10250,7 +11242,7 @@ func (x *ChangeDisplayUserIDResponse) String() string { func (*ChangeDisplayUserIDResponse) ProtoMessage() {} func (x *ChangeDisplayUserIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[140] + mi := &file_proto_user_v1_user_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10263,7 +11255,7 @@ func (x *ChangeDisplayUserIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeDisplayUserIDResponse.ProtoReflect.Descriptor instead. func (*ChangeDisplayUserIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{140} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{151} } func (x *ChangeDisplayUserIDResponse) GetIdentity() *UserIdentity { @@ -10288,7 +11280,7 @@ type ApplyPrettyDisplayUserIDRequest struct { func (x *ApplyPrettyDisplayUserIDRequest) Reset() { *x = ApplyPrettyDisplayUserIDRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[141] + mi := &file_proto_user_v1_user_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10300,7 +11292,7 @@ func (x *ApplyPrettyDisplayUserIDRequest) String() string { func (*ApplyPrettyDisplayUserIDRequest) ProtoMessage() {} func (x *ApplyPrettyDisplayUserIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[141] + mi := &file_proto_user_v1_user_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10313,7 +11305,7 @@ func (x *ApplyPrettyDisplayUserIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyPrettyDisplayUserIDRequest.ProtoReflect.Descriptor instead. func (*ApplyPrettyDisplayUserIDRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{141} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{152} } func (x *ApplyPrettyDisplayUserIDRequest) GetMeta() *RequestMeta { @@ -10363,7 +11355,7 @@ type ApplyPrettyDisplayUserIDResponse struct { func (x *ApplyPrettyDisplayUserIDResponse) Reset() { *x = ApplyPrettyDisplayUserIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[142] + mi := &file_proto_user_v1_user_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10375,7 +11367,7 @@ func (x *ApplyPrettyDisplayUserIDResponse) String() string { func (*ApplyPrettyDisplayUserIDResponse) ProtoMessage() {} func (x *ApplyPrettyDisplayUserIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[142] + mi := &file_proto_user_v1_user_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10388,7 +11380,7 @@ func (x *ApplyPrettyDisplayUserIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyPrettyDisplayUserIDResponse.ProtoReflect.Descriptor instead. func (*ApplyPrettyDisplayUserIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{142} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{153} } func (x *ApplyPrettyDisplayUserIDResponse) GetIdentity() *UserIdentity { @@ -10418,7 +11410,7 @@ type ExpirePrettyDisplayUserIDRequest struct { func (x *ExpirePrettyDisplayUserIDRequest) Reset() { *x = ExpirePrettyDisplayUserIDRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[143] + mi := &file_proto_user_v1_user_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10430,7 +11422,7 @@ func (x *ExpirePrettyDisplayUserIDRequest) String() string { func (*ExpirePrettyDisplayUserIDRequest) ProtoMessage() {} func (x *ExpirePrettyDisplayUserIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[143] + mi := &file_proto_user_v1_user_proto_msgTypes[154] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10443,7 +11435,7 @@ func (x *ExpirePrettyDisplayUserIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExpirePrettyDisplayUserIDRequest.ProtoReflect.Descriptor instead. func (*ExpirePrettyDisplayUserIDRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{143} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{154} } func (x *ExpirePrettyDisplayUserIDRequest) GetMeta() *RequestMeta { @@ -10478,7 +11470,7 @@ type ExpirePrettyDisplayUserIDResponse struct { func (x *ExpirePrettyDisplayUserIDResponse) Reset() { *x = ExpirePrettyDisplayUserIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[144] + mi := &file_proto_user_v1_user_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10490,7 +11482,7 @@ func (x *ExpirePrettyDisplayUserIDResponse) String() string { func (*ExpirePrettyDisplayUserIDResponse) ProtoMessage() {} func (x *ExpirePrettyDisplayUserIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[144] + mi := &file_proto_user_v1_user_proto_msgTypes[155] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10503,7 +11495,7 @@ func (x *ExpirePrettyDisplayUserIDResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ExpirePrettyDisplayUserIDResponse.ProtoReflect.Descriptor instead. func (*ExpirePrettyDisplayUserIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{144} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{155} } func (x *ExpirePrettyDisplayUserIDResponse) GetIdentity() *UserIdentity { @@ -10537,7 +11529,7 @@ type PrettyDisplayIDPool struct { func (x *PrettyDisplayIDPool) Reset() { *x = PrettyDisplayIDPool{} - mi := &file_proto_user_v1_user_proto_msgTypes[145] + mi := &file_proto_user_v1_user_proto_msgTypes[156] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10549,7 +11541,7 @@ func (x *PrettyDisplayIDPool) String() string { func (*PrettyDisplayIDPool) ProtoMessage() {} func (x *PrettyDisplayIDPool) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[145] + mi := &file_proto_user_v1_user_proto_msgTypes[156] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10562,7 +11554,7 @@ func (x *PrettyDisplayIDPool) ProtoReflect() protoreflect.Message { // Deprecated: Use PrettyDisplayIDPool.ProtoReflect.Descriptor instead. func (*PrettyDisplayIDPool) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{145} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{156} } func (x *PrettyDisplayIDPool) GetAppCode() string { @@ -10689,7 +11681,7 @@ type PrettyDisplayID struct { func (x *PrettyDisplayID) Reset() { *x = PrettyDisplayID{} - mi := &file_proto_user_v1_user_proto_msgTypes[146] + mi := &file_proto_user_v1_user_proto_msgTypes[157] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10701,7 +11693,7 @@ func (x *PrettyDisplayID) String() string { func (*PrettyDisplayID) ProtoMessage() {} func (x *PrettyDisplayID) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[146] + mi := &file_proto_user_v1_user_proto_msgTypes[157] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10714,7 +11706,7 @@ func (x *PrettyDisplayID) ProtoReflect() protoreflect.Message { // Deprecated: Use PrettyDisplayID.ProtoReflect.Descriptor instead. func (*PrettyDisplayID) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{146} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{157} } func (x *PrettyDisplayID) GetAppCode() string { @@ -10851,7 +11843,7 @@ type PrettyDisplayIDGenerationBatch struct { func (x *PrettyDisplayIDGenerationBatch) Reset() { *x = PrettyDisplayIDGenerationBatch{} - mi := &file_proto_user_v1_user_proto_msgTypes[147] + mi := &file_proto_user_v1_user_proto_msgTypes[158] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10863,7 +11855,7 @@ func (x *PrettyDisplayIDGenerationBatch) String() string { func (*PrettyDisplayIDGenerationBatch) ProtoMessage() {} func (x *PrettyDisplayIDGenerationBatch) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[147] + mi := &file_proto_user_v1_user_proto_msgTypes[158] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10876,7 +11868,7 @@ func (x *PrettyDisplayIDGenerationBatch) ProtoReflect() protoreflect.Message { // Deprecated: Use PrettyDisplayIDGenerationBatch.ProtoReflect.Descriptor instead. func (*PrettyDisplayIDGenerationBatch) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{147} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{158} } func (x *PrettyDisplayIDGenerationBatch) GetAppCode() string { @@ -10983,7 +11975,7 @@ type ListAvailablePrettyDisplayIDsRequest struct { func (x *ListAvailablePrettyDisplayIDsRequest) Reset() { *x = ListAvailablePrettyDisplayIDsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[148] + mi := &file_proto_user_v1_user_proto_msgTypes[159] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10995,7 +11987,7 @@ func (x *ListAvailablePrettyDisplayIDsRequest) String() string { func (*ListAvailablePrettyDisplayIDsRequest) ProtoMessage() {} func (x *ListAvailablePrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[148] + mi := &file_proto_user_v1_user_proto_msgTypes[159] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11008,7 +12000,7 @@ func (x *ListAvailablePrettyDisplayIDsRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use ListAvailablePrettyDisplayIDsRequest.ProtoReflect.Descriptor instead. func (*ListAvailablePrettyDisplayIDsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{148} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{159} } func (x *ListAvailablePrettyDisplayIDsRequest) GetMeta() *RequestMeta { @@ -11050,7 +12042,7 @@ type ListAvailablePrettyDisplayIDsResponse struct { func (x *ListAvailablePrettyDisplayIDsResponse) Reset() { *x = ListAvailablePrettyDisplayIDsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[149] + mi := &file_proto_user_v1_user_proto_msgTypes[160] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11062,7 +12054,7 @@ func (x *ListAvailablePrettyDisplayIDsResponse) String() string { func (*ListAvailablePrettyDisplayIDsResponse) ProtoMessage() {} func (x *ListAvailablePrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[149] + mi := &file_proto_user_v1_user_proto_msgTypes[160] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11075,7 +12067,7 @@ func (x *ListAvailablePrettyDisplayIDsResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use ListAvailablePrettyDisplayIDsResponse.ProtoReflect.Descriptor instead. func (*ListAvailablePrettyDisplayIDsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{149} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{160} } func (x *ListAvailablePrettyDisplayIDsResponse) GetItems() []*PrettyDisplayID { @@ -11104,7 +12096,7 @@ type ApplyPrettyDisplayIDFromPoolRequest struct { func (x *ApplyPrettyDisplayIDFromPoolRequest) Reset() { *x = ApplyPrettyDisplayIDFromPoolRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[150] + mi := &file_proto_user_v1_user_proto_msgTypes[161] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11116,7 +12108,7 @@ func (x *ApplyPrettyDisplayIDFromPoolRequest) String() string { func (*ApplyPrettyDisplayIDFromPoolRequest) ProtoMessage() {} func (x *ApplyPrettyDisplayIDFromPoolRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[150] + mi := &file_proto_user_v1_user_proto_msgTypes[161] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11129,7 +12121,7 @@ func (x *ApplyPrettyDisplayIDFromPoolRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use ApplyPrettyDisplayIDFromPoolRequest.ProtoReflect.Descriptor instead. func (*ApplyPrettyDisplayIDFromPoolRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{150} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{161} } func (x *ApplyPrettyDisplayIDFromPoolRequest) GetMeta() *RequestMeta { @@ -11165,7 +12157,7 @@ type ApplyPrettyDisplayIDFromPoolResponse struct { func (x *ApplyPrettyDisplayIDFromPoolResponse) Reset() { *x = ApplyPrettyDisplayIDFromPoolResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[151] + mi := &file_proto_user_v1_user_proto_msgTypes[162] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11177,7 +12169,7 @@ func (x *ApplyPrettyDisplayIDFromPoolResponse) String() string { func (*ApplyPrettyDisplayIDFromPoolResponse) ProtoMessage() {} func (x *ApplyPrettyDisplayIDFromPoolResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[151] + mi := &file_proto_user_v1_user_proto_msgTypes[162] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11190,7 +12182,7 @@ func (x *ApplyPrettyDisplayIDFromPoolResponse) ProtoReflect() protoreflect.Messa // Deprecated: Use ApplyPrettyDisplayIDFromPoolResponse.ProtoReflect.Descriptor instead. func (*ApplyPrettyDisplayIDFromPoolResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{151} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{162} } func (x *ApplyPrettyDisplayIDFromPoolResponse) GetIdentity() *UserIdentity { @@ -11228,7 +12220,7 @@ type ListPrettyDisplayIDPoolsRequest struct { func (x *ListPrettyDisplayIDPoolsRequest) Reset() { *x = ListPrettyDisplayIDPoolsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[152] + mi := &file_proto_user_v1_user_proto_msgTypes[163] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11240,7 +12232,7 @@ func (x *ListPrettyDisplayIDPoolsRequest) String() string { func (*ListPrettyDisplayIDPoolsRequest) ProtoMessage() {} func (x *ListPrettyDisplayIDPoolsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[152] + mi := &file_proto_user_v1_user_proto_msgTypes[163] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11253,7 +12245,7 @@ func (x *ListPrettyDisplayIDPoolsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPrettyDisplayIDPoolsRequest.ProtoReflect.Descriptor instead. func (*ListPrettyDisplayIDPoolsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{152} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{163} } func (x *ListPrettyDisplayIDPoolsRequest) GetMeta() *RequestMeta { @@ -11302,7 +12294,7 @@ type ListPrettyDisplayIDPoolsResponse struct { func (x *ListPrettyDisplayIDPoolsResponse) Reset() { *x = ListPrettyDisplayIDPoolsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[153] + mi := &file_proto_user_v1_user_proto_msgTypes[164] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11314,7 +12306,7 @@ func (x *ListPrettyDisplayIDPoolsResponse) String() string { func (*ListPrettyDisplayIDPoolsResponse) ProtoMessage() {} func (x *ListPrettyDisplayIDPoolsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[153] + mi := &file_proto_user_v1_user_proto_msgTypes[164] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11327,7 +12319,7 @@ func (x *ListPrettyDisplayIDPoolsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPrettyDisplayIDPoolsResponse.ProtoReflect.Descriptor instead. func (*ListPrettyDisplayIDPoolsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{153} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{164} } func (x *ListPrettyDisplayIDPoolsResponse) GetItems() []*PrettyDisplayIDPool { @@ -11363,7 +12355,7 @@ type CreatePrettyDisplayIDPoolRequest struct { func (x *CreatePrettyDisplayIDPoolRequest) Reset() { *x = CreatePrettyDisplayIDPoolRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[154] + mi := &file_proto_user_v1_user_proto_msgTypes[165] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11375,7 +12367,7 @@ func (x *CreatePrettyDisplayIDPoolRequest) String() string { func (*CreatePrettyDisplayIDPoolRequest) ProtoMessage() {} func (x *CreatePrettyDisplayIDPoolRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[154] + mi := &file_proto_user_v1_user_proto_msgTypes[165] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11388,7 +12380,7 @@ func (x *CreatePrettyDisplayIDPoolRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreatePrettyDisplayIDPoolRequest.ProtoReflect.Descriptor instead. func (*CreatePrettyDisplayIDPoolRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{154} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{165} } func (x *CreatePrettyDisplayIDPoolRequest) GetMeta() *RequestMeta { @@ -11481,7 +12473,7 @@ type UpdatePrettyDisplayIDPoolRequest struct { func (x *UpdatePrettyDisplayIDPoolRequest) Reset() { *x = UpdatePrettyDisplayIDPoolRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[155] + mi := &file_proto_user_v1_user_proto_msgTypes[166] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11493,7 +12485,7 @@ func (x *UpdatePrettyDisplayIDPoolRequest) String() string { func (*UpdatePrettyDisplayIDPoolRequest) ProtoMessage() {} func (x *UpdatePrettyDisplayIDPoolRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[155] + mi := &file_proto_user_v1_user_proto_msgTypes[166] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11506,7 +12498,7 @@ func (x *UpdatePrettyDisplayIDPoolRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdatePrettyDisplayIDPoolRequest.ProtoReflect.Descriptor instead. func (*UpdatePrettyDisplayIDPoolRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{155} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{166} } func (x *UpdatePrettyDisplayIDPoolRequest) GetMeta() *RequestMeta { @@ -11596,7 +12588,7 @@ type PrettyDisplayIDPoolResponse struct { func (x *PrettyDisplayIDPoolResponse) Reset() { *x = PrettyDisplayIDPoolResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[156] + mi := &file_proto_user_v1_user_proto_msgTypes[167] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11608,7 +12600,7 @@ func (x *PrettyDisplayIDPoolResponse) String() string { func (*PrettyDisplayIDPoolResponse) ProtoMessage() {} func (x *PrettyDisplayIDPoolResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[156] + mi := &file_proto_user_v1_user_proto_msgTypes[167] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11621,7 +12613,7 @@ func (x *PrettyDisplayIDPoolResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PrettyDisplayIDPoolResponse.ProtoReflect.Descriptor instead. func (*PrettyDisplayIDPoolResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{156} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{167} } func (x *PrettyDisplayIDPoolResponse) GetPool() *PrettyDisplayIDPool { @@ -11646,7 +12638,7 @@ type GeneratePrettyDisplayIDsRequest struct { func (x *GeneratePrettyDisplayIDsRequest) Reset() { *x = GeneratePrettyDisplayIDsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[157] + mi := &file_proto_user_v1_user_proto_msgTypes[168] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11658,7 +12650,7 @@ func (x *GeneratePrettyDisplayIDsRequest) String() string { func (*GeneratePrettyDisplayIDsRequest) ProtoMessage() {} func (x *GeneratePrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[157] + mi := &file_proto_user_v1_user_proto_msgTypes[168] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11671,7 +12663,7 @@ func (x *GeneratePrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GeneratePrettyDisplayIDsRequest.ProtoReflect.Descriptor instead. func (*GeneratePrettyDisplayIDsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{157} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{168} } func (x *GeneratePrettyDisplayIDsRequest) GetMeta() *RequestMeta { @@ -11726,7 +12718,7 @@ type GeneratePrettyDisplayIDsResponse struct { func (x *GeneratePrettyDisplayIDsResponse) Reset() { *x = GeneratePrettyDisplayIDsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[158] + mi := &file_proto_user_v1_user_proto_msgTypes[169] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11738,7 +12730,7 @@ func (x *GeneratePrettyDisplayIDsResponse) String() string { func (*GeneratePrettyDisplayIDsResponse) ProtoMessage() {} func (x *GeneratePrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[158] + mi := &file_proto_user_v1_user_proto_msgTypes[169] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11751,7 +12743,7 @@ func (x *GeneratePrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GeneratePrettyDisplayIDsResponse.ProtoReflect.Descriptor instead. func (*GeneratePrettyDisplayIDsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{158} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{169} } func (x *GeneratePrettyDisplayIDsResponse) GetBatch() *PrettyDisplayIDGenerationBatch { @@ -11778,7 +12770,7 @@ type ListPrettyDisplayIDsRequest struct { func (x *ListPrettyDisplayIDsRequest) Reset() { *x = ListPrettyDisplayIDsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[159] + mi := &file_proto_user_v1_user_proto_msgTypes[170] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11790,7 +12782,7 @@ func (x *ListPrettyDisplayIDsRequest) String() string { func (*ListPrettyDisplayIDsRequest) ProtoMessage() {} func (x *ListPrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[159] + mi := &file_proto_user_v1_user_proto_msgTypes[170] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11803,7 +12795,7 @@ func (x *ListPrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPrettyDisplayIDsRequest.ProtoReflect.Descriptor instead. func (*ListPrettyDisplayIDsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{159} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{170} } func (x *ListPrettyDisplayIDsRequest) GetMeta() *RequestMeta { @@ -11873,7 +12865,7 @@ type ListPrettyDisplayIDsResponse struct { func (x *ListPrettyDisplayIDsResponse) Reset() { *x = ListPrettyDisplayIDsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[160] + mi := &file_proto_user_v1_user_proto_msgTypes[171] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11885,7 +12877,7 @@ func (x *ListPrettyDisplayIDsResponse) String() string { func (*ListPrettyDisplayIDsResponse) ProtoMessage() {} func (x *ListPrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[160] + mi := &file_proto_user_v1_user_proto_msgTypes[171] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11898,7 +12890,7 @@ func (x *ListPrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPrettyDisplayIDsResponse.ProtoReflect.Descriptor instead. func (*ListPrettyDisplayIDsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{160} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{171} } func (x *ListPrettyDisplayIDsResponse) GetItems() []*PrettyDisplayID { @@ -11929,7 +12921,7 @@ type SetPrettyDisplayIDStatusRequest struct { func (x *SetPrettyDisplayIDStatusRequest) Reset() { *x = SetPrettyDisplayIDStatusRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[161] + mi := &file_proto_user_v1_user_proto_msgTypes[172] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11941,7 +12933,7 @@ func (x *SetPrettyDisplayIDStatusRequest) String() string { func (*SetPrettyDisplayIDStatusRequest) ProtoMessage() {} func (x *SetPrettyDisplayIDStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[161] + mi := &file_proto_user_v1_user_proto_msgTypes[172] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11954,7 +12946,7 @@ func (x *SetPrettyDisplayIDStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetPrettyDisplayIDStatusRequest.ProtoReflect.Descriptor instead. func (*SetPrettyDisplayIDStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{161} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{172} } func (x *SetPrettyDisplayIDStatusRequest) GetMeta() *RequestMeta { @@ -12005,7 +12997,7 @@ type RecyclePrettyDisplayIDRequest struct { func (x *RecyclePrettyDisplayIDRequest) Reset() { *x = RecyclePrettyDisplayIDRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[162] + mi := &file_proto_user_v1_user_proto_msgTypes[173] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12017,7 +13009,7 @@ func (x *RecyclePrettyDisplayIDRequest) String() string { func (*RecyclePrettyDisplayIDRequest) ProtoMessage() {} func (x *RecyclePrettyDisplayIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[162] + mi := &file_proto_user_v1_user_proto_msgTypes[173] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12030,7 +13022,7 @@ func (x *RecyclePrettyDisplayIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RecyclePrettyDisplayIDRequest.ProtoReflect.Descriptor instead. func (*RecyclePrettyDisplayIDRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{162} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{173} } func (x *RecyclePrettyDisplayIDRequest) GetMeta() *RequestMeta { @@ -12071,7 +13063,7 @@ type PrettyDisplayIDResponse struct { func (x *PrettyDisplayIDResponse) Reset() { *x = PrettyDisplayIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[163] + mi := &file_proto_user_v1_user_proto_msgTypes[174] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12083,7 +13075,7 @@ func (x *PrettyDisplayIDResponse) String() string { func (*PrettyDisplayIDResponse) ProtoMessage() {} func (x *PrettyDisplayIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[163] + mi := &file_proto_user_v1_user_proto_msgTypes[174] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12096,7 +13088,7 @@ func (x *PrettyDisplayIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PrettyDisplayIDResponse.ProtoReflect.Descriptor instead. func (*PrettyDisplayIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{163} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{174} } func (x *PrettyDisplayIDResponse) GetItem() *PrettyDisplayID { @@ -12121,7 +13113,7 @@ type AdminGrantPrettyDisplayIDRequest struct { func (x *AdminGrantPrettyDisplayIDRequest) Reset() { *x = AdminGrantPrettyDisplayIDRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[164] + mi := &file_proto_user_v1_user_proto_msgTypes[175] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12133,7 +13125,7 @@ func (x *AdminGrantPrettyDisplayIDRequest) String() string { func (*AdminGrantPrettyDisplayIDRequest) ProtoMessage() {} func (x *AdminGrantPrettyDisplayIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[164] + mi := &file_proto_user_v1_user_proto_msgTypes[175] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12146,7 +13138,7 @@ func (x *AdminGrantPrettyDisplayIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminGrantPrettyDisplayIDRequest.ProtoReflect.Descriptor instead. func (*AdminGrantPrettyDisplayIDRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{164} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{175} } func (x *AdminGrantPrettyDisplayIDRequest) GetMeta() *RequestMeta { @@ -12203,7 +13195,7 @@ type AdminGrantPrettyDisplayIDResponse struct { func (x *AdminGrantPrettyDisplayIDResponse) Reset() { *x = AdminGrantPrettyDisplayIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[165] + mi := &file_proto_user_v1_user_proto_msgTypes[176] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12215,7 +13207,7 @@ func (x *AdminGrantPrettyDisplayIDResponse) String() string { func (*AdminGrantPrettyDisplayIDResponse) ProtoMessage() {} func (x *AdminGrantPrettyDisplayIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[165] + mi := &file_proto_user_v1_user_proto_msgTypes[176] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12228,7 +13220,7 @@ func (x *AdminGrantPrettyDisplayIDResponse) ProtoReflect() protoreflect.Message // Deprecated: Use AdminGrantPrettyDisplayIDResponse.ProtoReflect.Descriptor instead. func (*AdminGrantPrettyDisplayIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{165} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{176} } func (x *AdminGrantPrettyDisplayIDResponse) GetIdentity() *UserIdentity { @@ -12263,7 +13255,7 @@ type ListAppsRequest struct { func (x *ListAppsRequest) Reset() { *x = ListAppsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[166] + mi := &file_proto_user_v1_user_proto_msgTypes[177] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12275,7 +13267,7 @@ func (x *ListAppsRequest) String() string { func (*ListAppsRequest) ProtoMessage() {} func (x *ListAppsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[166] + mi := &file_proto_user_v1_user_proto_msgTypes[177] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12288,7 +13280,7 @@ func (x *ListAppsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAppsRequest.ProtoReflect.Descriptor instead. func (*ListAppsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{166} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{177} } func (x *ListAppsRequest) GetMeta() *RequestMeta { @@ -12308,7 +13300,7 @@ type ListAppsResponse struct { func (x *ListAppsResponse) Reset() { *x = ListAppsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[167] + mi := &file_proto_user_v1_user_proto_msgTypes[178] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12320,7 +13312,7 @@ func (x *ListAppsResponse) String() string { func (*ListAppsResponse) ProtoMessage() {} func (x *ListAppsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[167] + mi := &file_proto_user_v1_user_proto_msgTypes[178] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12333,7 +13325,7 @@ func (x *ListAppsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAppsResponse.ProtoReflect.Descriptor instead. func (*ListAppsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{167} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{178} } func (x *ListAppsResponse) GetApps() []*App { @@ -12348,7 +13340,7 @@ var File_proto_user_v1_user_proto protoreflect.FileDescriptor var file_proto_user_v1_user_proto_rawDesc = []byte{ 0x0a, 0x18, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x22, 0x95, 0x03, 0x0a, 0x0b, 0x52, 0x65, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x22, 0xe0, 0x04, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x61, 0x6c, 0x6c, @@ -12374,2573 +13366,2812 @@ var file_proto_user_v1_user_proto_rawDesc = []byte{ 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, - 0x65, 0x22, 0xf1, 0x01, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x70, 0x70, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x61, 0x70, 0x70, 0x49, 0x64, - 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x61, - 0x70, 0x70, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x70, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, - 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, - 0x63, 0x6b, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, - 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, - 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, - 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, - 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, - 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x9d, 0x01, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, - 0x65, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x70, - 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, - 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, - 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, - 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, - 0x41, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x61, - 0x70, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x03, 0x61, 0x70, - 0x70, 0x22, 0x8b, 0x0a, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x26, - 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x17, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, - 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2f, 0x0a, - 0x14, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x64, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x3f, - 0x0a, 0x1d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, - 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x67, - 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x65, 0x6e, - 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, - 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, - 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x18, 0x0d, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x6c, - 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, - 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, - 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, - 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, - 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x67, 0x69, - 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x72, - 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, - 0x15, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x6f, - 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x35, 0x0a, 0x17, 0x70, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, - 0x6d, 0x73, 0x18, 0x16, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, - 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2b, - 0x0a, 0x11, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x6e, 0x62, 0x6f, 0x61, - 0x72, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x69, - 0x73, 0x6f, 0x5f, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x69, 0x73, 0x6f, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x12, 0x2c, 0x0a, 0x12, - 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, - 0x64, 0x65, 0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x1a, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x45, 0x6e, 0x61, 0x62, - 0x6c, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x1b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x35, - 0x0a, 0x06, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, - 0x6e, 0x76, 0x69, 0x74, 0x65, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x06, 0x69, - 0x6e, 0x76, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, - 0x69, 0x64, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, - 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x1e, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, - 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x5f, 0x62, 0x67, 0x5f, 0x69, 0x6d, 0x67, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x42, 0x67, 0x49, 0x6d, 0x67, 0x12, 0x21, 0x0a, - 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x20, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, - 0x12, 0x3d, 0x0a, 0x1b, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x75, 0x73, 0x64, - 0x74, 0x5f, 0x74, 0x72, 0x63, 0x32, 0x30, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, - 0x21, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x55, - 0x73, 0x64, 0x74, 0x54, 0x72, 0x63, 0x32, 0x30, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, - 0xed, 0x01, 0x0a, 0x0e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, - 0x65, 0x77, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x79, 0x5f, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x79, 0x49, 0x6e, - 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x76, 0x69, - 0x74, 0x65, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0d, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, - 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x69, 0x6e, 0x76, 0x69, - 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, - 0x76, 0x61, 0x6c, 0x69, 0x64, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x3d, 0x0a, 0x1b, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, - 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x49, 0x6e, 0x76, 0x69, - 0x74, 0x65, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x22, - 0x6e, 0x0a, 0x0d, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x42, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x12, 0x14, 0x0a, 0x05, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x05, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x76, - 0x69, 0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x6e, 0x76, 0x69, 0x74, - 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0d, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, - 0xaa, 0x03, 0x0a, 0x11, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, - 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, + 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x70, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, + 0x65, 0x72, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4e, + 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, + 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x2f, 0x0a, 0x13, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x6d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, 0x18, + 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4d, 0x61, 0x6e, + 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x73, 0x5f, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, + 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x6d, 0x65, 0x69, + 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x6d, 0x65, 0x69, 0x22, 0xf1, 0x01, 0x0a, + 0x03, 0x41, 0x70, 0x70, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x70, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x61, 0x70, 0x70, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, - 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0d, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, - 0x0a, 0x0f, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x76, - 0x69, 0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1e, 0x0a, 0x0b, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x62, 0x6f, - 0x75, 0x6e, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x76, 0x61, 0x6c, 0x69, 0x64, - 0x69, 0x74, 0x79, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x47, 0x0a, 0x20, 0x61, 0x63, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1d, 0x61, 0x63, 0x63, 0x75, - 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x43, - 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x41, 0x0a, 0x1d, 0x72, 0x65, 0x71, - 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x63, - 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x1a, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, - 0x67, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0b, - 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x75, 0x0a, 0x1b, - 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0f, 0x69, - 0x6e, 0x76, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x64, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x22, 0x62, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, - 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0b, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, - 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, 0x74, 0x74, 0x72, - 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xad, 0x02, 0x0a, 0x14, 0x55, 0x73, 0x65, 0x72, - 0x4d, 0x69, 0x63, 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x61, - 0x74, 0x5f, 0x6f, 0x63, 0x63, 0x75, 0x70, 0x69, 0x65, 0x64, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x65, 0x61, 0x74, 0x4f, 0x63, 0x63, 0x75, 0x70, 0x69, 0x65, - 0x64, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x63, 0x5f, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, - 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x69, 0x63, 0x4f, - 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0f, - 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x66, 0x69, 0x72, 0x73, 0x74, 0x4d, 0x69, 0x63, 0x41, - 0x74, 0x4d, 0x73, 0x12, 0x23, 0x0a, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x5f, - 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6c, 0x61, 0x73, - 0x74, 0x4d, 0x69, 0x63, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, + 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x19, 0x0a, 0x08, - 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x69, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x4d, 0x69, 0x63, 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, - 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x22, 0x5c, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4d, 0x69, 0x63, - 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4d, 0x69, 0x63, 0x4c, 0x69, 0x66, 0x65, - 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, - 0x22, 0xb3, 0x02, 0x0a, 0x10, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x72, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, - 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x61, 0x74, - 0x63, 0x68, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x62, - 0x61, 0x74, 0x63, 0x68, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1e, 0x0a, 0x0b, 0x6c, 0x6f, 0x63, 0x6b, - 0x5f, 0x74, 0x74, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x6c, - 0x6f, 0x63, 0x6b, 0x54, 0x74, 0x6c, 0x4d, 0x73, 0x12, 0x3a, 0x0a, 0x1a, 0x70, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x6d, 0x61, 0x78, 0x5f, - 0x61, 0x67, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x16, 0x70, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x4d, 0x61, 0x78, 0x41, - 0x67, 0x65, 0x4d, 0x73, 0x12, 0x40, 0x0a, 0x1d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, - 0x6e, 0x67, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x61, - 0x67, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x19, 0x70, 0x75, 0x62, - 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, - 0x78, 0x41, 0x67, 0x65, 0x4d, 0x73, 0x22, 0xc6, 0x01, 0x0a, 0x11, 0x43, 0x72, 0x6f, 0x6e, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, - 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x0c, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x63, - 0x65, 0x73, 0x73, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x75, - 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x0c, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x23, 0x0a, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x68, 0x61, 0x73, 0x5f, 0x6d, 0x6f, 0x72, 0x65, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x61, 0x73, 0x4d, 0x6f, 0x72, 0x65, 0x22, - 0x96, 0x04, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x17, 0x0a, - 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, - 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, - 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x24, 0x0a, - 0x0e, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x63, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x49, 0x6e, - 0x53, 0x65, 0x63, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x17, 0x64, 0x65, - 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x64, 0x65, 0x66, - 0x61, 0x75, 0x6c, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x11, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x4b, 0x69, - 0x6e, 0x64, 0x12, 0x3f, 0x0a, 0x1d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, - 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x64, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, - 0x74, 0x4d, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, - 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, - 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, - 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x6e, 0x62, - 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x19, 0x0a, - 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x59, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x22, 0x3a, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, - 0xbc, 0x01, 0x0a, 0x19, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x55, 0x73, 0x65, 0x72, - 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x22, 0x0a, - 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, - 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, - 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x9f, - 0x02, 0x0a, 0x16, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x55, 0x73, 0x65, 0x72, 0x4c, - 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, - 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, - 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x31, - 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, + 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, + 0x22, 0x9d, 0x01, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x41, 0x70, 0x70, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, + 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, + 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, + 0x63, 0x6b, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, + 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, + 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, + 0x22, 0x3a, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x41, 0x70, 0x70, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x61, 0x70, 0x70, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x03, 0x61, 0x70, 0x70, 0x22, 0x8b, 0x0a, 0x0a, + 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x31, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, - 0x0a, 0x0b, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, - 0x22, 0x59, 0x0a, 0x1a, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x55, 0x73, 0x65, 0x72, - 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, - 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, - 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, - 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x22, 0xdf, 0x01, 0x0a, 0x10, - 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x76, 0x69, 0x73, - 0x69, 0x74, 0x6f, 0x72, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0d, 0x76, 0x69, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, - 0x77, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x72, 0x69, - 0x65, 0x6e, 0x64, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, - 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x4d, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x63, 0x0a, - 0x18, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, - 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x22, 0x52, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, - 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x76, - 0x69, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x22, 0x7c, 0x0a, 0x1a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x12, 0x42, 0x0a, 0x0c, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x52, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x22, 0xb0, 0x01, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, - 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x74, - 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0d, 0x76, 0x69, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x69, 0x73, 0x69, 0x74, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x76, 0x69, 0x73, 0x69, - 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x76, - 0x69, 0x73, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x56, 0x69, 0x73, 0x69, 0x74, 0x65, 0x64, 0x41, - 0x74, 0x4d, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, - 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, - 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x70, 0x0a, 0x1b, - 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, - 0x6f, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x07, 0x72, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, - 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x97, - 0x01, 0x0a, 0x11, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, - 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, - 0x0a, 0x10, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, - 0x65, 0x65, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x7a, 0x0a, 0x12, 0x46, 0x6f, 0x6c, 0x6c, - 0x6f, 0x77, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, - 0x0a, 0x09, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x09, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x12, 0x46, 0x0a, 0x0e, - 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x0d, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x53, - 0x74, 0x61, 0x74, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x13, 0x55, 0x6e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, - 0x77, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x28, 0x0a, 0x10, - 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, - 0x65, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x65, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x22, 0x7c, 0x0a, 0x14, 0x55, 0x6e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x73, 0x65, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x66, 0x6f, 0x6c, 0x6c, - 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x66, 0x6f, 0x6c, - 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x12, 0x46, 0x0a, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, - 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, - 0x0d, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x73, 0x22, 0x88, - 0x01, 0x0a, 0x0c, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, - 0x28, 0x0a, 0x10, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, - 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x6f, 0x6c, - 0x6c, 0x6f, 0x77, 0x65, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x65, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, - 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x66, 0x6f, 0x6c, - 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xe7, 0x01, 0x0a, 0x14, 0x4c, 0x69, - 0x73, 0x74, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, - 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, - 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2f, 0x0a, 0x14, - 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, - 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x63, 0x75, 0x72, 0x73, - 0x6f, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, - 0x0e, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x22, 0x64, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, - 0x77, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x07, - 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, - 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, - 0x72, 0x64, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x96, 0x01, 0x0a, 0x12, 0x41, 0x70, - 0x70, 0x6c, 0x79, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x22, 0x82, 0x01, 0x0a, 0x13, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, 0x72, 0x69, 0x65, - 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0b, 0x61, 0x70, - 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, - 0x0a, 0x0f, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, - 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x73, 0x22, 0xa6, 0x01, 0x0a, 0x1e, 0x41, 0x63, 0x63, 0x65, - 0x70, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x63, - 0x63, 0x65, 0x70, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x72, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, - 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x22, 0x56, 0x0a, 0x1f, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, - 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x52, 0x06, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x22, 0x84, 0x01, 0x0a, 0x13, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x72, 0x69, - 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0c, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, - 0x30, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x64, 0x22, 0x73, 0x0a, 0x0c, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x72, - 0x69, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0c, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, - 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, - 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xe5, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x46, - 0x72, 0x69, 0x65, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, - 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, - 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, - 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, - 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2f, 0x0a, 0x14, 0x63, 0x75, 0x72, 0x73, 0x6f, - 0x72, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x75, 0x72, 0x73, - 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x62, - 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x14, 0x0a, 0x05, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x22, 0xc5, 0x01, 0x0a, 0x11, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x70, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x72, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xb7, 0x01, 0x0a, 0x1d, 0x4c, - 0x69, 0x73, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, - 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, - 0x53, 0x69, 0x7a, 0x65, 0x22, 0x7c, 0x0a, 0x1e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x72, 0x69, 0x65, - 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0c, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x72, 0x69, - 0x65, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, - 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x22, 0x84, 0x01, 0x0a, 0x0d, 0x43, 0x50, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, 0x0a, - 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x22, 0x84, 0x02, 0x0a, 0x0e, 0x43, 0x50, - 0x47, 0x69, 0x66, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x17, 0x0a, 0x07, - 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, - 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x63, 0x6f, 0x6e, 0x5f, - 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x67, 0x69, 0x66, 0x74, 0x49, - 0x63, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x61, - 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x66, 0x74, 0x41, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, - 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, - 0x22, 0xe7, 0x03, 0x0a, 0x0d, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x70, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6c, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x3a, 0x0a, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x55, 0x73, 0x65, 0x72, - 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x65, 0x72, 0x12, 0x34, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x50, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, - 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, - 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x6f, 0x6f, 0x6d, 0x52, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x04, 0x67, 0x69, 0x66, 0x74, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x47, 0x69, 0x66, 0x74, 0x53, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x67, 0x69, 0x66, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, - 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, - 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, - 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x64, 0x65, 0x63, 0x69, 0x64, 0x65, - 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x64, - 0x65, 0x63, 0x69, 0x64, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xad, 0x05, 0x0a, 0x0e, 0x43, - 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x12, 0x27, 0x0a, - 0x0f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x68, 0x69, 0x70, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, - 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x2c, 0x0a, 0x02, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x50, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x02, 0x6d, - 0x65, 0x12, 0x36, 0x0a, 0x07, 0x70, 0x61, 0x72, 0x74, 0x6e, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x50, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x52, 0x07, 0x70, 0x61, 0x72, 0x74, 0x6e, 0x65, 0x72, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x74, - 0x69, 0x6d, 0x61, 0x63, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x6f, - 0x72, 0x6d, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x36, 0x0a, 0x17, - 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x74, 0x68, - 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x63, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x68, 0x72, 0x65, 0x73, - 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x6c, 0x65, 0x76, - 0x65, 0x6c, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x0c, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x12, 0x6e, 0x65, 0x78, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x68, 0x72, - 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x31, 0x0a, 0x15, 0x6e, 0x65, 0x65, 0x64, 0x65, 0x64, - 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, - 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x46, 0x6f, 0x72, - 0x4e, 0x65, 0x78, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x34, 0x0a, 0x16, 0x6c, 0x65, 0x76, - 0x65, 0x6c, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x63, - 0x65, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x6c, 0x65, 0x76, 0x65, 0x6c, - 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, - 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x0f, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x2c, 0x0a, 0x12, - 0x62, 0x72, 0x65, 0x61, 0x6b, 0x75, 0x70, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x69, - 0x6e, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x75, - 0x70, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x65, 0x6e, - 0x64, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x09, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, - 0x52, 0x08, 0x65, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x22, 0xf9, 0x01, 0x0a, 0x15, 0x43, - 0x50, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1b, - 0x0a, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, - 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, - 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, - 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6a, 0x73, - 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x4a, 0x73, 0x6f, 0x6e, 0x22, 0xd9, 0x01, 0x0a, 0x19, 0x43, 0x50, 0x49, 0x6e, 0x74, - 0x69, 0x6d, 0x61, 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, - 0x55, 0x73, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, 0x0a, - 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x47, 0x0a, 0x0c, 0x61, 0x76, 0x61, - 0x74, 0x61, 0x72, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x50, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x6e, 0x61, - 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x0b, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x46, 0x72, 0x61, - 0x6d, 0x65, 0x22, 0x82, 0x03, 0x0a, 0x19, 0x43, 0x50, 0x49, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, - 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, - 0x12, 0x12, 0x0a, 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, - 0x72, 0x61, 0x6e, 0x6b, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x68, 0x69, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, - 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x49, 0x64, 0x12, 0x23, 0x0a, - 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x5f, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x74, 0x69, - 0x6d, 0x61, 0x63, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, - 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, - 0x3f, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x50, 0x49, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x62, 0x6f, 0x61, 0x72, 0x64, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x41, - 0x12, 0x3f, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x62, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x50, 0x49, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, - 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, - 0x42, 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, - 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x41, + 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x35, 0x0a, 0x17, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x14, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x64, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x5f, 0x6b, 0x69, 0x6e, 0x64, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x3f, 0x0a, 0x1d, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x5f, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x18, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x45, + 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, + 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, + 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x18, + 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, + 0x61, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, + 0x12, 0x14, 0x0a, 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, + 0x67, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, + 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, + 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, + 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, + 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x11, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x12, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, + 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x15, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x10, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x64, 0x12, 0x35, 0x0a, 0x17, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x6f, + 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x16, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x14, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x6e, 0x62, + 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x17, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x6f, 0x5f, 0x6e, 0x75, + 0x6d, 0x65, 0x72, 0x69, 0x63, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x73, 0x6f, + 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x68, 0x6f, 0x6e, 0x65, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x19, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, + 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, + 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x19, + 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x35, 0x0a, 0x06, 0x69, 0x6e, 0x76, + 0x69, 0x74, 0x65, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, + 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x06, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, + 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x1d, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x49, 0x64, 0x12, 0x33, 0x0a, + 0x16, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, + 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x62, 0x67, + 0x5f, 0x69, 0x6d, 0x67, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x42, 0x67, 0x49, 0x6d, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x63, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x20, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x1b, 0x77, + 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x75, 0x73, 0x64, 0x74, 0x5f, 0x74, 0x72, 0x63, + 0x32, 0x30, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x21, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x18, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x55, 0x73, 0x64, 0x74, 0x54, 0x72, + 0x63, 0x32, 0x30, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0xed, 0x01, 0x0a, 0x0e, 0x49, + 0x6e, 0x76, 0x69, 0x74, 0x65, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x12, 0x24, 0x0a, + 0x0e, 0x6d, 0x79, 0x5f, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x79, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, + 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x6e, 0x76, + 0x69, 0x74, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, + 0x76, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, + 0x12, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x76, 0x61, 0x6c, 0x69, 0x64, + 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3d, 0x0a, 0x1b, 0x76, + 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, 0x74, 0x68, 0x72, 0x65, + 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x18, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x54, 0x68, 0x72, + 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x22, 0x6e, 0x0a, 0x0d, 0x49, 0x6e, + 0x76, 0x69, 0x74, 0x65, 0x42, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x62, 0x6f, 0x75, 0x6e, + 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, + 0x64, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x76, + 0x69, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xaa, 0x03, 0x0a, 0x11, 0x49, + 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x05, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x76, 0x69, + 0x74, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x6e, 0x76, + 0x69, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, + 0x64, 0x65, 0x12, 0x1e, 0x0a, 0x0b, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, + 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x41, 0x74, + 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x69, 0x74, 0x79, 0x5f, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x76, 0x61, 0x6c, + 0x69, 0x64, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x47, 0x0a, 0x20, 0x61, + 0x63, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x63, 0x68, 0x61, + 0x72, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1d, 0x61, 0x63, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, + 0x65, 0x64, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x41, 0x0a, 0x1d, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, + 0x5f, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1a, 0x72, 0x65, 0x71, + 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x43, 0x6f, 0x69, + 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x76, 0x61, 0x6c, 0x69, 0x64, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x75, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x49, 0x6e, + 0x76, 0x69, 0x74, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, + 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, + 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0d, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x62, + 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, + 0x0a, 0x0b, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, + 0x6f, 0x6e, 0x22, 0xad, 0x02, 0x0a, 0x14, 0x55, 0x73, 0x65, 0x72, 0x4d, 0x69, 0x63, 0x4c, 0x69, + 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6f, 0x63, 0x63, + 0x75, 0x70, 0x69, 0x65, 0x64, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, + 0x73, 0x65, 0x61, 0x74, 0x4f, 0x63, 0x63, 0x75, 0x70, 0x69, 0x65, 0x64, 0x4d, 0x73, 0x12, 0x22, + 0x0a, 0x0d, 0x6d, 0x69, 0x63, 0x5f, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x6d, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x69, 0x63, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, + 0x4d, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0f, 0x66, 0x69, 0x72, 0x73, 0x74, + 0x5f, 0x6d, 0x69, 0x63, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0c, 0x66, 0x69, 0x72, 0x73, 0x74, 0x4d, 0x69, 0x63, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x23, + 0x0a, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x69, 0x63, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, - 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xab, 0x03, 0x0a, 0x11, 0x43, 0x50, 0x57, 0x65, - 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x12, 0x0a, - 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x72, 0x61, 0x6e, - 0x6b, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, - 0x70, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, - 0x73, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x49, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, - 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x55, 0x73, 0x65, 0x72, 0x52, - 0x05, 0x75, 0x73, 0x65, 0x72, 0x41, 0x12, 0x3f, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x62, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x49, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, - 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x55, 0x73, 0x65, 0x72, - 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x42, 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x6f, 0x72, 0x6d, 0x65, - 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, - 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2b, 0x0a, - 0x12, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x66, 0x69, 0x72, 0x73, 0x74, - 0x53, 0x63, 0x6f, 0x72, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x29, 0x0a, 0x11, 0x6c, 0x61, - 0x73, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x63, 0x6f, 0x72, 0x65, - 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xbd, 0x01, 0x0a, 0x1e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, - 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, - 0x08, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4d, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x65, 0x6e, 0x64, 0x5f, - 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x65, 0x6e, 0x64, 0x4d, 0x73, 0x12, - 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, - 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x83, 0x01, 0x0a, 0x1f, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, - 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x65, 0x6e, 0x74, - 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x57, 0x65, 0x65, - 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, - 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, - 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xcb, 0x01, 0x0a, 0x19, - 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, - 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, - 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x9a, 0x01, 0x0a, 0x1a, 0x4c, 0x69, - 0x73, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0c, 0x61, 0x70, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x61, 0x70, - 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, - 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x1a, 0x41, 0x63, 0x63, 0x65, 0x70, - 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, - 0x0a, 0x0e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xa0, 0x01, 0x0a, 0x1b, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, - 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x41, 0x70, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x68, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x52, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x22, 0xa4, 0x01, 0x0a, 0x1a, 0x52, 0x65, 0x6a, - 0x65, 0x63, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, - 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, - 0x5d, 0x0a, 0x1b, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, - 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xbb, - 0x01, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, - 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, - 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, - 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, - 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, - 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x9a, 0x01, 0x0a, - 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x68, 0x69, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0d, - 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, - 0x69, 0x70, 0x52, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, - 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x52, 0x1a, 0x6d, - 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x62, 0x79, 0x5f, 0x72, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0xa8, 0x01, 0x0a, 0x20, 0x4c, 0x69, - 0x73, 0x74, 0x43, 0x50, 0x49, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, - 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, - 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x23, - 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, - 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, - 0x53, 0x69, 0x7a, 0x65, 0x22, 0xd0, 0x01, 0x0a, 0x21, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x49, - 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, - 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x05, 0x69, 0x74, - 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x49, 0x6e, 0x74, 0x69, - 0x6d, 0x61, 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x49, - 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, - 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, - 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, - 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xb4, 0x01, 0x0a, 0x21, 0x50, 0x72, 0x65, 0x70, - 0x61, 0x72, 0x65, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, - 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, - 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x49, 0x64, 0x12, - 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x22, 0xae, - 0x02, 0x0a, 0x22, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, - 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x68, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x52, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x2c, 0x0a, 0x12, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x75, 0x70, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x5f, - 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x62, 0x72, 0x65, - 0x61, 0x6b, 0x75, 0x70, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x32, 0x0a, - 0x15, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, - 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, - 0x6f, 0x69, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x22, - 0xc0, 0x02, 0x0a, 0x21, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x42, 0x72, 0x65, 0x61, 0x6b, - 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x27, - 0x0a, 0x0f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x5f, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x68, 0x69, 0x70, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, 0x61, - 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x70, 0x61, - 0x69, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x70, 0x61, 0x69, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x62, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x10, 0x63, 0x6f, 0x69, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, - 0x65, 0x72, 0x22, 0xf7, 0x01, 0x0a, 0x22, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x42, 0x72, - 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, - 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x72, 0x65, 0x6c, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x0c, - 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x12, 0x2c, 0x0a, 0x12, - 0x62, 0x72, 0x65, 0x61, 0x6b, 0x75, 0x70, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x69, - 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x75, - 0x70, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2c, - 0x0a, 0x12, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, - 0x66, 0x74, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x69, 0x6e, - 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x22, 0xcb, 0x01, 0x0a, - 0x20, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, - 0x70, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x5a, 0x0a, 0x21, 0x43, 0x61, - 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x99, 0x05, 0x0a, 0x0f, 0x52, 0x6f, 0x6f, 0x6d, 0x47, - 0x69, 0x66, 0x74, 0x43, 0x50, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, - 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, - 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x21, - 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x63, 0x63, 0x75, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6f, 0x63, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, - 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, - 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, - 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x09, 0x67, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, - 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, - 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, - 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, - 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x67, 0x69, 0x66, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x70, 0x5f, - 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0f, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x70, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, 0x72, - 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x67, 0x69, 0x66, 0x74, 0x49, 0x63, 0x6f, - 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x61, 0x6e, 0x69, - 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x10, 0x67, 0x69, 0x66, 0x74, 0x41, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, - 0x72, 0x6c, 0x22, 0x85, 0x01, 0x0a, 0x1d, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x6f, - 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x43, 0x50, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, + 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, + 0x64, 0x65, 0x22, 0x69, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4d, 0x69, 0x63, + 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x12, 0x34, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x43, 0x50, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xbf, 0x01, 0x0a, 0x1e, 0x43, - 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x43, 0x50, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, - 0x08, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x08, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x12, 0x3e, 0x0a, 0x0b, 0x61, 0x70, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, 0x70, - 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0c, 0x72, 0x65, 0x6c, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x0c, - 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x22, 0xba, 0x02, 0x0a, - 0x0a, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x72, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, - 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1d, - 0x0a, 0x0a, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x73, 0x18, 0x08, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x55, 0x72, 0x6c, 0x73, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xf9, 0x01, 0x0a, 0x13, 0x53, 0x75, - 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1f, 0x0a, - 0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, - 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, - 0x75, 0x72, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6d, 0x61, 0x67, - 0x65, 0x55, 0x72, 0x6c, 0x73, 0x22, 0x49, 0x0a, 0x14, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, - 0x06, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, - 0x65, 0x72, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x22, 0x61, 0x0a, 0x14, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x73, 0x22, 0xad, 0x01, 0x0a, 0x15, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, - 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, - 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x75, - 0x73, 0x65, 0x72, 0x73, 0x1a, 0x4d, 0x0a, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x22, 0x84, 0x01, 0x0a, 0x0d, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x73, 0x69, - 0x63, 0x55, 0x73, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, - 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, - 0x61, 0x74, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, - 0x61, 0x72, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x6a, 0x0a, 0x1d, 0x42, 0x61, - 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x73, 0x69, 0x63, 0x55, - 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x07, 0x75, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, 0xc8, 0x01, 0x0a, 0x1e, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x73, 0x69, 0x63, 0x55, 0x73, 0x65, 0x72, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x05, 0x75, 0x73, 0x65, - 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, - 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x73, 0x69, 0x63, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x1a, 0x56, 0x0a, 0x0a, 0x55, 0x73, 0x65, - 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x73, - 0x69, 0x63, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0xe1, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x63, - 0x75, 0x72, 0x73, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1b, - 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x73, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x07, - 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x6e, 0x65, 0x78, 0x74, 0x5f, - 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x6e, 0x65, 0x78, 0x74, 0x43, 0x75, 0x72, 0x73, 0x6f, 0x72, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x22, 0x86, 0x02, 0x0a, 0x18, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, - 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x1f, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, - 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x01, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x88, 0x01, 0x01, 0x12, 0x19, - 0x0a, 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, - 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x67, 0x65, 0x6e, - 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x06, 0x67, 0x65, 0x6e, - 0x64, 0x65, 0x72, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, - 0x61, 0x6d, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x42, 0x08, - 0x0a, 0x06, 0x5f, 0x62, 0x69, 0x72, 0x74, 0x68, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x67, 0x65, 0x6e, - 0x64, 0x65, 0x72, 0x22, 0x44, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, - 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x27, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x93, 0x01, 0x0a, 0x22, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x42, - 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x5c, 0x0a, + 0x1f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4d, 0x69, 0x63, 0x4c, 0x69, 0x66, 0x65, 0x74, + 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x39, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x73, 0x65, 0x72, 0x4d, 0x69, 0x63, 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0xe9, 0x02, 0x0a, 0x10, + 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x62, 0x67, 0x5f, 0x69, 0x6d, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x42, 0x67, 0x49, 0x6d, 0x67, 0x22, - 0x4e, 0x0a, 0x23, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, - 0x8a, 0x01, 0x0a, 0x1c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, - 0x6e, 0x74, 0x61, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x63, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x48, 0x0a, 0x1d, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x63, - 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, - 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, - 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x99, 0x01, 0x0a, 0x20, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x55, 0x73, 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, + 0x12, 0x15, 0x0a, 0x06, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x72, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x73, 0x69, + 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x62, 0x61, 0x74, 0x63, 0x68, 0x53, + 0x69, 0x7a, 0x65, 0x12, 0x1e, 0x0a, 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x74, 0x6c, 0x5f, + 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x74, + 0x6c, 0x4d, 0x73, 0x12, 0x3a, 0x0a, 0x1a, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, + 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x67, 0x65, 0x5f, 0x6d, + 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x16, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x4d, 0x61, 0x78, 0x41, 0x67, 0x65, 0x4d, 0x73, 0x12, + 0x40, 0x0a, 0x1d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x67, 0x65, 0x5f, 0x6d, 0x73, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x19, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, + 0x6e, 0x67, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x78, 0x41, 0x67, 0x65, 0x4d, + 0x73, 0x12, 0x34, 0x0a, 0x17, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x67, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x13, 0x6f, 0x70, 0x65, 0x6e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4d, + 0x61, 0x78, 0x41, 0x67, 0x65, 0x4d, 0x73, 0x22, 0xc6, 0x01, 0x0a, 0x11, 0x43, 0x72, 0x6f, 0x6e, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, + 0x0d, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x70, 0x72, 0x6f, + 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x73, + 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0c, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x68, 0x61, 0x73, 0x5f, 0x6d, 0x6f, 0x72, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x61, 0x73, 0x4d, 0x6f, 0x72, 0x65, + 0x22, 0x96, 0x04, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x17, + 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, + 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x24, + 0x0a, 0x0e, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x63, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x49, + 0x6e, 0x53, 0x65, 0x63, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x17, 0x64, + 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x64, 0x65, + 0x66, 0x61, 0x75, 0x6c, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x11, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x4b, + 0x69, 0x6e, 0x64, 0x12, 0x3f, 0x0a, 0x1d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x64, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, + 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, + 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x10, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x6e, + 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x19, + 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x59, 0x0a, 0x0e, 0x47, 0x65, 0x74, + 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x75, 0x73, 0x64, 0x74, 0x5f, 0x74, 0x72, 0x63, - 0x32, 0x30, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x10, 0x75, 0x73, 0x64, 0x74, 0x54, 0x72, 0x63, 0x32, 0x30, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x22, 0x4c, 0x0a, 0x21, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, - 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, + 0x65, 0x72, 0x49, 0x64, 0x22, 0x3a, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, - 0x22, 0x7d, 0x0a, 0x18, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x22, - 0xcb, 0x01, 0x0a, 0x1d, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, - 0x73, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xed, 0x01, - 0x0a, 0x19, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x75, - 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, - 0x75, 0x73, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x19, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x6e, 0x65, 0x78, 0x74, 0x43, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, - 0x0a, 0x0d, 0x6f, 0x6c, 0x64, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x6c, 0x64, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6e, 0x65, 0x77, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6e, 0x65, 0x77, 0x52, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, - 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x22, 0x86, 0x02, - 0x0a, 0x14, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, - 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, - 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x22, 0xcd, 0x03, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x27, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x32, 0x0a, 0x15, 0x72, 0x65, 0x76, - 0x6f, 0x6b, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, - 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, - 0x09, 0x69, 0x6d, 0x5f, 0x6b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x08, 0x69, 0x6d, 0x4b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x69, 0x6d, - 0x5f, 0x6b, 0x69, 0x63, 0x6b, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x69, 0x6d, 0x4b, 0x69, 0x63, 0x6b, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x21, - 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x65, 0x76, 0x69, 0x63, 0x74, 0x65, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x45, 0x76, 0x69, 0x63, 0x74, 0x65, - 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x74, - 0x63, 0x5f, 0x6b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, - 0x72, 0x74, 0x63, 0x4b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x6f, 0x6f, - 0x6d, 0x5f, 0x65, 0x76, 0x69, 0x63, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x6f, 0x6f, 0x6d, 0x45, 0x76, 0x69, 0x63, 0x74, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x74, 0x63, 0x5f, 0x6b, 0x69, 0x63, 0x6b, 0x5f, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x74, 0x63, - 0x4b, 0x69, 0x63, 0x6b, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x30, 0x0a, 0x14, 0x61, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, - 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x12, 0x39, 0x0a, 0x19, 0x61, - 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x72, 0x65, 0x76, 0x6f, - 0x6b, 0x65, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, - 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x76, 0x6f, 0x6b, - 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xba, 0x03, 0x0a, 0x10, 0x4d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x19, 0x0a, 0x08, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, - 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x64, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x76, 0x61, - 0x74, 0x61, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x28, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x75, 0x6e, 0x74, - 0x69, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x62, 0x6c, 0x6f, - 0x63, 0x6b, 0x65, 0x64, 0x55, 0x6e, 0x74, 0x69, 0x6c, 0x4d, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0d, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, + 0x22, 0xbc, 0x01, 0x0a, 0x19, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x55, 0x73, 0x65, + 0x72, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, + 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x22, + 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, + 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, + 0x72, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, + 0x9f, 0x02, 0x0a, 0x16, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x55, 0x73, 0x65, 0x72, + 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, + 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, + 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, + 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, + 0x31, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, + 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, + 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, + 0x65, 0x22, 0x59, 0x0a, 0x1a, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x55, 0x73, 0x65, + 0x72, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3b, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, + 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, + 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x22, 0xdf, 0x01, 0x0a, + 0x10, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x76, 0x69, + 0x73, 0x69, 0x74, 0x6f, 0x72, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0d, 0x76, 0x69, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x73, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x5f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, + 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x72, + 0x69, 0x65, 0x6e, 0x64, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, - 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, - 0x74, 0x4d, 0x73, 0x22, 0xfe, 0x01, 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x4d, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x63, + 0x0a, 0x18, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, + 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x22, 0x52, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x50, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, + 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x75, 0x6e, - 0x74, 0x69, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x6e, 0x74, 0x69, 0x6c, 0x4d, 0x73, 0x12, 0x16, 0x0a, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x95, 0x01, 0x0a, 0x1e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, - 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x3c, - 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xab, 0x01, 0x0a, - 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, - 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x74, 0x6f, 0x72, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, + 0x76, 0x69, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, + 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x22, 0x7c, 0x0a, 0x1a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x12, 0x42, 0x0a, + 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x73, 0x52, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x22, 0xb0, 0x01, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, + 0x69, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x76, 0x69, 0x73, 0x69, + 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0d, 0x76, 0x69, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x69, 0x73, 0x69, 0x74, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x76, 0x69, 0x73, + 0x69, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, + 0x76, 0x69, 0x73, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x56, 0x69, 0x73, 0x69, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x4d, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, + 0x70, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, + 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x70, 0x0a, + 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, + 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x07, + 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, + 0x97, 0x01, 0x0a, 0x11, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, + 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x28, 0x0a, 0x10, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, + 0x77, 0x65, 0x65, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x7a, 0x0a, 0x12, 0x46, 0x6f, 0x6c, + 0x6c, 0x6f, 0x77, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x1c, 0x0a, 0x09, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x09, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x12, 0x46, 0x0a, + 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x0d, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, + 0x53, 0x74, 0x61, 0x74, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x13, 0x55, 0x6e, 0x66, 0x6f, 0x6c, 0x6c, + 0x6f, 0x77, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x26, 0x0a, - 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, - 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x58, 0x0a, 0x1d, 0x4c, 0x69, - 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x06, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x73, 0x22, 0xa6, 0x01, 0x0a, 0x19, 0x55, 0x6e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x91, 0x01, - 0x0a, 0x1a, 0x55, 0x6e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, - 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x05, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x05, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x22, 0xeb, 0x01, 0x0a, 0x19, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x6e, - 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, - 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x18, 0x0a, 0x07, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1f, - 0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, - 0xbc, 0x02, 0x0a, 0x1a, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x6e, 0x62, 0x6f, - 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, - 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, - 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, - 0x65, 0x74, 0x65, 0x64, 0x12, 0x35, 0x0a, 0x17, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, - 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x6f, - 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x6f, - 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, - 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2e, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, 0x0a, 0x06, 0x69, 0x6e, 0x76, 0x69, - 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x42, - 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x22, 0x87, - 0x01, 0x0a, 0x1b, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, - 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, + 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x28, 0x0a, + 0x10, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, + 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, + 0x77, 0x65, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x65, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x22, 0x7c, 0x0a, 0x14, 0x55, 0x6e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x66, 0x6f, 0x6c, + 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x66, 0x6f, + 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x12, 0x46, 0x0a, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, + 0x77, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, + 0x52, 0x0d, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x73, 0x22, + 0x88, 0x01, 0x0a, 0x0c, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x12, 0x28, 0x0a, 0x10, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, + 0x6f, 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x6f, + 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x65, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x66, 0x6f, + 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xe7, 0x01, 0x0a, 0x14, 0x4c, + 0x69, 0x73, 0x74, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, + 0x70, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, + 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2f, 0x0a, + 0x14, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x63, 0x75, 0x72, + 0x73, 0x6f, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, + 0x0a, 0x0e, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x22, 0x64, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6f, 0x6c, 0x6c, + 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, + 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, + 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x72, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x96, 0x01, 0x0a, 0x12, 0x41, + 0x70, 0x70, 0x6c, 0x79, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, + 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x22, 0x82, 0x01, 0x0a, 0x13, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, 0x72, 0x69, + 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0b, 0x61, + 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x27, 0x0a, 0x0f, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x66, 0x72, 0x69, 0x65, 0x6e, + 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, + 0x79, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x73, 0x22, 0xa6, 0x01, 0x0a, 0x1e, 0x41, 0x63, 0x63, + 0x65, 0x70, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x28, 0x0a, 0x10, 0x61, + 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x72, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x22, 0x56, 0x0a, 0x1f, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, + 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x52, 0x06, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x22, 0x84, 0x01, 0x0a, 0x13, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x72, + 0x69, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0c, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x22, 0x30, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x64, 0x22, 0x73, 0x0a, 0x0c, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x66, + 0x72, 0x69, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0c, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x66, 0x72, 0x69, 0x65, 0x6e, + 0x64, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xe5, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, + 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, - 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, - 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x4d, 0x0a, 0x1c, 0x53, 0x65, 0x61, 0x72, - 0x63, 0x68, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x07, 0x69, 0x6e, 0x76, 0x69, - 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, - 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x22, 0x85, 0x01, 0x0a, 0x19, 0x42, 0x69, 0x6e, 0x64, - 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, - 0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, - 0x81, 0x01, 0x0a, 0x1a, 0x42, 0x69, 0x6e, 0x64, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x65, - 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, - 0x0a, 0x06, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, - 0x6e, 0x76, 0x69, 0x74, 0x65, 0x42, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x69, 0x6e, - 0x76, 0x69, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x07, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, 0x69, 0x6e, 0x76, 0x69, - 0x74, 0x65, 0x72, 0x22, 0xac, 0x02, 0x0a, 0x14, 0x42, 0x69, 0x6e, 0x64, 0x50, 0x75, 0x73, 0x68, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x1a, 0x0a, - 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x70, - 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x61, 0x70, 0x70, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, - 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, - 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, - 0x6e, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, - 0x6e, 0x65, 0x22, 0x51, 0x0a, 0x15, 0x42, 0x69, 0x6e, 0x64, 0x50, 0x75, 0x73, 0x68, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x62, - 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x62, 0x6f, 0x75, 0x6e, - 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, - 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x9d, 0x01, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x50, 0x75, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x73, 0x68, 0x5f, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x73, 0x68, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x57, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, - 0x75, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xbe, - 0x03, 0x0a, 0x07, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x30, 0x0a, 0x14, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, - 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x6f, 0x5f, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x33, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x73, - 0x6f, 0x41, 0x6c, 0x70, 0x68, 0x61, 0x33, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x6f, 0x5f, 0x6e, - 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x73, - 0x6f, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x68, 0x6f, 0x6e, - 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x66, 0x6c, 0x61, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, - 0x92, 0x02, 0x0a, 0x06, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x67, 0x69, 0x6f, - 0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, - 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, - 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, - 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, - 0x43, 0x6f, 0x64, 0x65, 0x22, 0x71, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x07, - 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, - 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, - 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x4d, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x34, 0x0a, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0x85, 0x03, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, - 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x21, - 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x64, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x12, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, - 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x69, 0x73, 0x6f, 0x5f, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x33, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x69, 0x73, 0x6f, 0x41, 0x6c, 0x70, 0x68, 0x61, 0x33, 0x12, 0x1f, 0x0a, 0x0b, 0x69, - 0x73, 0x6f, 0x5f, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x69, 0x73, 0x6f, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x12, 0x2c, 0x0a, 0x12, - 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, - 0x64, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, - 0x61, 0x67, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x22, 0x43, - 0x0a, 0x0f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x30, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x79, 0x22, 0x52, 0x0a, 0x20, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, - 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x59, 0x0a, 0x21, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x09, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, - 0x65, 0x73, 0x22, 0x7a, 0x0a, 0x17, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x42, - 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x21, 0x0a, - 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, - 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x56, - 0x0a, 0x24, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x42, - 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, - 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x91, 0x01, 0x0a, 0x25, 0x4c, 0x69, 0x73, 0x74, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x44, 0x0a, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x5c, 0x0a, 0x12, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x46, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x2f, 0x0a, 0x07, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, - 0x22, 0x5f, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, - 0x64, 0x22, 0xe0, 0x01, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, - 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, - 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x22, 0xb4, 0x01, 0x0a, 0x1d, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, - 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, - 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, - 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x3f, 0x0a, 0x0e, 0x52, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, - 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x22, 0xfd, 0x02, 0x0a, - 0x0c, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x17, 0x0a, - 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, - 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, - 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, - 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2f, 0x0a, - 0x14, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x64, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x3f, - 0x0a, 0x1d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, - 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, - 0x65, 0x74, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, - 0x72, 0x65, 0x74, 0x74, 0x79, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x70, 0x72, 0x65, 0x74, 0x74, - 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x61, 0x0a, 0x16, - 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, - 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, - 0x52, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x69, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x22, 0x75, 0x0a, 0x1b, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x44, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x57, 0x0a, 0x1c, 0x52, 0x65, - 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x69, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x22, 0xd6, 0x01, 0x0a, 0x1a, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x44, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x13, 0x6e, - 0x65, 0x77, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6e, 0x65, 0x77, 0x44, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, - 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x56, 0x0a, 0x1b, - 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x69, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x22, 0xf9, 0x01, 0x0a, 0x1f, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, - 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x33, 0x0a, 0x16, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x13, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, - 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0f, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x4d, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, - 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, - 0x22, 0x76, 0x0a, 0x20, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x19, 0x0a, - 0x08, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x49, 0x64, 0x22, 0x86, 0x01, 0x0a, 0x20, 0x45, 0x78, 0x70, - 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, + 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, + 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, + 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2f, 0x0a, 0x14, 0x63, 0x75, 0x72, 0x73, + 0x6f, 0x72, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x75, 0x72, + 0x73, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, + 0x62, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x14, 0x0a, + 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x22, 0xc5, 0x01, 0x0a, 0x11, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x70, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x72, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xb7, 0x01, 0x0a, 0x1d, + 0x4c, 0x69, 0x73, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, - 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x49, - 0x64, 0x22, 0x5c, 0x0a, 0x21, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, - 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, - 0xdc, 0x03, 0x0a, 0x13, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, - 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, - 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, + 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x7c, 0x0a, 0x1e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x72, 0x69, + 0x65, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0c, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x72, + 0x69, 0x65, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x0c, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, + 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x22, 0x84, 0x01, 0x0a, 0x0d, 0x43, 0x50, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, + 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x22, 0x84, 0x02, 0x0a, 0x0e, 0x43, + 0x50, 0x47, 0x69, 0x66, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x17, 0x0a, + 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x63, 0x6f, 0x6e, + 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x67, 0x69, 0x66, 0x74, + 0x49, 0x63, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x66, 0x74, 0x5f, + 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x66, 0x74, 0x41, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, + 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, + 0x64, 0x22, 0xe7, 0x03, 0x0a, 0x0d, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x70, 0x70, + 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x3a, 0x0a, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x55, 0x73, 0x65, + 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x65, 0x72, 0x12, 0x34, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, + 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, + 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x6f, 0x6f, 0x6d, + 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x04, 0x67, 0x69, 0x66, 0x74, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x47, 0x69, 0x66, 0x74, 0x53, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x67, 0x69, 0x66, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, + 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, + 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x64, 0x65, 0x63, 0x69, 0x64, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x64, 0x65, 0x63, 0x69, 0x64, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xad, 0x05, 0x0a, 0x0e, + 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x12, 0x27, + 0x0a, 0x0f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x68, 0x69, 0x70, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x2c, 0x0a, 0x02, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x50, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x02, + 0x6d, 0x65, 0x12, 0x36, 0x0a, 0x07, 0x70, 0x61, 0x72, 0x74, 0x6e, 0x65, 0x72, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x52, 0x07, 0x70, 0x61, 0x72, 0x74, 0x6e, 0x65, 0x72, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, + 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x6f, 0x72, 0x6d, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, + 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x36, 0x0a, + 0x17, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x74, + 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x68, 0x72, 0x65, + 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x6c, 0x65, + 0x76, 0x65, 0x6c, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x0c, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x12, 0x6e, 0x65, 0x78, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x68, + 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x31, 0x0a, 0x15, 0x6e, 0x65, 0x65, 0x64, 0x65, + 0x64, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x46, 0x6f, + 0x72, 0x4e, 0x65, 0x78, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x34, 0x0a, 0x16, 0x6c, 0x65, + 0x76, 0x65, 0x6c, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x65, 0x72, + 0x63, 0x65, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x6c, 0x65, 0x76, 0x65, + 0x6c, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, + 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x0f, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x2c, 0x0a, + 0x12, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x75, 0x70, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x6f, + 0x69, 0x6e, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x62, 0x72, 0x65, 0x61, 0x6b, + 0x75, 0x70, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x65, + 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x09, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x4a, 0x04, 0x08, 0x08, 0x10, + 0x09, 0x52, 0x08, 0x65, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x22, 0xf9, 0x01, 0x0a, 0x15, + 0x43, 0x50, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x1f, 0x0a, 0x0b, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, - 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1b, 0x0a, - 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x08, 0x6d, 0x61, 0x78, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x75, - 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, - 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x75, 0x6c, 0x65, 0x5f, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0e, 0x72, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x73, 0x6f, - 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, - 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, - 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x2d, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, - 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x13, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0c, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, - 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xe0, - 0x04, 0x0a, 0x0f, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x49, 0x44, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, - 0x09, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, - 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, - 0x6c, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x64, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x61, - 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, - 0x64, 0x5f, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0f, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x49, - 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x61, 0x73, 0x73, 0x69, 0x67, - 0x6e, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x6c, 0x65, 0x61, - 0x73, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0c, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x25, 0x0a, - 0x0e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x52, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x10, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, - 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, - 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, - 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x36, 0x0a, 0x04, 0x70, 0x6f, 0x6f, - 0x6c, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x04, 0x70, 0x6f, 0x6f, - 0x6c, 0x22, 0xe9, 0x03, 0x0a, 0x1e, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x49, 0x44, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x19, 0x0a, 0x08, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x62, 0x61, 0x74, 0x63, 0x68, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, - 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, - 0x6c, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x28, 0x0a, 0x10, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, - 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x75, 0x6c, 0x65, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x67, 0x65, - 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x34, 0x0a, 0x16, - 0x73, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x73, 0x6b, - 0x69, 0x70, 0x70, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, - 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xa0, 0x01, - 0x0a, 0x24, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x50, - 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, 0x52, + 0x1b, 0x0a, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, + 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, + 0x0d, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, + 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6a, + 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x4a, 0x73, 0x6f, 0x6e, 0x22, 0xd9, 0x01, 0x0a, 0x19, 0x43, 0x50, 0x49, 0x6e, + 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, + 0x64, 0x55, 0x73, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, + 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x47, 0x0a, 0x0c, 0x61, 0x76, + 0x61, 0x74, 0x61, 0x72, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x50, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x0b, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x46, 0x72, + 0x61, 0x6d, 0x65, 0x22, 0x82, 0x03, 0x0a, 0x19, 0x43, 0x50, 0x49, 0x6e, 0x74, 0x69, 0x6d, 0x61, + 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, + 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x68, 0x69, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x49, 0x64, 0x12, 0x23, + 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x74, + 0x69, 0x6d, 0x61, 0x63, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, + 0x76, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, + 0x12, 0x3f, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x50, 0x49, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, + 0x41, 0x12, 0x3f, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x62, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x50, 0x49, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, + 0x72, 0x42, 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, + 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xab, 0x03, 0x0a, 0x11, 0x43, 0x50, 0x57, + 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x12, + 0x0a, 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x72, 0x61, + 0x6e, 0x6b, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, + 0x69, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, + 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x49, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, + 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x41, 0x12, 0x3f, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x62, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x49, 0x6e, 0x74, 0x69, 0x6d, 0x61, + 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x42, 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x6f, 0x72, 0x6d, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, + 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2b, + 0x0a, 0x12, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x66, 0x69, 0x72, 0x73, + 0x74, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x29, 0x0a, 0x11, 0x6c, + 0x61, 0x73, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x63, 0x6f, 0x72, + 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xbd, 0x01, 0x0a, 0x1e, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x45, 0x6e, 0x74, 0x72, 0x69, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, + 0x0a, 0x08, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4d, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x65, 0x6e, 0x64, + 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x65, 0x6e, 0x64, 0x4d, 0x73, + 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x83, 0x01, 0x0a, 0x1f, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x45, 0x6e, 0x74, 0x72, 0x69, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x65, 0x6e, + 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x57, 0x65, + 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, + 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xcb, 0x01, 0x0a, + 0x19, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x9a, 0x01, 0x0a, 0x1a, 0x4c, + 0x69, 0x73, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0c, 0x61, 0x70, 0x70, + 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x61, + 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x1a, 0x41, 0x63, 0x63, 0x65, + 0x70, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, - 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, - 0x22, 0x73, 0x0a, 0x25, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, - 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x05, 0x69, 0x74, 0x65, - 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, - 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x8b, 0x01, 0x0a, 0x23, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, - 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x46, 0x72, - 0x6f, 0x6d, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, - 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, - 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, - 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x65, 0x74, 0x74, - 0x79, 0x49, 0x64, 0x22, 0x97, 0x01, 0x0a, 0x24, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x65, - 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x46, 0x72, 0x6f, 0x6d, - 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, - 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, + 0x25, 0x0a, 0x0e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xa0, 0x01, 0x0a, 0x1b, 0x41, 0x63, 0x63, 0x65, 0x70, + 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x41, 0x70, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x52, + 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x0c, 0x72, 0x65, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x22, 0xa4, 0x01, 0x0a, 0x1a, 0x52, 0x65, + 0x6a, 0x65, 0x63, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, + 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x22, 0x5d, 0x0a, 0x1b, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3e, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, + 0xbb, 0x01, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, + 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, + 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x70, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, + 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x9a, 0x01, + 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x68, 0x69, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, + 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x68, 0x69, 0x70, 0x52, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, + 0x70, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x52, 0x1a, + 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x62, 0x79, 0x5f, 0x72, 0x65, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0xa8, 0x01, 0x0a, 0x20, 0x4c, + 0x69, 0x73, 0x74, 0x43, 0x50, 0x49, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x4c, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, + 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, + 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0xd0, 0x01, 0x0a, 0x21, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, + 0x49, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, + 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x05, 0x69, + 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x49, 0x6e, 0x74, + 0x69, 0x6d, 0x61, 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, + 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, + 0x7a, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xb4, 0x01, 0x0a, 0x21, 0x50, 0x72, 0x65, + 0x70, 0x61, 0x72, 0x65, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, + 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, + 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x49, 0x64, + 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x22, + 0xae, 0x02, 0x0a, 0x22, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x42, 0x72, 0x65, 0x61, 0x6b, + 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x50, 0x52, + 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x0c, 0x72, 0x65, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, + 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x75, 0x70, 0x5f, 0x63, 0x6f, 0x73, 0x74, + 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x62, 0x72, + 0x65, 0x61, 0x6b, 0x75, 0x70, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x32, + 0x0a, 0x15, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, + 0x63, 0x6f, 0x69, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, + 0x22, 0xc0, 0x02, 0x0a, 0x21, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x42, 0x72, 0x65, 0x61, + 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, + 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x5f, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x70, + 0x61, 0x69, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x70, 0x61, 0x69, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x41, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x62, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x10, 0x63, 0x6f, 0x69, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, + 0x74, 0x65, 0x72, 0x22, 0xf7, 0x01, 0x0a, 0x22, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x42, + 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, + 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x72, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, + 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x12, 0x2c, 0x0a, + 0x12, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x75, 0x70, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x6f, + 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x62, 0x72, 0x65, 0x61, 0x6b, + 0x75, 0x70, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, + 0x2c, 0x0a, 0x12, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, + 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x69, + 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x22, 0xcb, 0x01, + 0x0a, 0x20, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, + 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, + 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, + 0x69, 0x70, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, + 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x5a, 0x0a, 0x21, 0x43, + 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x99, 0x05, 0x0a, 0x0f, 0x52, 0x6f, 0x6f, 0x6d, + 0x47, 0x69, 0x66, 0x74, 0x43, 0x50, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, + 0x21, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x63, 0x63, 0x75, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6f, 0x63, 0x63, 0x75, + 0x72, 0x72, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, + 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, + 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, + 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x62, + 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, + 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, + 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x67, 0x69, + 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x70, + 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0f, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x70, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, + 0x72, 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x67, 0x69, 0x66, 0x74, 0x49, 0x63, + 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x61, 0x6e, + 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x12, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x10, 0x67, 0x69, 0x66, 0x74, 0x41, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x55, 0x72, 0x6c, 0x22, 0x85, 0x01, 0x0a, 0x1d, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x52, + 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x43, 0x50, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x34, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x43, 0x50, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xbf, 0x01, 0x0a, 0x1e, + 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x43, + 0x50, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, + 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x12, 0x3e, 0x0a, 0x0b, 0x61, 0x70, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, + 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0c, 0x72, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, + 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x22, 0xba, 0x02, + 0x0a, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, + 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, + 0x1d, 0x0a, 0x0a, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x73, 0x18, 0x08, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x55, 0x72, 0x6c, 0x73, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xf9, 0x01, 0x0a, 0x13, 0x53, + 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1f, + 0x0a, 0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6d, 0x61, 0x67, 0x65, + 0x5f, 0x75, 0x72, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6d, 0x61, + 0x67, 0x65, 0x55, 0x72, 0x6c, 0x73, 0x22, 0x49, 0x0a, 0x14, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, + 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, + 0x0a, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x69, 0x64, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, - 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x49, 0x64, 0x22, 0xbb, 0x01, - 0x0a, 0x1f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x73, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x22, 0x61, 0x0a, 0x14, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x73, 0x22, 0xad, 0x01, 0x0a, 0x15, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, + 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, + 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, + 0x75, 0x73, 0x65, 0x72, 0x73, 0x1a, 0x4d, 0x0a, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x22, 0x90, 0x03, 0x0a, 0x14, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x55, + 0x73, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x16, 0x0a, + 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x61, + 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x15, 0x0a, + 0x06, 0x62, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x62, + 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x65, 0x72, 0x6d, 0x61, 0x6e, 0x65, 0x6e, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x70, 0x65, 0x72, 0x6d, 0x61, 0x6e, 0x65, + 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, + 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x3a, 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x28, 0x0a, + 0x10, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x62, 0x61, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x42, + 0x61, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc5, 0x03, 0x0a, 0x10, 0x55, 0x73, 0x65, 0x72, + 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x27, 0x0a, 0x04, + 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, + 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x12, 0x27, 0x0a, 0x0f, 0x72, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x18, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x75, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x14, 0x0a, 0x05, + 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, 0x6c, + 0x65, 0x73, 0x12, 0x35, 0x0a, 0x03, 0x62, 0x61, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x53, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x52, 0x03, 0x62, 0x61, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x6c, 0x61, 0x73, + 0x74, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x31, 0x0a, 0x15, 0x6c, 0x61, 0x73, 0x74, 0x5f, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x6c, 0x61, + 0x73, 0x74, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x6c, 0x61, 0x73, 0x74, 0x4f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2f, + 0x0a, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x6c, 0x61, + 0x73, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x74, 0x4d, 0x73, 0x22, + 0x6d, 0x0a, 0x20, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, + 0x64, 0x6d, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, 0xdd, + 0x01, 0x0a, 0x21, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, + 0x64, 0x6d, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, + 0x73, 0x65, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, + 0x1a, 0x5c, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x35, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x86, + 0x01, 0x0a, 0x20, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x55, 0x73, 0x65, + 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, + 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x87, 0x02, 0x0a, 0x21, 0x41, 0x64, 0x6d, 0x69, + 0x6e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, + 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x24, 0x0a, 0x0e, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x65, + 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, + 0x49, 0x6e, 0x53, 0x65, 0x63, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, + 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, + 0x64, 0x12, 0x3e, 0x0a, 0x1c, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x61, 0x73, + 0x74, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, + 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x4c, 0x61, 0x73, 0x74, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x41, 0x74, 0x4d, + 0x73, 0x22, 0x84, 0x01, 0x0a, 0x0d, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x73, 0x69, 0x63, 0x55, + 0x73, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, + 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, + 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, + 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x6a, 0x0a, 0x1d, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x73, 0x69, 0x63, 0x55, 0x73, 0x65, + 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x73, 0x22, 0xc8, 0x01, 0x0a, 0x1e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, + 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x73, 0x69, 0x63, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x52, + 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x73, 0x69, 0x63, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x1a, 0x56, 0x0a, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x73, 0x69, 0x63, + 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, + 0xe1, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, + 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x75, 0x72, + 0x73, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1b, 0x0a, 0x09, + 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x79, 0x22, 0x73, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x63, 0x75, + 0x72, 0x73, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x10, 0x6e, 0x65, 0x78, 0x74, 0x43, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x22, 0x86, 0x02, 0x0a, 0x18, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, + 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x00, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, + 0x1b, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x01, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, + 0x62, 0x69, 0x72, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x05, 0x62, + 0x69, 0x72, 0x74, 0x68, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, + 0x65, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x42, 0x08, 0x0a, 0x06, + 0x5f, 0x62, 0x69, 0x72, 0x74, 0x68, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x67, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x22, 0x44, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, + 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x93, 0x01, 0x0a, 0x22, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x42, 0x61, 0x63, + 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, + 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, + 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x5f, 0x62, 0x67, 0x5f, 0x69, 0x6d, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x42, 0x67, 0x49, 0x6d, 0x67, 0x22, 0x4e, 0x0a, + 0x23, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x8a, 0x01, + 0x0a, 0x1c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, + 0x61, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, + 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, + 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x63, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x48, 0x0a, 0x1d, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x75, + 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, + 0x75, 0x73, 0x65, 0x72, 0x22, 0x99, 0x01, 0x0a, 0x20, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, + 0x73, 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x75, 0x73, 0x64, 0x74, 0x5f, 0x74, 0x72, 0x63, 0x32, 0x30, + 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, + 0x75, 0x73, 0x64, 0x74, 0x54, 0x72, 0x63, 0x32, 0x30, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x22, 0x4c, 0x0a, 0x21, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x57, 0x69, + 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x7d, + 0x0a, 0x18, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x22, 0xcb, 0x01, + 0x0a, 0x1d, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x73, 0x65, + 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, + 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x22, 0x0a, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xed, 0x01, 0x0a, 0x19, + 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x75, 0x73, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, + 0x65, 0x72, 0x12, 0x38, 0x0a, 0x19, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x6e, 0x65, 0x78, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, + 0x6f, 0x6c, 0x64, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x6c, 0x64, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x12, 0x22, 0x0a, 0x0d, 0x6e, 0x65, 0x77, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6e, 0x65, 0x77, 0x52, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x72, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x22, 0x86, 0x02, 0x0a, 0x14, + 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x28, 0x0a, + 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, + 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, + 0x23, 0x0a, 0x0d, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x54, 0x79, 0x70, 0x65, 0x22, 0xcd, 0x03, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, + 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x32, 0x0a, 0x15, 0x72, 0x65, 0x76, 0x6f, 0x6b, + 0x65, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x53, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x69, + 0x6d, 0x5f, 0x6b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, + 0x69, 0x6d, 0x4b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x69, 0x6d, 0x5f, 0x6b, + 0x69, 0x63, 0x6b, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x69, 0x6d, 0x4b, 0x69, 0x63, 0x6b, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x21, 0x0a, 0x0c, + 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x65, 0x76, 0x69, 0x63, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x45, 0x76, 0x69, 0x63, 0x74, 0x65, 0x64, 0x12, + 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x74, 0x63, 0x5f, + 0x6b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x74, + 0x63, 0x4b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, + 0x65, 0x76, 0x69, 0x63, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x72, 0x6f, 0x6f, 0x6d, 0x45, 0x76, 0x69, 0x63, 0x74, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x74, 0x63, 0x5f, 0x6b, 0x69, 0x63, 0x6b, 0x5f, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x74, 0x63, 0x4b, 0x69, + 0x63, 0x6b, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x30, 0x0a, 0x14, 0x61, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x12, 0x39, 0x0a, 0x19, 0x61, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, + 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x61, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x22, 0xd0, 0x03, 0x0a, 0x0c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, + 0x65, 0x72, 0x42, 0x61, 0x6e, 0x12, 0x15, 0x0a, 0x06, 0x62, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x62, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, + 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x65, 0x72, 0x6d, 0x61, 0x6e, 0x65, + 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x70, 0x65, 0x72, 0x6d, 0x61, 0x6e, + 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, + 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2f, 0x0a, 0x14, 0x72, 0x65, 0x6c, 0x65, 0x61, + 0x73, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x64, 0x42, + 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x6c, 0x65, + 0x61, 0x73, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x64, 0x52, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x65, 0x61, + 0x73, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xf2, 0x01, 0x0a, 0x13, 0x41, 0x64, 0x6d, 0x69, + 0x6e, 0x42, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, + 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, + 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, + 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d, + 0x69, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x83, 0x01, 0x0a, + 0x14, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x42, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x03, 0x62, 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x52, + 0x03, 0x62, 0x61, 0x6e, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x22, 0xc8, 0x01, 0x0a, 0x15, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x6e, 0x62, 0x61, + 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, + 0x62, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x62, 0x61, + 0x6e, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xa8, 0x02, + 0x0a, 0x16, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x03, 0x62, 0x61, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x42, + 0x61, 0x6e, 0x52, 0x03, 0x62, 0x61, 0x6e, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x37, 0x0a, 0x18, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, + 0x64, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x62, 0x61, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, + 0x64, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x42, 0x61, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3f, + 0x0a, 0x1c, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x72, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x19, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x64, 0x4d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x27, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, + 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x22, 0xba, 0x03, 0x0a, 0x10, 0x4d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x19, 0x0a, + 0x08, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x61, + 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x75, + 0x6e, 0x74, 0x69, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x6e, 0x74, 0x69, 0x6c, 0x4d, 0x73, 0x12, 0x16, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x22, 0x0a, + 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, + 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xfe, 0x01, 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, + 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, + 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, + 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x6e, 0x74, 0x69, 0x6c, 0x4d, 0x73, 0x12, 0x16, + 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x95, 0x01, 0x0a, 0x1e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, + 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xab, + 0x01, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, + 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, + 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x58, 0x0a, 0x1d, + 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, + 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x06, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x22, 0xa6, 0x01, 0x0a, 0x19, 0x55, 0x6e, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, + 0x91, 0x01, 0x0a, 0x1a, 0x55, 0x6e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, + 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x05, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x22, 0xeb, 0x01, 0x0a, 0x19, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x65, 0x76, - 0x65, 0x6c, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, - 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, - 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x72, 0x0a, 0x20, 0x4c, - 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x38, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, - 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, - 0x6f, 0x6c, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, - 0xeb, 0x02, 0x0a, 0x20, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, + 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, + 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, + 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x18, + 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x65, 0x6e, 0x64, + 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, + 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x64, + 0x65, 0x22, 0xbc, 0x02, 0x0a, 0x1a, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x6e, + 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x27, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, + 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x35, 0x0a, 0x17, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2b, 0x0a, + 0x11, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, + 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2e, 0x0a, 0x05, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, 0x0a, 0x06, 0x69, 0x6e, + 0x76, 0x69, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, + 0x65, 0x42, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, + 0x22, 0x87, 0x01, 0x0a, 0x1b, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x76, 0x69, 0x74, + 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x76, + 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x4d, 0x0a, 0x1c, 0x53, 0x65, + 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, + 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x07, 0x69, 0x6e, + 0x76, 0x69, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x07, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x22, 0x85, 0x01, 0x0a, 0x19, 0x42, 0x69, + 0x6e, 0x64, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, + 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x64, + 0x65, 0x22, 0x81, 0x01, 0x0a, 0x1a, 0x42, 0x69, 0x6e, 0x64, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, + 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x34, 0x0a, 0x06, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x42, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x06, + 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x07, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, + 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, 0x69, 0x6e, + 0x76, 0x69, 0x74, 0x65, 0x72, 0x22, 0xac, 0x02, 0x0a, 0x14, 0x42, 0x69, 0x6e, 0x64, 0x50, 0x75, + 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, + 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, + 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x73, 0x68, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, + 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1f, 0x0a, 0x0b, 0x61, + 0x70, 0x70, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x61, 0x70, 0x70, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, + 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, + 0x7a, 0x6f, 0x6e, 0x65, 0x22, 0x51, 0x0a, 0x15, 0x42, 0x69, 0x6e, 0x64, 0x50, 0x75, 0x73, 0x68, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x9d, 0x01, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x50, 0x75, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x73, 0x68, + 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, + 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x57, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x50, 0x75, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, + 0x22, 0xbe, 0x03, 0x0a, 0x07, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1d, 0x0a, 0x0a, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, + 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x12, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, + 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, + 0x6f, 0x5f, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x33, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x69, 0x73, 0x6f, 0x41, 0x6c, 0x70, 0x68, 0x61, 0x33, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x6f, + 0x5f, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x69, 0x73, 0x6f, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x68, + 0x6f, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, + 0x65, 0x22, 0x92, 0x02, 0x0a, 0x06, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, + 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, + 0x69, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x72, 0x69, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, + 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, + 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x71, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, + 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, + 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, + 0x00, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, + 0x08, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x4d, 0x0a, 0x15, 0x4c, 0x69, 0x73, + 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x34, 0x0a, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0x85, 0x03, 0x0a, 0x14, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, + 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x64, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x12, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, + 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x69, 0x73, 0x6f, 0x5f, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x33, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x69, 0x73, 0x6f, 0x41, 0x6c, 0x70, 0x68, 0x61, 0x33, 0x12, 0x1f, 0x0a, + 0x0b, 0x69, 0x73, 0x6f, 0x5f, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x69, 0x73, 0x6f, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x12, 0x2c, + 0x0a, 0x12, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x68, 0x6f, 0x6e, + 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x66, 0x6c, 0x61, 0x67, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x61, 0x67, + 0x22, 0x43, 0x0a, 0x0f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x52, 0x0a, 0x20, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x59, 0x0a, 0x21, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, + 0x0a, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x72, 0x69, 0x65, 0x73, 0x22, 0x7a, 0x0a, 0x17, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, + 0x6b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, + 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x22, 0x0a, 0x0d, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, + 0x22, 0x56, 0x0a, 0x24, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, + 0x6b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, + 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x91, 0x01, 0x0a, 0x25, 0x4c, 0x69, 0x73, + 0x74, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, + 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x44, 0x0a, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x5c, 0x0a, 0x12, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x46, 0x0a, 0x13, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x72, 0x65, 0x67, 0x69, 0x6f, + 0x6e, 0x73, 0x22, 0x5f, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, + 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x22, 0xe0, 0x01, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x72, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, + 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, + 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xb4, 0x01, 0x0a, 0x1d, 0x52, 0x65, 0x70, 0x6c, 0x61, + 0x63, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, + 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, + 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, + 0x69, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x3f, 0x0a, + 0x0e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2d, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x22, 0xfd, + 0x02, 0x0a, 0x0c, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, + 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x64, 0x65, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x2f, 0x0a, 0x14, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x64, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x4b, 0x69, 0x6e, 0x64, + 0x12, 0x3f, 0x0a, 0x1d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, + 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, + 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, + 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x70, 0x72, 0x65, + 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x65, 0x74, 0x74, + 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x61, + 0x0a, 0x16, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, + 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x22, 0x52, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, + 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x69, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x75, 0x0a, 0x1b, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, + 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x57, 0x0a, 0x1c, + 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, + 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x69, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0xd6, 0x01, 0x0a, 0x1a, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2d, 0x0a, + 0x13, 0x6e, 0x65, 0x77, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6e, 0x65, 0x77, 0x44, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, + 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x56, + 0x0a, 0x1b, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, + 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x69, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0xf9, 0x01, 0x0a, 0x1f, 0x41, 0x70, 0x70, 0x6c, 0x79, + 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x65, 0x61, 0x73, + 0x65, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, + 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x10, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, + 0x49, 0x64, 0x22, 0x76, 0x0a, 0x20, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x65, 0x74, 0x74, + 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, + 0x19, 0x0a, 0x08, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x49, 0x64, 0x22, 0x86, 0x01, 0x0a, 0x20, 0x45, + 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, + 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x65, 0x61, 0x73, + 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x65, 0x61, 0x73, + 0x65, 0x49, 0x64, 0x22, 0x5c, 0x0a, 0x21, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, + 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x69, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x22, 0xdc, 0x03, 0x0a, 0x13, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, + 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, + 0x63, 0x6b, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, + 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, + 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x72, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x75, 0x6c, + 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, + 0x73, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, + 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x2d, 0x0a, 0x13, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x13, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, + 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, + 0x22, 0xe0, 0x04, 0x0a, 0x0f, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x49, 0x44, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, + 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, + 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x26, 0x0a, + 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x28, 0x0a, + 0x10, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, + 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x61, 0x73, 0x73, 0x69, 0x67, + 0x6e, 0x65, 0x64, 0x5f, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4c, 0x65, 0x61, 0x73, + 0x65, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x61, 0x73, 0x73, + 0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x6c, + 0x65, 0x61, 0x73, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, + 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, + 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x10, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, + 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x36, 0x0a, 0x04, 0x70, + 0x6f, 0x6f, 0x6c, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, + 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x04, 0x70, + 0x6f, 0x6f, 0x6c, 0x22, 0xe9, 0x03, 0x0a, 0x1e, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x74, 0x63, 0x68, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, + 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, + 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x75, 0x6c, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x75, + 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, + 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x34, + 0x0a, 0x16, 0x73, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, + 0x63, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, + 0x73, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2a, 0x0a, 0x11, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, + 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, + 0xa0, 0x01, 0x0a, 0x24, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, + 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, + 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, + 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, + 0x7a, 0x65, 0x22, 0x73, 0x0a, 0x25, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, + 0x62, 0x6c, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x05, 0x69, + 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x74, 0x74, + 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, + 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x8b, 0x01, 0x0a, 0x23, 0x41, 0x70, 0x70, 0x6c, + 0x79, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, + 0x46, 0x72, 0x6f, 0x6d, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, + 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x74, + 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x65, + 0x74, 0x74, 0x79, 0x49, 0x64, 0x22, 0x97, 0x01, 0x0a, 0x24, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, + 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x46, 0x72, + 0x6f, 0x6d, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, + 0x0a, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x69, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x74, 0x74, + 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x65, 0x74, + 0x74, 0x79, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x49, 0x64, 0x22, + 0xbb, 0x01, 0x0a, 0x1f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, + 0x70, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, + 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x72, 0x0a, + 0x20, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x38, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, + 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x22, 0xeb, 0x02, 0x0a, 0x20, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x74, + 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, + 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x65, + 0x76, 0x65, 0x6c, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x12, 0x1b, 0x0a, 0x09, 0x6d, + 0x69, 0x6e, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, + 0x6d, 0x69, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, + 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x75, 0x6c, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x75, + 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, + 0x84, 0x03, 0x0a, 0x20, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x65, 0x76, 0x65, - 0x6c, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, - 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, - 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, - 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x65, - 0x76, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x28, 0x0a, 0x10, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, - 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x75, 0x6c, 0x65, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, - 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x84, 0x03, - 0x0a, 0x20, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x1f, 0x0a, 0x0b, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, - 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1b, 0x0a, - 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x08, 0x6d, 0x61, 0x78, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x75, - 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, + 0x63, 0x6b, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, + 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, + 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x72, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x75, 0x6c, + 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, + 0x73, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, + 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, + 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x55, 0x0a, 0x1b, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, + 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, + 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x04, 0x70, 0x6f, 0x6f, 0x6c, 0x22, 0xf3, 0x01, + 0x0a, 0x1f, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, + 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x75, + 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x75, 0x6c, 0x65, 0x5f, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x73, 0x6f, - 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, - 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, - 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, - 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d, - 0x69, 0x6e, 0x49, 0x64, 0x22, 0x55, 0x0a, 0x1b, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, - 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x04, 0x70, 0x6f, 0x6f, 0x6c, 0x22, 0xf3, 0x01, 0x0a, 0x1f, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, - 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x75, 0x6c, 0x65, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x75, 0x6c, - 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0e, 0x72, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x73, 0x6f, 0x6e, 0x12, - 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, - 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, - 0x64, 0x22, 0x67, 0x0a, 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, - 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x49, 0x44, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, - 0x74, 0x63, 0x68, 0x52, 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x22, 0x8b, 0x02, 0x0a, 0x1b, 0x4c, - 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, + 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d, 0x69, + 0x6e, 0x49, 0x64, 0x22, 0x67, 0x0a, 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, + 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x22, 0x8b, 0x02, 0x0a, + 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, + 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, + 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, + 0x28, 0x0a, 0x10, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x61, 0x73, 0x73, 0x69, 0x67, + 0x6e, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, + 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x6a, 0x0a, 0x1c, 0x4c, 0x69, + 0x73, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, + 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x05, 0x69, 0x74, + 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, + 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0xca, 0x01, 0x0a, 0x1f, 0x53, 0x65, 0x74, 0x50, 0x72, + 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, - 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, - 0x6c, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x28, 0x0a, - 0x10, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, - 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, - 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, - 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x6a, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, - 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x14, - 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, - 0x6f, 0x74, 0x61, 0x6c, 0x22, 0xca, 0x01, 0x0a, 0x1f, 0x53, 0x65, 0x74, 0x50, 0x72, 0x65, 0x74, - 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x74, - 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x65, - 0x74, 0x74, 0x79, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2a, 0x0a, - 0x11, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x6f, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x22, 0xb0, 0x01, 0x0a, 0x1d, 0x52, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x50, 0x72, 0x65, - 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x49, 0x64, - 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, - 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d, - 0x69, 0x6e, 0x49, 0x64, 0x22, 0x4d, 0x0a, 0x17, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x32, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, - 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, 0x04, 0x69, - 0x74, 0x65, 0x6d, 0x22, 0x85, 0x02, 0x0a, 0x20, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x72, 0x61, - 0x6e, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, - 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, - 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, - 0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, - 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x94, 0x01, 0x0a, 0x21, - 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, - 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x37, 0x0a, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, - 0x72, 0x65, 0x74, 0x74, 0x79, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x65, 0x61, 0x73, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x65, 0x61, 0x73, 0x65, - 0x49, 0x64, 0x22, 0x41, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x70, 0x73, 0x52, 0x65, + 0x72, 0x65, 0x74, 0x74, 0x79, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x22, 0xb0, 0x01, 0x0a, 0x1d, 0x52, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x50, + 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x3a, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x70, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x61, 0x70, 0x70, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x04, 0x61, 0x70, 0x70, - 0x73, 0x2a, 0x73, 0x0a, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x1b, 0x0a, 0x17, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, - 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, - 0x55, 0x53, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x41, 0x43, 0x54, 0x49, - 0x56, 0x45, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x55, 0x53, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x12, 0x16, - 0x0a, 0x12, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x42, 0x41, - 0x4e, 0x4e, 0x45, 0x44, 0x10, 0x03, 0x32, 0xf4, 0x11, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x12, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x6f, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x74, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x69, - 0x74, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x74, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x69, 0x0a, 0x12, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x55, 0x73, 0x65, - 0x72, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, - 0x55, 0x73, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x6f, - 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x11, - 0x47, 0x65, 0x74, 0x4d, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, - 0x73, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x79, - 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x75, 0x0a, 0x16, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, - 0x42, 0x61, 0x73, 0x69, 0x63, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, + 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, + 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x4d, 0x0a, 0x17, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, + 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x32, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x85, 0x02, 0x0a, 0x20, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, + 0x72, 0x61, 0x6e, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, + 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, + 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x94, 0x01, + 0x0a, 0x21, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x50, 0x72, 0x65, 0x74, + 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1b, 0x0a, 0x09, + 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x65, 0x61, + 0x73, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x65, 0x61, + 0x73, 0x65, 0x49, 0x64, 0x22, 0x41, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x70, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, + 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x3a, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x41, + 0x70, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x61, + 0x70, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x04, 0x61, + 0x70, 0x70, 0x73, 0x2a, 0x73, 0x0a, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x1b, 0x0a, 0x17, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, + 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x16, + 0x0a, 0x12, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x41, 0x43, + 0x54, 0x49, 0x56, 0x45, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x53, + 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x02, + 0x12, 0x16, 0x0a, 0x12, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, + 0x42, 0x41, 0x4e, 0x4e, 0x45, 0x44, 0x10, 0x03, 0x32, 0xac, 0x15, 0x0a, 0x0b, 0x55, 0x73, 0x65, + 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, + 0x73, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, + 0x76, 0x69, 0x74, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, + 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x55, + 0x73, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, + 0x73, 0x73, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x55, 0x73, 0x65, 0x72, + 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, + 0x0a, 0x11, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, + 0x61, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x4d, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, + 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, + 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x19, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, + 0x65, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, + 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x64, 0x6d, 0x69, + 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x64, 0x6d, + 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x19, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x73, 0x73, 0x75, 0x65, + 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, + 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x75, 0x0a, 0x16, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x52, 0x6f, + 0x6f, 0x6d, 0x42, 0x61, 0x73, 0x69, 0x63, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x2c, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x73, 0x69, 0x63, 0x55, 0x73, + 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x73, 0x69, 0x63, 0x55, 0x73, 0x65, 0x72, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, - 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x61, 0x73, 0x69, 0x63, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x44, 0x73, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, - 0x17, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4d, 0x69, 0x63, 0x4c, 0x69, 0x66, 0x65, 0x74, - 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, - 0x4d, 0x69, 0x63, 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4d, - 0x69, 0x63, 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x27, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, - 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x84, 0x01, 0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, - 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, - 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, - 0x65, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, - 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x63, - 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x19, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x4c, 0x69, 0x73, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x73, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x78, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4d, 0x69, 0x63, 0x4c, 0x69, 0x66, + 0x65, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, + 0x65, 0x72, 0x4d, 0x69, 0x63, 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x4d, 0x69, 0x63, 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x11, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x27, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, - 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, - 0x73, 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x11, 0x43, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, - 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x84, 0x01, 0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, + 0x64, 0x12, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x49, 0x6e, 0x66, + 0x6f, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, + 0x61, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, + 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x19, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, + 0x61, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x55, 0x73, 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x55, 0x73, 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, - 0x73, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x70, 0x0a, 0x16, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x2c, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, - 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, + 0x79, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x75, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x4d, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x16, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x2c, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, + 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, + 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x42, 0x61, 0x6e, 0x55, 0x73, + 0x65, 0x72, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x42, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x42, 0x61, 0x6e, 0x55, + 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x41, + 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x12, 0x24, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x55, 0x73, + 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x75, 0x0a, 0x16, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, + 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x72, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, + 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, - 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, - 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x55, - 0x6e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, - 0x72, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x55, 0x6e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, - 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x55, 0x6e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, - 0x74, 0x65, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x28, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, - 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4f, - 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x76, 0x69, 0x74, - 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, - 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x76, 0x69, - 0x74, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x42, 0x69, 0x6e, 0x64, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, - 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x69, 0x6e, 0x64, 0x49, 0x6e, 0x76, - 0x69, 0x74, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x42, 0x69, 0x6e, 0x64, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x65, 0x66, - 0x65, 0x72, 0x72, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc3, 0x08, - 0x0a, 0x11, 0x55, 0x73, 0x65, 0x72, 0x53, 0x6f, 0x63, 0x69, 0x61, 0x6c, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, - 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, - 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, - 0x69, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, - 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, - 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0a, - 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x73, 0x65, 0x72, 0x12, 0x20, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, - 0x77, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, 0x6c, - 0x6c, 0x6f, 0x77, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x57, 0x0a, 0x0c, 0x55, 0x6e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x73, 0x65, 0x72, 0x12, - 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x55, 0x6e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x73, 0x65, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, - 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6f, - 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, 0x72, 0x69, - 0x65, 0x6e, 0x64, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, 0x72, 0x69, 0x65, - 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, 0x17, 0x41, 0x63, - 0x63, 0x65, 0x70, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x46, 0x72, 0x69, 0x65, - 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, - 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x72, - 0x69, 0x65, 0x6e, 0x64, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x72, 0x69, 0x65, 0x6e, - 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, - 0x72, 0x69, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, - 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x73, 0x12, 0x21, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x75, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, - 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2c, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x53, 0x75, - 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, - 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x32, 0xcc, 0x07, 0x0a, 0x0d, 0x55, 0x73, 0x65, 0x72, 0x43, 0x50, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x41, - 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x28, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x6c, 0x0a, 0x13, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x43, 0x50, - 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, - 0x0a, 0x13, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x50, 0x41, 0x70, - 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, - 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, - 0x69, 0x70, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, - 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x19, 0x4c, 0x69, - 0x73, 0x74, 0x43, 0x50, 0x49, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, - 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x49, 0x6e, - 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, - 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x49, - 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, - 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x81, 0x01, 0x0a, 0x1a, 0x50, - 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x12, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, - 0x65, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x70, - 0x61, 0x72, 0x65, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x81, - 0x01, 0x0a, 0x1a, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, - 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x12, 0x30, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x19, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x72, 0x65, 0x61, - 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x12, - 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x32, 0x88, 0x02, 0x0a, 0x15, 0x55, 0x73, 0x65, 0x72, 0x43, 0x50, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x75, 0x0a, 0x16, - 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x43, - 0x50, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x6f, - 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x43, 0x50, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x6f, 0x6f, 0x6d, - 0x47, 0x69, 0x66, 0x74, 0x43, 0x50, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x57, 0x65, 0x65, - 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x2d, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x45, - 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x45, 0x6e, - 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xf0, 0x03, - 0x0a, 0x0f, 0x55, 0x73, 0x65, 0x72, 0x43, 0x72, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x5c, 0x0a, 0x17, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x67, 0x69, - 0x6e, 0x49, 0x50, 0x52, 0x69, 0x73, 0x6b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1f, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, - 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x5e, 0x0a, 0x19, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1f, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, - 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x5e, 0x0a, 0x19, 0x43, 0x6f, 0x6d, 0x70, 0x65, 0x6e, 0x73, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x63, - 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1f, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, - 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x5c, 0x0a, 0x17, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, - 0x55, 0x73, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, - 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, - 0x1c, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x43, 0x50, 0x49, 0x6e, 0x74, 0x69, 0x6d, 0x61, - 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x1f, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x32, 0xd1, 0x01, 0x0a, 0x11, 0x55, 0x73, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x42, 0x69, 0x6e, 0x64, 0x50, 0x75, - 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x69, 0x6e, 0x64, 0x50, 0x75, 0x73, 0x68, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x69, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x6e, 0x62, 0x6f, + 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4f, + 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x53, + 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, + 0x72, 0x65, 0x72, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, + 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x65, 0x66, 0x65, + 0x72, 0x72, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, + 0x42, 0x69, 0x6e, 0x64, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, + 0x65, 0x72, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x42, 0x69, 0x6e, 0x64, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x65, 0x66, + 0x65, 0x72, 0x72, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x69, 0x6e, - 0x64, 0x50, 0x75, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x75, 0x73, 0x68, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x75, 0x73, 0x68, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x50, 0x75, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xb4, 0x01, 0x0a, 0x12, 0x41, 0x70, 0x70, 0x52, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x51, 0x0a, 0x0a, 0x52, - 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x41, 0x70, 0x70, 0x12, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, - 0x65, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, - 0x6c, 0x76, 0x65, 0x41, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, - 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x70, 0x73, 0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, - 0x70, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, - 0x70, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc7, 0x01, 0x0a, 0x13, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x69, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x64, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc3, 0x08, 0x0a, 0x11, 0x55, 0x73, 0x65, 0x72, + 0x53, 0x6f, 0x63, 0x69, 0x61, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x69, 0x0a, + 0x12, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, + 0x73, 0x69, 0x74, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x73, 0x12, + 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, + 0x6f, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x56, 0x69, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0a, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, + 0x55, 0x73, 0x65, 0x72, 0x12, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x73, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x55, 0x6e, 0x66, + 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x73, 0x65, 0x72, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x66, 0x6f, 0x6c, 0x6c, + 0x6f, 0x77, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, + 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, + 0x69, 0x6e, 0x67, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, + 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6f, 0x6c, + 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, + 0x0a, 0x0b, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x12, 0x21, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, + 0x70, 0x6c, 0x79, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, 0x17, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x46, 0x72, + 0x69, 0x65, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, + 0x63, 0x63, 0x65, 0x70, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, + 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x12, 0x22, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x46, + 0x72, 0x69, 0x65, 0x6e, 0x64, 0x73, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, + 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x72, + 0x69, 0x65, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x75, 0x0a, + 0x16, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x72, 0x69, 0x65, + 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, + 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xcc, 0x07, + 0x0a, 0x0d, 0x55, 0x73, 0x65, 0x72, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x69, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x41, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, + 0x65, 0x70, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x52, 0x65, 0x6a, 0x65, + 0x63, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6a, 0x65, 0x63, + 0x74, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, + 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x12, 0x29, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x52, + 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x49, 0x6e, + 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, + 0x64, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x49, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, + 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x49, 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, + 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x81, 0x01, 0x0a, 0x1a, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, + 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x68, 0x69, 0x70, 0x12, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x42, 0x72, 0x65, 0x61, 0x6b, + 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x42, 0x72, 0x65, + 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x81, 0x01, 0x0a, 0x1a, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x72, 0x6d, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x12, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x42, + 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, + 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, + 0x6d, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x19, + 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, + 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x68, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, + 0x6c, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x43, 0x50, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x88, 0x02, 0x0a, + 0x15, 0x55, 0x73, 0x65, 0x72, 0x43, 0x50, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x75, 0x0a, 0x16, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, + 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x43, 0x50, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, + 0x43, 0x50, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x43, 0x50, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, + 0x17, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, + 0x6b, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x57, + 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x50, 0x57, 0x65, + 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x6b, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xab, 0x05, 0x0a, 0x0f, 0x55, 0x73, 0x65, 0x72, + 0x43, 0x72, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5c, 0x0a, 0x17, 0x50, + 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x50, 0x52, 0x69, 0x73, + 0x6b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x19, 0x50, 0x72, 0x6f, + 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, + 0x64, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x19, 0x43, 0x6f, 0x6d, + 0x70, 0x65, 0x6e, 0x73, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x63, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x1a, 0x43, 0x6f, 0x6d, + 0x70, 0x65, 0x6e, 0x73, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x4f, 0x70, 0x65, 0x6e, 0x53, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x17, 0x45, 0x78, + 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x13, 0x45, 0x78, 0x70, 0x69, + 0x72, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x73, 0x12, + 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x61, 0x0a, 0x1c, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x43, 0x50, 0x49, + 0x6e, 0x74, 0x69, 0x6d, 0x61, 0x63, 0x79, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x61, + 0x72, 0x64, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xd1, 0x01, 0x0a, 0x11, 0x55, 0x73, 0x65, 0x72, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x42, + 0x69, 0x6e, 0x64, 0x50, 0x75, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x69, 0x6e, + 0x64, 0x50, 0x75, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x42, 0x69, 0x6e, 0x64, 0x50, 0x75, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x50, 0x75, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x50, 0x75, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x75, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xb4, 0x01, 0x0a, 0x12, 0x41, 0x70, + 0x70, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x51, 0x0a, 0x0a, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x41, 0x70, 0x70, 0x12, 0x20, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x41, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x70, 0x73, 0x12, + 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x32, 0xc7, 0x01, 0x0a, 0x13, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x41, 0x64, 0x6d, 0x69, + 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x54, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xa2, 0x02, 0x0a, 0x13, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, - 0x79, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7e, 0x0a, - 0x19, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8a, 0x01, - 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x42, - 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, - 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xa2, 0x02, 0x0a, 0x13, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x12, 0x7e, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, + 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x8a, 0x01, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x52, 0x69, 0x73, 0x6b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x72, 0x69, 0x65, 0x73, 0x12, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, - 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xf1, 0x02, 0x0a, 0x12, 0x52, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x52, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x16, 0x52, 0x65, 0x70, 0x6c, 0x61, - 0x63, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, - 0x73, 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xea, - 0x06, 0x0a, 0x13, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x6f, - 0x6c, 0x76, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, - 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, - 0x6f, 0x6c, 0x76, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x43, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, - 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x6f, + 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, + 0xf1, 0x02, 0x0a, 0x12, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x09, + 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0c, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x16, + 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x32, 0xea, 0x06, 0x0a, 0x13, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x47, + 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x25, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, + 0x14, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x44, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, + 0x0a, 0x13, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x44, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7b, 0x0a, 0x18, 0x41, 0x70, 0x70, 0x6c, 0x79, - 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x44, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8a, 0x01, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, - 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, 0x12, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, - 0x61, 0x62, 0x6c, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, - 0x79, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x68, 0x79, + 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7b, 0x0a, 0x18, + 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, + 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, + 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8a, 0x01, 0x0a, 0x1d, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, + 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, 0x12, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x87, 0x01, 0x0a, 0x1c, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x65, 0x74, 0x74, - 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x46, 0x72, 0x6f, 0x6d, 0x50, 0x6f, - 0x6f, 0x6c, 0x12, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x46, 0x72, 0x6f, 0x6d, 0x50, 0x6f, 0x6f, 0x6c, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x65, 0x74, - 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x46, 0x72, 0x6f, 0x6d, 0x50, - 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x19, 0x45, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x72, + 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x87, 0x01, 0x0a, 0x1c, 0x41, 0x70, 0x70, 0x6c, 0x79, + 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x46, + 0x72, 0x6f, 0x6d, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x65, + 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x46, 0x72, 0x6f, 0x6d, + 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, + 0x79, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, + 0x46, 0x72, 0x6f, 0x6d, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x7e, 0x0a, 0x19, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, + 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x2f, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, + 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, + 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, - 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, - 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xe4, 0x07, 0x0a, 0x1f, - 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, - 0x79, 0x49, 0x44, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, - 0x7b, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x73, 0x12, 0x2e, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x32, 0xe4, 0x07, 0x0a, 0x1f, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x7b, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x74, + 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x73, + 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x78, 0x0a, 0x19, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, + 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x2f, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, - 0x6f, 0x6f, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, - 0x6f, 0x6f, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, 0x19, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, - 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x74, 0x74, - 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, - 0x6f, 0x6f, 0x6c, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, - 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x7b, 0x0a, 0x18, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x74, - 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, 0x12, 0x2e, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, - 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, - 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, - 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x49, 0x44, 0x73, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, + 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, 0x19, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, + 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, + 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, + 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7b, 0x0a, 0x18, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, + 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, + 0x73, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, - 0x0a, 0x18, 0x53, 0x65, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x49, 0x44, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x72, - 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x74, 0x74, - 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x16, 0x52, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x50, 0x72, 0x65, - 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x12, 0x2c, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, - 0x79, 0x63, 0x6c, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, - 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x74, 0x74, - 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x19, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x72, 0x61, 0x6e, 0x74, - 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x12, - 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, - 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, - 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, - 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, - 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, + 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, + 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, + 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, + 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, + 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x65, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x49, 0x44, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x16, 0x52, 0x65, 0x63, 0x79, 0x63, + 0x6c, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, + 0x44, 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x19, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x47, 0x72, 0x61, 0x6e, 0x74, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x49, 0x44, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x50, + 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x47, 0x72, 0x61, 0x6e, 0x74, + 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x49, 0x44, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x76, 0x31, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -14956,7 +16187,7 @@ func file_proto_user_v1_user_proto_rawDescGZIP() []byte { } var file_proto_user_v1_user_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_proto_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 170) +var file_proto_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 182) var file_proto_user_v1_user_proto_goTypes = []any{ (UserStatus)(0), // 0: hyapp.user.v1.UserStatus (*RequestMeta)(nil), // 1: hyapp.user.v1.RequestMeta @@ -15041,94 +16272,106 @@ var file_proto_user_v1_user_proto_goTypes = []any{ (*SubmitReportResponse)(nil), // 80: hyapp.user.v1.SubmitReportResponse (*BatchGetUsersRequest)(nil), // 81: hyapp.user.v1.BatchGetUsersRequest (*BatchGetUsersResponse)(nil), // 82: hyapp.user.v1.BatchGetUsersResponse - (*RoomBasicUser)(nil), // 83: hyapp.user.v1.RoomBasicUser - (*BatchGetRoomBasicUsersRequest)(nil), // 84: hyapp.user.v1.BatchGetRoomBasicUsersRequest - (*BatchGetRoomBasicUsersResponse)(nil), // 85: hyapp.user.v1.BatchGetRoomBasicUsersResponse - (*ListUserIDsRequest)(nil), // 86: hyapp.user.v1.ListUserIDsRequest - (*ListUserIDsResponse)(nil), // 87: hyapp.user.v1.ListUserIDsResponse - (*UpdateUserProfileRequest)(nil), // 88: hyapp.user.v1.UpdateUserProfileRequest - (*UpdateUserProfileResponse)(nil), // 89: hyapp.user.v1.UpdateUserProfileResponse - (*UpdateUserProfileBackgroundRequest)(nil), // 90: hyapp.user.v1.UpdateUserProfileBackgroundRequest - (*UpdateUserProfileBackgroundResponse)(nil), // 91: hyapp.user.v1.UpdateUserProfileBackgroundResponse - (*UpdateUserContactInfoRequest)(nil), // 92: hyapp.user.v1.UpdateUserContactInfoRequest - (*UpdateUserContactInfoResponse)(nil), // 93: hyapp.user.v1.UpdateUserContactInfoResponse - (*UpdateUserWithdrawAddressRequest)(nil), // 94: hyapp.user.v1.UpdateUserWithdrawAddressRequest - (*UpdateUserWithdrawAddressResponse)(nil), // 95: hyapp.user.v1.UpdateUserWithdrawAddressResponse - (*ChangeUserCountryRequest)(nil), // 96: hyapp.user.v1.ChangeUserCountryRequest - (*AdminChangeUserCountryRequest)(nil), // 97: hyapp.user.v1.AdminChangeUserCountryRequest - (*ChangeUserCountryResponse)(nil), // 98: hyapp.user.v1.ChangeUserCountryResponse - (*SetUserStatusRequest)(nil), // 99: hyapp.user.v1.SetUserStatusRequest - (*SetUserStatusResponse)(nil), // 100: hyapp.user.v1.SetUserStatusResponse - (*ManagerUserBlock)(nil), // 101: hyapp.user.v1.ManagerUserBlock - (*CreateManagerUserBlockRequest)(nil), // 102: hyapp.user.v1.CreateManagerUserBlockRequest - (*CreateManagerUserBlockResponse)(nil), // 103: hyapp.user.v1.CreateManagerUserBlockResponse - (*ListManagerUserBlocksRequest)(nil), // 104: hyapp.user.v1.ListManagerUserBlocksRequest - (*ListManagerUserBlocksResponse)(nil), // 105: hyapp.user.v1.ListManagerUserBlocksResponse - (*UnblockManagerUserRequest)(nil), // 106: hyapp.user.v1.UnblockManagerUserRequest - (*UnblockManagerUserResponse)(nil), // 107: hyapp.user.v1.UnblockManagerUserResponse - (*CompleteOnboardingRequest)(nil), // 108: hyapp.user.v1.CompleteOnboardingRequest - (*CompleteOnboardingResponse)(nil), // 109: hyapp.user.v1.CompleteOnboardingResponse - (*SearchInviteReferrerRequest)(nil), // 110: hyapp.user.v1.SearchInviteReferrerRequest - (*SearchInviteReferrerResponse)(nil), // 111: hyapp.user.v1.SearchInviteReferrerResponse - (*BindInviteReferrerRequest)(nil), // 112: hyapp.user.v1.BindInviteReferrerRequest - (*BindInviteReferrerResponse)(nil), // 113: hyapp.user.v1.BindInviteReferrerResponse - (*BindPushTokenRequest)(nil), // 114: hyapp.user.v1.BindPushTokenRequest - (*BindPushTokenResponse)(nil), // 115: hyapp.user.v1.BindPushTokenResponse - (*DeletePushTokenRequest)(nil), // 116: hyapp.user.v1.DeletePushTokenRequest - (*DeletePushTokenResponse)(nil), // 117: hyapp.user.v1.DeletePushTokenResponse - (*Country)(nil), // 118: hyapp.user.v1.Country - (*Region)(nil), // 119: hyapp.user.v1.Region - (*ListCountriesRequest)(nil), // 120: hyapp.user.v1.ListCountriesRequest - (*ListCountriesResponse)(nil), // 121: hyapp.user.v1.ListCountriesResponse - (*UpdateCountryRequest)(nil), // 122: hyapp.user.v1.UpdateCountryRequest - (*CountryResponse)(nil), // 123: hyapp.user.v1.CountryResponse - (*ListRegistrationCountriesRequest)(nil), // 124: hyapp.user.v1.ListRegistrationCountriesRequest - (*ListRegistrationCountriesResponse)(nil), // 125: hyapp.user.v1.ListRegistrationCountriesResponse - (*LoginRiskBlockedCountry)(nil), // 126: hyapp.user.v1.LoginRiskBlockedCountry - (*ListLoginRiskBlockedCountriesRequest)(nil), // 127: hyapp.user.v1.ListLoginRiskBlockedCountriesRequest - (*ListLoginRiskBlockedCountriesResponse)(nil), // 128: hyapp.user.v1.ListLoginRiskBlockedCountriesResponse - (*ListRegionsRequest)(nil), // 129: hyapp.user.v1.ListRegionsRequest - (*ListRegionsResponse)(nil), // 130: hyapp.user.v1.ListRegionsResponse - (*GetRegionRequest)(nil), // 131: hyapp.user.v1.GetRegionRequest - (*UpdateRegionRequest)(nil), // 132: hyapp.user.v1.UpdateRegionRequest - (*ReplaceRegionCountriesRequest)(nil), // 133: hyapp.user.v1.ReplaceRegionCountriesRequest - (*RegionResponse)(nil), // 134: hyapp.user.v1.RegionResponse - (*UserIdentity)(nil), // 135: hyapp.user.v1.UserIdentity - (*GetUserIdentityRequest)(nil), // 136: hyapp.user.v1.GetUserIdentityRequest - (*GetUserIdentityResponse)(nil), // 137: hyapp.user.v1.GetUserIdentityResponse - (*ResolveDisplayUserIDRequest)(nil), // 138: hyapp.user.v1.ResolveDisplayUserIDRequest - (*ResolveDisplayUserIDResponse)(nil), // 139: hyapp.user.v1.ResolveDisplayUserIDResponse - (*ChangeDisplayUserIDRequest)(nil), // 140: hyapp.user.v1.ChangeDisplayUserIDRequest - (*ChangeDisplayUserIDResponse)(nil), // 141: hyapp.user.v1.ChangeDisplayUserIDResponse - (*ApplyPrettyDisplayUserIDRequest)(nil), // 142: hyapp.user.v1.ApplyPrettyDisplayUserIDRequest - (*ApplyPrettyDisplayUserIDResponse)(nil), // 143: hyapp.user.v1.ApplyPrettyDisplayUserIDResponse - (*ExpirePrettyDisplayUserIDRequest)(nil), // 144: hyapp.user.v1.ExpirePrettyDisplayUserIDRequest - (*ExpirePrettyDisplayUserIDResponse)(nil), // 145: hyapp.user.v1.ExpirePrettyDisplayUserIDResponse - (*PrettyDisplayIDPool)(nil), // 146: hyapp.user.v1.PrettyDisplayIDPool - (*PrettyDisplayID)(nil), // 147: hyapp.user.v1.PrettyDisplayID - (*PrettyDisplayIDGenerationBatch)(nil), // 148: hyapp.user.v1.PrettyDisplayIDGenerationBatch - (*ListAvailablePrettyDisplayIDsRequest)(nil), // 149: hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest - (*ListAvailablePrettyDisplayIDsResponse)(nil), // 150: hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse - (*ApplyPrettyDisplayIDFromPoolRequest)(nil), // 151: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest - (*ApplyPrettyDisplayIDFromPoolResponse)(nil), // 152: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse - (*ListPrettyDisplayIDPoolsRequest)(nil), // 153: hyapp.user.v1.ListPrettyDisplayIDPoolsRequest - (*ListPrettyDisplayIDPoolsResponse)(nil), // 154: hyapp.user.v1.ListPrettyDisplayIDPoolsResponse - (*CreatePrettyDisplayIDPoolRequest)(nil), // 155: hyapp.user.v1.CreatePrettyDisplayIDPoolRequest - (*UpdatePrettyDisplayIDPoolRequest)(nil), // 156: hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest - (*PrettyDisplayIDPoolResponse)(nil), // 157: hyapp.user.v1.PrettyDisplayIDPoolResponse - (*GeneratePrettyDisplayIDsRequest)(nil), // 158: hyapp.user.v1.GeneratePrettyDisplayIDsRequest - (*GeneratePrettyDisplayIDsResponse)(nil), // 159: hyapp.user.v1.GeneratePrettyDisplayIDsResponse - (*ListPrettyDisplayIDsRequest)(nil), // 160: hyapp.user.v1.ListPrettyDisplayIDsRequest - (*ListPrettyDisplayIDsResponse)(nil), // 161: hyapp.user.v1.ListPrettyDisplayIDsResponse - (*SetPrettyDisplayIDStatusRequest)(nil), // 162: hyapp.user.v1.SetPrettyDisplayIDStatusRequest - (*RecyclePrettyDisplayIDRequest)(nil), // 163: hyapp.user.v1.RecyclePrettyDisplayIDRequest - (*PrettyDisplayIDResponse)(nil), // 164: hyapp.user.v1.PrettyDisplayIDResponse - (*AdminGrantPrettyDisplayIDRequest)(nil), // 165: hyapp.user.v1.AdminGrantPrettyDisplayIDRequest - (*AdminGrantPrettyDisplayIDResponse)(nil), // 166: hyapp.user.v1.AdminGrantPrettyDisplayIDResponse - (*ListAppsRequest)(nil), // 167: hyapp.user.v1.ListAppsRequest - (*ListAppsResponse)(nil), // 168: hyapp.user.v1.ListAppsResponse - nil, // 169: hyapp.user.v1.BatchGetUsersResponse.UsersEntry - nil, // 170: hyapp.user.v1.BatchGetRoomBasicUsersResponse.UsersEntry + (*ActiveUserBanSummary)(nil), // 83: hyapp.user.v1.ActiveUserBanSummary + (*UserAdminProfile)(nil), // 84: hyapp.user.v1.UserAdminProfile + (*BatchGetUserAdminProfilesRequest)(nil), // 85: hyapp.user.v1.BatchGetUserAdminProfilesRequest + (*BatchGetUserAdminProfilesResponse)(nil), // 86: hyapp.user.v1.BatchGetUserAdminProfilesResponse + (*AdminIssueUserAccessTokenRequest)(nil), // 87: hyapp.user.v1.AdminIssueUserAccessTokenRequest + (*AdminIssueUserAccessTokenResponse)(nil), // 88: hyapp.user.v1.AdminIssueUserAccessTokenResponse + (*RoomBasicUser)(nil), // 89: hyapp.user.v1.RoomBasicUser + (*BatchGetRoomBasicUsersRequest)(nil), // 90: hyapp.user.v1.BatchGetRoomBasicUsersRequest + (*BatchGetRoomBasicUsersResponse)(nil), // 91: hyapp.user.v1.BatchGetRoomBasicUsersResponse + (*ListUserIDsRequest)(nil), // 92: hyapp.user.v1.ListUserIDsRequest + (*ListUserIDsResponse)(nil), // 93: hyapp.user.v1.ListUserIDsResponse + (*UpdateUserProfileRequest)(nil), // 94: hyapp.user.v1.UpdateUserProfileRequest + (*UpdateUserProfileResponse)(nil), // 95: hyapp.user.v1.UpdateUserProfileResponse + (*UpdateUserProfileBackgroundRequest)(nil), // 96: hyapp.user.v1.UpdateUserProfileBackgroundRequest + (*UpdateUserProfileBackgroundResponse)(nil), // 97: hyapp.user.v1.UpdateUserProfileBackgroundResponse + (*UpdateUserContactInfoRequest)(nil), // 98: hyapp.user.v1.UpdateUserContactInfoRequest + (*UpdateUserContactInfoResponse)(nil), // 99: hyapp.user.v1.UpdateUserContactInfoResponse + (*UpdateUserWithdrawAddressRequest)(nil), // 100: hyapp.user.v1.UpdateUserWithdrawAddressRequest + (*UpdateUserWithdrawAddressResponse)(nil), // 101: hyapp.user.v1.UpdateUserWithdrawAddressResponse + (*ChangeUserCountryRequest)(nil), // 102: hyapp.user.v1.ChangeUserCountryRequest + (*AdminChangeUserCountryRequest)(nil), // 103: hyapp.user.v1.AdminChangeUserCountryRequest + (*ChangeUserCountryResponse)(nil), // 104: hyapp.user.v1.ChangeUserCountryResponse + (*SetUserStatusRequest)(nil), // 105: hyapp.user.v1.SetUserStatusRequest + (*SetUserStatusResponse)(nil), // 106: hyapp.user.v1.SetUserStatusResponse + (*AdminUserBan)(nil), // 107: hyapp.user.v1.AdminUserBan + (*AdminBanUserRequest)(nil), // 108: hyapp.user.v1.AdminBanUserRequest + (*AdminBanUserResponse)(nil), // 109: hyapp.user.v1.AdminBanUserResponse + (*AdminUnbanUserRequest)(nil), // 110: hyapp.user.v1.AdminUnbanUserRequest + (*AdminUnbanUserResponse)(nil), // 111: hyapp.user.v1.AdminUnbanUserResponse + (*ManagerUserBlock)(nil), // 112: hyapp.user.v1.ManagerUserBlock + (*CreateManagerUserBlockRequest)(nil), // 113: hyapp.user.v1.CreateManagerUserBlockRequest + (*CreateManagerUserBlockResponse)(nil), // 114: hyapp.user.v1.CreateManagerUserBlockResponse + (*ListManagerUserBlocksRequest)(nil), // 115: hyapp.user.v1.ListManagerUserBlocksRequest + (*ListManagerUserBlocksResponse)(nil), // 116: hyapp.user.v1.ListManagerUserBlocksResponse + (*UnblockManagerUserRequest)(nil), // 117: hyapp.user.v1.UnblockManagerUserRequest + (*UnblockManagerUserResponse)(nil), // 118: hyapp.user.v1.UnblockManagerUserResponse + (*CompleteOnboardingRequest)(nil), // 119: hyapp.user.v1.CompleteOnboardingRequest + (*CompleteOnboardingResponse)(nil), // 120: hyapp.user.v1.CompleteOnboardingResponse + (*SearchInviteReferrerRequest)(nil), // 121: hyapp.user.v1.SearchInviteReferrerRequest + (*SearchInviteReferrerResponse)(nil), // 122: hyapp.user.v1.SearchInviteReferrerResponse + (*BindInviteReferrerRequest)(nil), // 123: hyapp.user.v1.BindInviteReferrerRequest + (*BindInviteReferrerResponse)(nil), // 124: hyapp.user.v1.BindInviteReferrerResponse + (*BindPushTokenRequest)(nil), // 125: hyapp.user.v1.BindPushTokenRequest + (*BindPushTokenResponse)(nil), // 126: hyapp.user.v1.BindPushTokenResponse + (*DeletePushTokenRequest)(nil), // 127: hyapp.user.v1.DeletePushTokenRequest + (*DeletePushTokenResponse)(nil), // 128: hyapp.user.v1.DeletePushTokenResponse + (*Country)(nil), // 129: hyapp.user.v1.Country + (*Region)(nil), // 130: hyapp.user.v1.Region + (*ListCountriesRequest)(nil), // 131: hyapp.user.v1.ListCountriesRequest + (*ListCountriesResponse)(nil), // 132: hyapp.user.v1.ListCountriesResponse + (*UpdateCountryRequest)(nil), // 133: hyapp.user.v1.UpdateCountryRequest + (*CountryResponse)(nil), // 134: hyapp.user.v1.CountryResponse + (*ListRegistrationCountriesRequest)(nil), // 135: hyapp.user.v1.ListRegistrationCountriesRequest + (*ListRegistrationCountriesResponse)(nil), // 136: hyapp.user.v1.ListRegistrationCountriesResponse + (*LoginRiskBlockedCountry)(nil), // 137: hyapp.user.v1.LoginRiskBlockedCountry + (*ListLoginRiskBlockedCountriesRequest)(nil), // 138: hyapp.user.v1.ListLoginRiskBlockedCountriesRequest + (*ListLoginRiskBlockedCountriesResponse)(nil), // 139: hyapp.user.v1.ListLoginRiskBlockedCountriesResponse + (*ListRegionsRequest)(nil), // 140: hyapp.user.v1.ListRegionsRequest + (*ListRegionsResponse)(nil), // 141: hyapp.user.v1.ListRegionsResponse + (*GetRegionRequest)(nil), // 142: hyapp.user.v1.GetRegionRequest + (*UpdateRegionRequest)(nil), // 143: hyapp.user.v1.UpdateRegionRequest + (*ReplaceRegionCountriesRequest)(nil), // 144: hyapp.user.v1.ReplaceRegionCountriesRequest + (*RegionResponse)(nil), // 145: hyapp.user.v1.RegionResponse + (*UserIdentity)(nil), // 146: hyapp.user.v1.UserIdentity + (*GetUserIdentityRequest)(nil), // 147: hyapp.user.v1.GetUserIdentityRequest + (*GetUserIdentityResponse)(nil), // 148: hyapp.user.v1.GetUserIdentityResponse + (*ResolveDisplayUserIDRequest)(nil), // 149: hyapp.user.v1.ResolveDisplayUserIDRequest + (*ResolveDisplayUserIDResponse)(nil), // 150: hyapp.user.v1.ResolveDisplayUserIDResponse + (*ChangeDisplayUserIDRequest)(nil), // 151: hyapp.user.v1.ChangeDisplayUserIDRequest + (*ChangeDisplayUserIDResponse)(nil), // 152: hyapp.user.v1.ChangeDisplayUserIDResponse + (*ApplyPrettyDisplayUserIDRequest)(nil), // 153: hyapp.user.v1.ApplyPrettyDisplayUserIDRequest + (*ApplyPrettyDisplayUserIDResponse)(nil), // 154: hyapp.user.v1.ApplyPrettyDisplayUserIDResponse + (*ExpirePrettyDisplayUserIDRequest)(nil), // 155: hyapp.user.v1.ExpirePrettyDisplayUserIDRequest + (*ExpirePrettyDisplayUserIDResponse)(nil), // 156: hyapp.user.v1.ExpirePrettyDisplayUserIDResponse + (*PrettyDisplayIDPool)(nil), // 157: hyapp.user.v1.PrettyDisplayIDPool + (*PrettyDisplayID)(nil), // 158: hyapp.user.v1.PrettyDisplayID + (*PrettyDisplayIDGenerationBatch)(nil), // 159: hyapp.user.v1.PrettyDisplayIDGenerationBatch + (*ListAvailablePrettyDisplayIDsRequest)(nil), // 160: hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest + (*ListAvailablePrettyDisplayIDsResponse)(nil), // 161: hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse + (*ApplyPrettyDisplayIDFromPoolRequest)(nil), // 162: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest + (*ApplyPrettyDisplayIDFromPoolResponse)(nil), // 163: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse + (*ListPrettyDisplayIDPoolsRequest)(nil), // 164: hyapp.user.v1.ListPrettyDisplayIDPoolsRequest + (*ListPrettyDisplayIDPoolsResponse)(nil), // 165: hyapp.user.v1.ListPrettyDisplayIDPoolsResponse + (*CreatePrettyDisplayIDPoolRequest)(nil), // 166: hyapp.user.v1.CreatePrettyDisplayIDPoolRequest + (*UpdatePrettyDisplayIDPoolRequest)(nil), // 167: hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest + (*PrettyDisplayIDPoolResponse)(nil), // 168: hyapp.user.v1.PrettyDisplayIDPoolResponse + (*GeneratePrettyDisplayIDsRequest)(nil), // 169: hyapp.user.v1.GeneratePrettyDisplayIDsRequest + (*GeneratePrettyDisplayIDsResponse)(nil), // 170: hyapp.user.v1.GeneratePrettyDisplayIDsResponse + (*ListPrettyDisplayIDsRequest)(nil), // 171: hyapp.user.v1.ListPrettyDisplayIDsRequest + (*ListPrettyDisplayIDsResponse)(nil), // 172: hyapp.user.v1.ListPrettyDisplayIDsResponse + (*SetPrettyDisplayIDStatusRequest)(nil), // 173: hyapp.user.v1.SetPrettyDisplayIDStatusRequest + (*RecyclePrettyDisplayIDRequest)(nil), // 174: hyapp.user.v1.RecyclePrettyDisplayIDRequest + (*PrettyDisplayIDResponse)(nil), // 175: hyapp.user.v1.PrettyDisplayIDResponse + (*AdminGrantPrettyDisplayIDRequest)(nil), // 176: hyapp.user.v1.AdminGrantPrettyDisplayIDRequest + (*AdminGrantPrettyDisplayIDResponse)(nil), // 177: hyapp.user.v1.AdminGrantPrettyDisplayIDResponse + (*ListAppsRequest)(nil), // 178: hyapp.user.v1.ListAppsRequest + (*ListAppsResponse)(nil), // 179: hyapp.user.v1.ListAppsResponse + nil, // 180: hyapp.user.v1.BatchGetUsersResponse.UsersEntry + nil, // 181: hyapp.user.v1.BatchGetUserAdminProfilesResponse.ProfilesEntry + nil, // 182: hyapp.user.v1.BatchGetRoomBasicUsersResponse.UsersEntry } var file_proto_user_v1_user_proto_depIdxs = []int32{ 1, // 0: hyapp.user.v1.ResolveAppRequest.meta:type_name -> hyapp.user.v1.RequestMeta @@ -15202,243 +16445,268 @@ var file_proto_user_v1_user_proto_depIdxs = []int32{ 1, // 68: hyapp.user.v1.SubmitReportRequest.meta:type_name -> hyapp.user.v1.RequestMeta 78, // 69: hyapp.user.v1.SubmitReportResponse.report:type_name -> hyapp.user.v1.UserReport 1, // 70: hyapp.user.v1.BatchGetUsersRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 169, // 71: hyapp.user.v1.BatchGetUsersResponse.users:type_name -> hyapp.user.v1.BatchGetUsersResponse.UsersEntry - 1, // 72: hyapp.user.v1.BatchGetRoomBasicUsersRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 170, // 73: hyapp.user.v1.BatchGetRoomBasicUsersResponse.users:type_name -> hyapp.user.v1.BatchGetRoomBasicUsersResponse.UsersEntry - 1, // 74: hyapp.user.v1.ListUserIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 75: hyapp.user.v1.UpdateUserProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 5, // 76: hyapp.user.v1.UpdateUserProfileResponse.user:type_name -> hyapp.user.v1.User - 1, // 77: hyapp.user.v1.UpdateUserProfileBackgroundRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 5, // 78: hyapp.user.v1.UpdateUserProfileBackgroundResponse.user:type_name -> hyapp.user.v1.User - 1, // 79: hyapp.user.v1.UpdateUserContactInfoRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 5, // 80: hyapp.user.v1.UpdateUserContactInfoResponse.user:type_name -> hyapp.user.v1.User - 1, // 81: hyapp.user.v1.UpdateUserWithdrawAddressRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 5, // 82: hyapp.user.v1.UpdateUserWithdrawAddressResponse.user:type_name -> hyapp.user.v1.User - 1, // 83: hyapp.user.v1.ChangeUserCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 84: hyapp.user.v1.AdminChangeUserCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 5, // 85: hyapp.user.v1.ChangeUserCountryResponse.user:type_name -> hyapp.user.v1.User - 1, // 86: hyapp.user.v1.SetUserStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 0, // 87: hyapp.user.v1.SetUserStatusRequest.status:type_name -> hyapp.user.v1.UserStatus - 5, // 88: hyapp.user.v1.SetUserStatusResponse.user:type_name -> hyapp.user.v1.User - 1, // 89: hyapp.user.v1.CreateManagerUserBlockRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 101, // 90: hyapp.user.v1.CreateManagerUserBlockResponse.block:type_name -> hyapp.user.v1.ManagerUserBlock - 100, // 91: hyapp.user.v1.CreateManagerUserBlockResponse.status:type_name -> hyapp.user.v1.SetUserStatusResponse - 1, // 92: hyapp.user.v1.ListManagerUserBlocksRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 101, // 93: hyapp.user.v1.ListManagerUserBlocksResponse.blocks:type_name -> hyapp.user.v1.ManagerUserBlock - 1, // 94: hyapp.user.v1.UnblockManagerUserRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 101, // 95: hyapp.user.v1.UnblockManagerUserResponse.block:type_name -> hyapp.user.v1.ManagerUserBlock - 100, // 96: hyapp.user.v1.UnblockManagerUserResponse.status:type_name -> hyapp.user.v1.SetUserStatusResponse - 1, // 97: hyapp.user.v1.CompleteOnboardingRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 5, // 98: hyapp.user.v1.CompleteOnboardingResponse.user:type_name -> hyapp.user.v1.User - 16, // 99: hyapp.user.v1.CompleteOnboardingResponse.token:type_name -> hyapp.user.v1.AuthToken - 7, // 100: hyapp.user.v1.CompleteOnboardingResponse.invite:type_name -> hyapp.user.v1.InviteBinding - 1, // 101: hyapp.user.v1.SearchInviteReferrerRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 5, // 102: hyapp.user.v1.SearchInviteReferrerResponse.inviter:type_name -> hyapp.user.v1.User - 1, // 103: hyapp.user.v1.BindInviteReferrerRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 7, // 104: hyapp.user.v1.BindInviteReferrerResponse.invite:type_name -> hyapp.user.v1.InviteBinding - 5, // 105: hyapp.user.v1.BindInviteReferrerResponse.inviter:type_name -> hyapp.user.v1.User - 1, // 106: hyapp.user.v1.BindPushTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 107: hyapp.user.v1.DeletePushTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 108: hyapp.user.v1.ListCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 118, // 109: hyapp.user.v1.ListCountriesResponse.countries:type_name -> hyapp.user.v1.Country - 1, // 110: hyapp.user.v1.UpdateCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 118, // 111: hyapp.user.v1.CountryResponse.country:type_name -> hyapp.user.v1.Country - 1, // 112: hyapp.user.v1.ListRegistrationCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 118, // 113: hyapp.user.v1.ListRegistrationCountriesResponse.countries:type_name -> hyapp.user.v1.Country - 1, // 114: hyapp.user.v1.ListLoginRiskBlockedCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 126, // 115: hyapp.user.v1.ListLoginRiskBlockedCountriesResponse.countries:type_name -> hyapp.user.v1.LoginRiskBlockedCountry - 1, // 116: hyapp.user.v1.ListRegionsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 119, // 117: hyapp.user.v1.ListRegionsResponse.regions:type_name -> hyapp.user.v1.Region - 1, // 118: hyapp.user.v1.GetRegionRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 119: hyapp.user.v1.UpdateRegionRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 120: hyapp.user.v1.ReplaceRegionCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 119, // 121: hyapp.user.v1.RegionResponse.region:type_name -> hyapp.user.v1.Region - 1, // 122: hyapp.user.v1.GetUserIdentityRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 135, // 123: hyapp.user.v1.GetUserIdentityResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 1, // 124: hyapp.user.v1.ResolveDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 135, // 125: hyapp.user.v1.ResolveDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 1, // 126: hyapp.user.v1.ChangeDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 135, // 127: hyapp.user.v1.ChangeDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 1, // 128: hyapp.user.v1.ApplyPrettyDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 135, // 129: hyapp.user.v1.ApplyPrettyDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 1, // 130: hyapp.user.v1.ExpirePrettyDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 135, // 131: hyapp.user.v1.ExpirePrettyDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 146, // 132: hyapp.user.v1.PrettyDisplayID.pool:type_name -> hyapp.user.v1.PrettyDisplayIDPool - 1, // 133: hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 147, // 134: hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayID - 1, // 135: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 135, // 136: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 1, // 137: hyapp.user.v1.ListPrettyDisplayIDPoolsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 146, // 138: hyapp.user.v1.ListPrettyDisplayIDPoolsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayIDPool - 1, // 139: hyapp.user.v1.CreatePrettyDisplayIDPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 140: hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 146, // 141: hyapp.user.v1.PrettyDisplayIDPoolResponse.pool:type_name -> hyapp.user.v1.PrettyDisplayIDPool - 1, // 142: hyapp.user.v1.GeneratePrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 148, // 143: hyapp.user.v1.GeneratePrettyDisplayIDsResponse.batch:type_name -> hyapp.user.v1.PrettyDisplayIDGenerationBatch - 1, // 144: hyapp.user.v1.ListPrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 147, // 145: hyapp.user.v1.ListPrettyDisplayIDsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayID - 1, // 146: hyapp.user.v1.SetPrettyDisplayIDStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 147: hyapp.user.v1.RecyclePrettyDisplayIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 147, // 148: hyapp.user.v1.PrettyDisplayIDResponse.item:type_name -> hyapp.user.v1.PrettyDisplayID - 1, // 149: hyapp.user.v1.AdminGrantPrettyDisplayIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 135, // 150: hyapp.user.v1.AdminGrantPrettyDisplayIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 1, // 151: hyapp.user.v1.ListAppsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 2, // 152: hyapp.user.v1.ListAppsResponse.apps:type_name -> hyapp.user.v1.App - 5, // 153: hyapp.user.v1.BatchGetUsersResponse.UsersEntry.value:type_name -> hyapp.user.v1.User - 83, // 154: hyapp.user.v1.BatchGetRoomBasicUsersResponse.UsersEntry.value:type_name -> hyapp.user.v1.RoomBasicUser - 17, // 155: hyapp.user.v1.UserService.GetUser:input_type -> hyapp.user.v1.GetUserRequest - 9, // 156: hyapp.user.v1.UserService.GetInviteAttribution:input_type -> hyapp.user.v1.GetInviteAttributionRequest - 19, // 157: hyapp.user.v1.UserService.BusinessUserLookup:input_type -> hyapp.user.v1.BusinessUserLookupRequest - 23, // 158: hyapp.user.v1.UserService.GetMyProfileStats:input_type -> hyapp.user.v1.GetMyProfileStatsRequest - 81, // 159: hyapp.user.v1.UserService.BatchGetUsers:input_type -> hyapp.user.v1.BatchGetUsersRequest - 84, // 160: hyapp.user.v1.UserService.BatchGetRoomBasicUsers:input_type -> hyapp.user.v1.BatchGetRoomBasicUsersRequest - 86, // 161: hyapp.user.v1.UserService.ListUserIDs:input_type -> hyapp.user.v1.ListUserIDsRequest - 12, // 162: hyapp.user.v1.UserService.GetUserMicLifetimeStats:input_type -> hyapp.user.v1.GetUserMicLifetimeStatsRequest - 88, // 163: hyapp.user.v1.UserService.UpdateUserProfile:input_type -> hyapp.user.v1.UpdateUserProfileRequest - 90, // 164: hyapp.user.v1.UserService.UpdateUserProfileBackground:input_type -> hyapp.user.v1.UpdateUserProfileBackgroundRequest - 92, // 165: hyapp.user.v1.UserService.UpdateUserContactInfo:input_type -> hyapp.user.v1.UpdateUserContactInfoRequest - 94, // 166: hyapp.user.v1.UserService.UpdateUserWithdrawAddress:input_type -> hyapp.user.v1.UpdateUserWithdrawAddressRequest - 96, // 167: hyapp.user.v1.UserService.ChangeUserCountry:input_type -> hyapp.user.v1.ChangeUserCountryRequest - 97, // 168: hyapp.user.v1.UserService.AdminChangeUserCountry:input_type -> hyapp.user.v1.AdminChangeUserCountryRequest - 99, // 169: hyapp.user.v1.UserService.SetUserStatus:input_type -> hyapp.user.v1.SetUserStatusRequest - 102, // 170: hyapp.user.v1.UserService.CreateManagerUserBlock:input_type -> hyapp.user.v1.CreateManagerUserBlockRequest - 104, // 171: hyapp.user.v1.UserService.ListManagerUserBlocks:input_type -> hyapp.user.v1.ListManagerUserBlocksRequest - 106, // 172: hyapp.user.v1.UserService.UnblockManagerUser:input_type -> hyapp.user.v1.UnblockManagerUserRequest - 108, // 173: hyapp.user.v1.UserService.CompleteOnboarding:input_type -> hyapp.user.v1.CompleteOnboardingRequest - 110, // 174: hyapp.user.v1.UserService.SearchInviteReferrer:input_type -> hyapp.user.v1.SearchInviteReferrerRequest - 112, // 175: hyapp.user.v1.UserService.BindInviteReferrer:input_type -> hyapp.user.v1.BindInviteReferrerRequest - 25, // 176: hyapp.user.v1.UserSocialService.RecordProfileVisit:input_type -> hyapp.user.v1.RecordProfileVisitRequest - 28, // 177: hyapp.user.v1.UserSocialService.ListProfileVisitors:input_type -> hyapp.user.v1.ListProfileVisitorsRequest - 30, // 178: hyapp.user.v1.UserSocialService.FollowUser:input_type -> hyapp.user.v1.FollowUserRequest - 32, // 179: hyapp.user.v1.UserSocialService.UnfollowUser:input_type -> hyapp.user.v1.UnfollowUserRequest - 35, // 180: hyapp.user.v1.UserSocialService.ListFollowing:input_type -> hyapp.user.v1.ListFollowingRequest - 37, // 181: hyapp.user.v1.UserSocialService.ApplyFriend:input_type -> hyapp.user.v1.ApplyFriendRequest - 39, // 182: hyapp.user.v1.UserSocialService.AcceptFriendApplication:input_type -> hyapp.user.v1.AcceptFriendApplicationRequest - 41, // 183: hyapp.user.v1.UserSocialService.DeleteFriend:input_type -> hyapp.user.v1.DeleteFriendRequest - 44, // 184: hyapp.user.v1.UserSocialService.ListFriends:input_type -> hyapp.user.v1.ListFriendsRequest - 47, // 185: hyapp.user.v1.UserSocialService.ListFriendApplications:input_type -> hyapp.user.v1.ListFriendApplicationsRequest - 79, // 186: hyapp.user.v1.UserSocialService.SubmitReport:input_type -> hyapp.user.v1.SubmitReportRequest - 59, // 187: hyapp.user.v1.UserCPService.ListCPApplications:input_type -> hyapp.user.v1.ListCPApplicationsRequest - 61, // 188: hyapp.user.v1.UserCPService.AcceptCPApplication:input_type -> hyapp.user.v1.AcceptCPApplicationRequest - 63, // 189: hyapp.user.v1.UserCPService.RejectCPApplication:input_type -> hyapp.user.v1.RejectCPApplicationRequest - 65, // 190: hyapp.user.v1.UserCPService.ListCPRelationships:input_type -> hyapp.user.v1.ListCPRelationshipsRequest - 67, // 191: hyapp.user.v1.UserCPService.ListCPIntimacyLeaderboard:input_type -> hyapp.user.v1.ListCPIntimacyLeaderboardRequest - 69, // 192: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:input_type -> hyapp.user.v1.PrepareBreakCPRelationshipRequest - 71, // 193: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:input_type -> hyapp.user.v1.ConfirmBreakCPRelationshipRequest - 73, // 194: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:input_type -> hyapp.user.v1.CancelBreakCPRelationshipRequest - 76, // 195: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:input_type -> hyapp.user.v1.ConsumeRoomGiftCPEventRequest - 57, // 196: hyapp.user.v1.UserCPInternalService.ListCPWeeklyRankEntries:input_type -> hyapp.user.v1.ListCPWeeklyRankEntriesRequest - 14, // 197: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:input_type -> hyapp.user.v1.CronBatchRequest - 14, // 198: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:input_type -> hyapp.user.v1.CronBatchRequest - 14, // 199: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:input_type -> hyapp.user.v1.CronBatchRequest - 14, // 200: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:input_type -> hyapp.user.v1.CronBatchRequest - 14, // 201: hyapp.user.v1.UserCronService.RefreshCPIntimacyLeaderboard:input_type -> hyapp.user.v1.CronBatchRequest - 114, // 202: hyapp.user.v1.UserDeviceService.BindPushToken:input_type -> hyapp.user.v1.BindPushTokenRequest - 116, // 203: hyapp.user.v1.UserDeviceService.DeletePushToken:input_type -> hyapp.user.v1.DeletePushTokenRequest - 3, // 204: hyapp.user.v1.AppRegistryService.ResolveApp:input_type -> hyapp.user.v1.ResolveAppRequest - 167, // 205: hyapp.user.v1.AppRegistryService.ListApps:input_type -> hyapp.user.v1.ListAppsRequest - 120, // 206: hyapp.user.v1.CountryAdminService.ListCountries:input_type -> hyapp.user.v1.ListCountriesRequest - 122, // 207: hyapp.user.v1.CountryAdminService.UpdateCountry:input_type -> hyapp.user.v1.UpdateCountryRequest - 124, // 208: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:input_type -> hyapp.user.v1.ListRegistrationCountriesRequest - 127, // 209: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:input_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesRequest - 129, // 210: hyapp.user.v1.RegionAdminService.ListRegions:input_type -> hyapp.user.v1.ListRegionsRequest - 131, // 211: hyapp.user.v1.RegionAdminService.GetRegion:input_type -> hyapp.user.v1.GetRegionRequest - 132, // 212: hyapp.user.v1.RegionAdminService.UpdateRegion:input_type -> hyapp.user.v1.UpdateRegionRequest - 133, // 213: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:input_type -> hyapp.user.v1.ReplaceRegionCountriesRequest - 136, // 214: hyapp.user.v1.UserIdentityService.GetUserIdentity:input_type -> hyapp.user.v1.GetUserIdentityRequest - 138, // 215: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:input_type -> hyapp.user.v1.ResolveDisplayUserIDRequest - 140, // 216: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:input_type -> hyapp.user.v1.ChangeDisplayUserIDRequest - 142, // 217: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:input_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDRequest - 149, // 218: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:input_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest - 151, // 219: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:input_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest - 144, // 220: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:input_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDRequest - 153, // 221: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:input_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsRequest - 155, // 222: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:input_type -> hyapp.user.v1.CreatePrettyDisplayIDPoolRequest - 156, // 223: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:input_type -> hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest - 158, // 224: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:input_type -> hyapp.user.v1.GeneratePrettyDisplayIDsRequest - 160, // 225: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:input_type -> hyapp.user.v1.ListPrettyDisplayIDsRequest - 162, // 226: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:input_type -> hyapp.user.v1.SetPrettyDisplayIDStatusRequest - 163, // 227: hyapp.user.v1.UserPrettyDisplayIDAdminService.RecyclePrettyDisplayID:input_type -> hyapp.user.v1.RecyclePrettyDisplayIDRequest - 165, // 228: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:input_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDRequest - 18, // 229: hyapp.user.v1.UserService.GetUser:output_type -> hyapp.user.v1.GetUserResponse - 10, // 230: hyapp.user.v1.UserService.GetInviteAttribution:output_type -> hyapp.user.v1.GetInviteAttributionResponse - 21, // 231: hyapp.user.v1.UserService.BusinessUserLookup:output_type -> hyapp.user.v1.BusinessUserLookupResponse - 24, // 232: hyapp.user.v1.UserService.GetMyProfileStats:output_type -> hyapp.user.v1.GetMyProfileStatsResponse - 82, // 233: hyapp.user.v1.UserService.BatchGetUsers:output_type -> hyapp.user.v1.BatchGetUsersResponse - 85, // 234: hyapp.user.v1.UserService.BatchGetRoomBasicUsers:output_type -> hyapp.user.v1.BatchGetRoomBasicUsersResponse - 87, // 235: hyapp.user.v1.UserService.ListUserIDs:output_type -> hyapp.user.v1.ListUserIDsResponse - 13, // 236: hyapp.user.v1.UserService.GetUserMicLifetimeStats:output_type -> hyapp.user.v1.GetUserMicLifetimeStatsResponse - 89, // 237: hyapp.user.v1.UserService.UpdateUserProfile:output_type -> hyapp.user.v1.UpdateUserProfileResponse - 91, // 238: hyapp.user.v1.UserService.UpdateUserProfileBackground:output_type -> hyapp.user.v1.UpdateUserProfileBackgroundResponse - 93, // 239: hyapp.user.v1.UserService.UpdateUserContactInfo:output_type -> hyapp.user.v1.UpdateUserContactInfoResponse - 95, // 240: hyapp.user.v1.UserService.UpdateUserWithdrawAddress:output_type -> hyapp.user.v1.UpdateUserWithdrawAddressResponse - 98, // 241: hyapp.user.v1.UserService.ChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse - 98, // 242: hyapp.user.v1.UserService.AdminChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse - 100, // 243: hyapp.user.v1.UserService.SetUserStatus:output_type -> hyapp.user.v1.SetUserStatusResponse - 103, // 244: hyapp.user.v1.UserService.CreateManagerUserBlock:output_type -> hyapp.user.v1.CreateManagerUserBlockResponse - 105, // 245: hyapp.user.v1.UserService.ListManagerUserBlocks:output_type -> hyapp.user.v1.ListManagerUserBlocksResponse - 107, // 246: hyapp.user.v1.UserService.UnblockManagerUser:output_type -> hyapp.user.v1.UnblockManagerUserResponse - 109, // 247: hyapp.user.v1.UserService.CompleteOnboarding:output_type -> hyapp.user.v1.CompleteOnboardingResponse - 111, // 248: hyapp.user.v1.UserService.SearchInviteReferrer:output_type -> hyapp.user.v1.SearchInviteReferrerResponse - 113, // 249: hyapp.user.v1.UserService.BindInviteReferrer:output_type -> hyapp.user.v1.BindInviteReferrerResponse - 26, // 250: hyapp.user.v1.UserSocialService.RecordProfileVisit:output_type -> hyapp.user.v1.RecordProfileVisitResponse - 29, // 251: hyapp.user.v1.UserSocialService.ListProfileVisitors:output_type -> hyapp.user.v1.ListProfileVisitorsResponse - 31, // 252: hyapp.user.v1.UserSocialService.FollowUser:output_type -> hyapp.user.v1.FollowUserResponse - 33, // 253: hyapp.user.v1.UserSocialService.UnfollowUser:output_type -> hyapp.user.v1.UnfollowUserResponse - 36, // 254: hyapp.user.v1.UserSocialService.ListFollowing:output_type -> hyapp.user.v1.ListFollowingResponse - 38, // 255: hyapp.user.v1.UserSocialService.ApplyFriend:output_type -> hyapp.user.v1.ApplyFriendResponse - 40, // 256: hyapp.user.v1.UserSocialService.AcceptFriendApplication:output_type -> hyapp.user.v1.AcceptFriendApplicationResponse - 42, // 257: hyapp.user.v1.UserSocialService.DeleteFriend:output_type -> hyapp.user.v1.DeleteFriendResponse - 45, // 258: hyapp.user.v1.UserSocialService.ListFriends:output_type -> hyapp.user.v1.ListFriendsResponse - 48, // 259: hyapp.user.v1.UserSocialService.ListFriendApplications:output_type -> hyapp.user.v1.ListFriendApplicationsResponse - 80, // 260: hyapp.user.v1.UserSocialService.SubmitReport:output_type -> hyapp.user.v1.SubmitReportResponse - 60, // 261: hyapp.user.v1.UserCPService.ListCPApplications:output_type -> hyapp.user.v1.ListCPApplicationsResponse - 62, // 262: hyapp.user.v1.UserCPService.AcceptCPApplication:output_type -> hyapp.user.v1.AcceptCPApplicationResponse - 64, // 263: hyapp.user.v1.UserCPService.RejectCPApplication:output_type -> hyapp.user.v1.RejectCPApplicationResponse - 66, // 264: hyapp.user.v1.UserCPService.ListCPRelationships:output_type -> hyapp.user.v1.ListCPRelationshipsResponse - 68, // 265: hyapp.user.v1.UserCPService.ListCPIntimacyLeaderboard:output_type -> hyapp.user.v1.ListCPIntimacyLeaderboardResponse - 70, // 266: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:output_type -> hyapp.user.v1.PrepareBreakCPRelationshipResponse - 72, // 267: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:output_type -> hyapp.user.v1.ConfirmBreakCPRelationshipResponse - 74, // 268: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:output_type -> hyapp.user.v1.CancelBreakCPRelationshipResponse - 77, // 269: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:output_type -> hyapp.user.v1.ConsumeRoomGiftCPEventResponse - 58, // 270: hyapp.user.v1.UserCPInternalService.ListCPWeeklyRankEntries:output_type -> hyapp.user.v1.ListCPWeeklyRankEntriesResponse - 15, // 271: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:output_type -> hyapp.user.v1.CronBatchResponse - 15, // 272: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:output_type -> hyapp.user.v1.CronBatchResponse - 15, // 273: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:output_type -> hyapp.user.v1.CronBatchResponse - 15, // 274: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:output_type -> hyapp.user.v1.CronBatchResponse - 15, // 275: hyapp.user.v1.UserCronService.RefreshCPIntimacyLeaderboard:output_type -> hyapp.user.v1.CronBatchResponse - 115, // 276: hyapp.user.v1.UserDeviceService.BindPushToken:output_type -> hyapp.user.v1.BindPushTokenResponse - 117, // 277: hyapp.user.v1.UserDeviceService.DeletePushToken:output_type -> hyapp.user.v1.DeletePushTokenResponse - 4, // 278: hyapp.user.v1.AppRegistryService.ResolveApp:output_type -> hyapp.user.v1.ResolveAppResponse - 168, // 279: hyapp.user.v1.AppRegistryService.ListApps:output_type -> hyapp.user.v1.ListAppsResponse - 121, // 280: hyapp.user.v1.CountryAdminService.ListCountries:output_type -> hyapp.user.v1.ListCountriesResponse - 123, // 281: hyapp.user.v1.CountryAdminService.UpdateCountry:output_type -> hyapp.user.v1.CountryResponse - 125, // 282: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:output_type -> hyapp.user.v1.ListRegistrationCountriesResponse - 128, // 283: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:output_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesResponse - 130, // 284: hyapp.user.v1.RegionAdminService.ListRegions:output_type -> hyapp.user.v1.ListRegionsResponse - 134, // 285: hyapp.user.v1.RegionAdminService.GetRegion:output_type -> hyapp.user.v1.RegionResponse - 134, // 286: hyapp.user.v1.RegionAdminService.UpdateRegion:output_type -> hyapp.user.v1.RegionResponse - 134, // 287: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:output_type -> hyapp.user.v1.RegionResponse - 137, // 288: hyapp.user.v1.UserIdentityService.GetUserIdentity:output_type -> hyapp.user.v1.GetUserIdentityResponse - 139, // 289: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:output_type -> hyapp.user.v1.ResolveDisplayUserIDResponse - 141, // 290: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:output_type -> hyapp.user.v1.ChangeDisplayUserIDResponse - 143, // 291: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:output_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDResponse - 150, // 292: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:output_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse - 152, // 293: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:output_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse - 145, // 294: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:output_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDResponse - 154, // 295: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:output_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsResponse - 157, // 296: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse - 157, // 297: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse - 159, // 298: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:output_type -> hyapp.user.v1.GeneratePrettyDisplayIDsResponse - 161, // 299: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:output_type -> hyapp.user.v1.ListPrettyDisplayIDsResponse - 164, // 300: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:output_type -> hyapp.user.v1.PrettyDisplayIDResponse - 164, // 301: hyapp.user.v1.UserPrettyDisplayIDAdminService.RecyclePrettyDisplayID:output_type -> hyapp.user.v1.PrettyDisplayIDResponse - 166, // 302: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:output_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDResponse - 229, // [229:303] is the sub-list for method output_type - 155, // [155:229] is the sub-list for method input_type - 155, // [155:155] is the sub-list for extension type_name - 155, // [155:155] is the sub-list for extension extendee - 0, // [0:155] is the sub-list for field type_name + 180, // 71: hyapp.user.v1.BatchGetUsersResponse.users:type_name -> hyapp.user.v1.BatchGetUsersResponse.UsersEntry + 0, // 72: hyapp.user.v1.ActiveUserBanSummary.user_status:type_name -> hyapp.user.v1.UserStatus + 5, // 73: hyapp.user.v1.UserAdminProfile.user:type_name -> hyapp.user.v1.User + 83, // 74: hyapp.user.v1.UserAdminProfile.ban:type_name -> hyapp.user.v1.ActiveUserBanSummary + 1, // 75: hyapp.user.v1.BatchGetUserAdminProfilesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 181, // 76: hyapp.user.v1.BatchGetUserAdminProfilesResponse.profiles:type_name -> hyapp.user.v1.BatchGetUserAdminProfilesResponse.ProfilesEntry + 1, // 77: hyapp.user.v1.AdminIssueUserAccessTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 78: hyapp.user.v1.BatchGetRoomBasicUsersRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 182, // 79: hyapp.user.v1.BatchGetRoomBasicUsersResponse.users:type_name -> hyapp.user.v1.BatchGetRoomBasicUsersResponse.UsersEntry + 1, // 80: hyapp.user.v1.ListUserIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 81: hyapp.user.v1.UpdateUserProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 5, // 82: hyapp.user.v1.UpdateUserProfileResponse.user:type_name -> hyapp.user.v1.User + 1, // 83: hyapp.user.v1.UpdateUserProfileBackgroundRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 5, // 84: hyapp.user.v1.UpdateUserProfileBackgroundResponse.user:type_name -> hyapp.user.v1.User + 1, // 85: hyapp.user.v1.UpdateUserContactInfoRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 5, // 86: hyapp.user.v1.UpdateUserContactInfoResponse.user:type_name -> hyapp.user.v1.User + 1, // 87: hyapp.user.v1.UpdateUserWithdrawAddressRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 5, // 88: hyapp.user.v1.UpdateUserWithdrawAddressResponse.user:type_name -> hyapp.user.v1.User + 1, // 89: hyapp.user.v1.ChangeUserCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 90: hyapp.user.v1.AdminChangeUserCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 5, // 91: hyapp.user.v1.ChangeUserCountryResponse.user:type_name -> hyapp.user.v1.User + 1, // 92: hyapp.user.v1.SetUserStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 0, // 93: hyapp.user.v1.SetUserStatusRequest.status:type_name -> hyapp.user.v1.UserStatus + 5, // 94: hyapp.user.v1.SetUserStatusResponse.user:type_name -> hyapp.user.v1.User + 1, // 95: hyapp.user.v1.AdminBanUserRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 107, // 96: hyapp.user.v1.AdminBanUserResponse.ban:type_name -> hyapp.user.v1.AdminUserBan + 106, // 97: hyapp.user.v1.AdminBanUserResponse.status:type_name -> hyapp.user.v1.SetUserStatusResponse + 1, // 98: hyapp.user.v1.AdminUnbanUserRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 107, // 99: hyapp.user.v1.AdminUnbanUserResponse.ban:type_name -> hyapp.user.v1.AdminUserBan + 106, // 100: hyapp.user.v1.AdminUnbanUserResponse.status:type_name -> hyapp.user.v1.SetUserStatusResponse + 1, // 101: hyapp.user.v1.CreateManagerUserBlockRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 112, // 102: hyapp.user.v1.CreateManagerUserBlockResponse.block:type_name -> hyapp.user.v1.ManagerUserBlock + 106, // 103: hyapp.user.v1.CreateManagerUserBlockResponse.status:type_name -> hyapp.user.v1.SetUserStatusResponse + 1, // 104: hyapp.user.v1.ListManagerUserBlocksRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 112, // 105: hyapp.user.v1.ListManagerUserBlocksResponse.blocks:type_name -> hyapp.user.v1.ManagerUserBlock + 1, // 106: hyapp.user.v1.UnblockManagerUserRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 112, // 107: hyapp.user.v1.UnblockManagerUserResponse.block:type_name -> hyapp.user.v1.ManagerUserBlock + 106, // 108: hyapp.user.v1.UnblockManagerUserResponse.status:type_name -> hyapp.user.v1.SetUserStatusResponse + 1, // 109: hyapp.user.v1.CompleteOnboardingRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 5, // 110: hyapp.user.v1.CompleteOnboardingResponse.user:type_name -> hyapp.user.v1.User + 16, // 111: hyapp.user.v1.CompleteOnboardingResponse.token:type_name -> hyapp.user.v1.AuthToken + 7, // 112: hyapp.user.v1.CompleteOnboardingResponse.invite:type_name -> hyapp.user.v1.InviteBinding + 1, // 113: hyapp.user.v1.SearchInviteReferrerRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 5, // 114: hyapp.user.v1.SearchInviteReferrerResponse.inviter:type_name -> hyapp.user.v1.User + 1, // 115: hyapp.user.v1.BindInviteReferrerRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 7, // 116: hyapp.user.v1.BindInviteReferrerResponse.invite:type_name -> hyapp.user.v1.InviteBinding + 5, // 117: hyapp.user.v1.BindInviteReferrerResponse.inviter:type_name -> hyapp.user.v1.User + 1, // 118: hyapp.user.v1.BindPushTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 119: hyapp.user.v1.DeletePushTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 120: hyapp.user.v1.ListCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 129, // 121: hyapp.user.v1.ListCountriesResponse.countries:type_name -> hyapp.user.v1.Country + 1, // 122: hyapp.user.v1.UpdateCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 129, // 123: hyapp.user.v1.CountryResponse.country:type_name -> hyapp.user.v1.Country + 1, // 124: hyapp.user.v1.ListRegistrationCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 129, // 125: hyapp.user.v1.ListRegistrationCountriesResponse.countries:type_name -> hyapp.user.v1.Country + 1, // 126: hyapp.user.v1.ListLoginRiskBlockedCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 137, // 127: hyapp.user.v1.ListLoginRiskBlockedCountriesResponse.countries:type_name -> hyapp.user.v1.LoginRiskBlockedCountry + 1, // 128: hyapp.user.v1.ListRegionsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 130, // 129: hyapp.user.v1.ListRegionsResponse.regions:type_name -> hyapp.user.v1.Region + 1, // 130: hyapp.user.v1.GetRegionRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 131: hyapp.user.v1.UpdateRegionRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 132: hyapp.user.v1.ReplaceRegionCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 130, // 133: hyapp.user.v1.RegionResponse.region:type_name -> hyapp.user.v1.Region + 1, // 134: hyapp.user.v1.GetUserIdentityRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 146, // 135: hyapp.user.v1.GetUserIdentityResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 1, // 136: hyapp.user.v1.ResolveDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 146, // 137: hyapp.user.v1.ResolveDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 1, // 138: hyapp.user.v1.ChangeDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 146, // 139: hyapp.user.v1.ChangeDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 1, // 140: hyapp.user.v1.ApplyPrettyDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 146, // 141: hyapp.user.v1.ApplyPrettyDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 1, // 142: hyapp.user.v1.ExpirePrettyDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 146, // 143: hyapp.user.v1.ExpirePrettyDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 157, // 144: hyapp.user.v1.PrettyDisplayID.pool:type_name -> hyapp.user.v1.PrettyDisplayIDPool + 1, // 145: hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 158, // 146: hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayID + 1, // 147: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 146, // 148: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 1, // 149: hyapp.user.v1.ListPrettyDisplayIDPoolsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 157, // 150: hyapp.user.v1.ListPrettyDisplayIDPoolsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayIDPool + 1, // 151: hyapp.user.v1.CreatePrettyDisplayIDPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 152: hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 157, // 153: hyapp.user.v1.PrettyDisplayIDPoolResponse.pool:type_name -> hyapp.user.v1.PrettyDisplayIDPool + 1, // 154: hyapp.user.v1.GeneratePrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 159, // 155: hyapp.user.v1.GeneratePrettyDisplayIDsResponse.batch:type_name -> hyapp.user.v1.PrettyDisplayIDGenerationBatch + 1, // 156: hyapp.user.v1.ListPrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 158, // 157: hyapp.user.v1.ListPrettyDisplayIDsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayID + 1, // 158: hyapp.user.v1.SetPrettyDisplayIDStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 159: hyapp.user.v1.RecyclePrettyDisplayIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 158, // 160: hyapp.user.v1.PrettyDisplayIDResponse.item:type_name -> hyapp.user.v1.PrettyDisplayID + 1, // 161: hyapp.user.v1.AdminGrantPrettyDisplayIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 146, // 162: hyapp.user.v1.AdminGrantPrettyDisplayIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 1, // 163: hyapp.user.v1.ListAppsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 2, // 164: hyapp.user.v1.ListAppsResponse.apps:type_name -> hyapp.user.v1.App + 5, // 165: hyapp.user.v1.BatchGetUsersResponse.UsersEntry.value:type_name -> hyapp.user.v1.User + 84, // 166: hyapp.user.v1.BatchGetUserAdminProfilesResponse.ProfilesEntry.value:type_name -> hyapp.user.v1.UserAdminProfile + 89, // 167: hyapp.user.v1.BatchGetRoomBasicUsersResponse.UsersEntry.value:type_name -> hyapp.user.v1.RoomBasicUser + 17, // 168: hyapp.user.v1.UserService.GetUser:input_type -> hyapp.user.v1.GetUserRequest + 9, // 169: hyapp.user.v1.UserService.GetInviteAttribution:input_type -> hyapp.user.v1.GetInviteAttributionRequest + 19, // 170: hyapp.user.v1.UserService.BusinessUserLookup:input_type -> hyapp.user.v1.BusinessUserLookupRequest + 23, // 171: hyapp.user.v1.UserService.GetMyProfileStats:input_type -> hyapp.user.v1.GetMyProfileStatsRequest + 81, // 172: hyapp.user.v1.UserService.BatchGetUsers:input_type -> hyapp.user.v1.BatchGetUsersRequest + 85, // 173: hyapp.user.v1.UserService.BatchGetUserAdminProfiles:input_type -> hyapp.user.v1.BatchGetUserAdminProfilesRequest + 87, // 174: hyapp.user.v1.UserService.AdminIssueUserAccessToken:input_type -> hyapp.user.v1.AdminIssueUserAccessTokenRequest + 90, // 175: hyapp.user.v1.UserService.BatchGetRoomBasicUsers:input_type -> hyapp.user.v1.BatchGetRoomBasicUsersRequest + 92, // 176: hyapp.user.v1.UserService.ListUserIDs:input_type -> hyapp.user.v1.ListUserIDsRequest + 12, // 177: hyapp.user.v1.UserService.GetUserMicLifetimeStats:input_type -> hyapp.user.v1.GetUserMicLifetimeStatsRequest + 94, // 178: hyapp.user.v1.UserService.UpdateUserProfile:input_type -> hyapp.user.v1.UpdateUserProfileRequest + 96, // 179: hyapp.user.v1.UserService.UpdateUserProfileBackground:input_type -> hyapp.user.v1.UpdateUserProfileBackgroundRequest + 98, // 180: hyapp.user.v1.UserService.UpdateUserContactInfo:input_type -> hyapp.user.v1.UpdateUserContactInfoRequest + 100, // 181: hyapp.user.v1.UserService.UpdateUserWithdrawAddress:input_type -> hyapp.user.v1.UpdateUserWithdrawAddressRequest + 102, // 182: hyapp.user.v1.UserService.ChangeUserCountry:input_type -> hyapp.user.v1.ChangeUserCountryRequest + 103, // 183: hyapp.user.v1.UserService.AdminChangeUserCountry:input_type -> hyapp.user.v1.AdminChangeUserCountryRequest + 105, // 184: hyapp.user.v1.UserService.SetUserStatus:input_type -> hyapp.user.v1.SetUserStatusRequest + 108, // 185: hyapp.user.v1.UserService.AdminBanUser:input_type -> hyapp.user.v1.AdminBanUserRequest + 110, // 186: hyapp.user.v1.UserService.AdminUnbanUser:input_type -> hyapp.user.v1.AdminUnbanUserRequest + 113, // 187: hyapp.user.v1.UserService.CreateManagerUserBlock:input_type -> hyapp.user.v1.CreateManagerUserBlockRequest + 115, // 188: hyapp.user.v1.UserService.ListManagerUserBlocks:input_type -> hyapp.user.v1.ListManagerUserBlocksRequest + 117, // 189: hyapp.user.v1.UserService.UnblockManagerUser:input_type -> hyapp.user.v1.UnblockManagerUserRequest + 119, // 190: hyapp.user.v1.UserService.CompleteOnboarding:input_type -> hyapp.user.v1.CompleteOnboardingRequest + 121, // 191: hyapp.user.v1.UserService.SearchInviteReferrer:input_type -> hyapp.user.v1.SearchInviteReferrerRequest + 123, // 192: hyapp.user.v1.UserService.BindInviteReferrer:input_type -> hyapp.user.v1.BindInviteReferrerRequest + 25, // 193: hyapp.user.v1.UserSocialService.RecordProfileVisit:input_type -> hyapp.user.v1.RecordProfileVisitRequest + 28, // 194: hyapp.user.v1.UserSocialService.ListProfileVisitors:input_type -> hyapp.user.v1.ListProfileVisitorsRequest + 30, // 195: hyapp.user.v1.UserSocialService.FollowUser:input_type -> hyapp.user.v1.FollowUserRequest + 32, // 196: hyapp.user.v1.UserSocialService.UnfollowUser:input_type -> hyapp.user.v1.UnfollowUserRequest + 35, // 197: hyapp.user.v1.UserSocialService.ListFollowing:input_type -> hyapp.user.v1.ListFollowingRequest + 37, // 198: hyapp.user.v1.UserSocialService.ApplyFriend:input_type -> hyapp.user.v1.ApplyFriendRequest + 39, // 199: hyapp.user.v1.UserSocialService.AcceptFriendApplication:input_type -> hyapp.user.v1.AcceptFriendApplicationRequest + 41, // 200: hyapp.user.v1.UserSocialService.DeleteFriend:input_type -> hyapp.user.v1.DeleteFriendRequest + 44, // 201: hyapp.user.v1.UserSocialService.ListFriends:input_type -> hyapp.user.v1.ListFriendsRequest + 47, // 202: hyapp.user.v1.UserSocialService.ListFriendApplications:input_type -> hyapp.user.v1.ListFriendApplicationsRequest + 79, // 203: hyapp.user.v1.UserSocialService.SubmitReport:input_type -> hyapp.user.v1.SubmitReportRequest + 59, // 204: hyapp.user.v1.UserCPService.ListCPApplications:input_type -> hyapp.user.v1.ListCPApplicationsRequest + 61, // 205: hyapp.user.v1.UserCPService.AcceptCPApplication:input_type -> hyapp.user.v1.AcceptCPApplicationRequest + 63, // 206: hyapp.user.v1.UserCPService.RejectCPApplication:input_type -> hyapp.user.v1.RejectCPApplicationRequest + 65, // 207: hyapp.user.v1.UserCPService.ListCPRelationships:input_type -> hyapp.user.v1.ListCPRelationshipsRequest + 67, // 208: hyapp.user.v1.UserCPService.ListCPIntimacyLeaderboard:input_type -> hyapp.user.v1.ListCPIntimacyLeaderboardRequest + 69, // 209: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:input_type -> hyapp.user.v1.PrepareBreakCPRelationshipRequest + 71, // 210: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:input_type -> hyapp.user.v1.ConfirmBreakCPRelationshipRequest + 73, // 211: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:input_type -> hyapp.user.v1.CancelBreakCPRelationshipRequest + 76, // 212: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:input_type -> hyapp.user.v1.ConsumeRoomGiftCPEventRequest + 57, // 213: hyapp.user.v1.UserCPInternalService.ListCPWeeklyRankEntries:input_type -> hyapp.user.v1.ListCPWeeklyRankEntriesRequest + 14, // 214: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:input_type -> hyapp.user.v1.CronBatchRequest + 14, // 215: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:input_type -> hyapp.user.v1.CronBatchRequest + 14, // 216: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:input_type -> hyapp.user.v1.CronBatchRequest + 14, // 217: hyapp.user.v1.UserCronService.CompensateRoomOpenSessions:input_type -> hyapp.user.v1.CronBatchRequest + 14, // 218: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:input_type -> hyapp.user.v1.CronBatchRequest + 14, // 219: hyapp.user.v1.UserCronService.ExpireAdminUserBans:input_type -> hyapp.user.v1.CronBatchRequest + 14, // 220: hyapp.user.v1.UserCronService.RefreshCPIntimacyLeaderboard:input_type -> hyapp.user.v1.CronBatchRequest + 125, // 221: hyapp.user.v1.UserDeviceService.BindPushToken:input_type -> hyapp.user.v1.BindPushTokenRequest + 127, // 222: hyapp.user.v1.UserDeviceService.DeletePushToken:input_type -> hyapp.user.v1.DeletePushTokenRequest + 3, // 223: hyapp.user.v1.AppRegistryService.ResolveApp:input_type -> hyapp.user.v1.ResolveAppRequest + 178, // 224: hyapp.user.v1.AppRegistryService.ListApps:input_type -> hyapp.user.v1.ListAppsRequest + 131, // 225: hyapp.user.v1.CountryAdminService.ListCountries:input_type -> hyapp.user.v1.ListCountriesRequest + 133, // 226: hyapp.user.v1.CountryAdminService.UpdateCountry:input_type -> hyapp.user.v1.UpdateCountryRequest + 135, // 227: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:input_type -> hyapp.user.v1.ListRegistrationCountriesRequest + 138, // 228: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:input_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesRequest + 140, // 229: hyapp.user.v1.RegionAdminService.ListRegions:input_type -> hyapp.user.v1.ListRegionsRequest + 142, // 230: hyapp.user.v1.RegionAdminService.GetRegion:input_type -> hyapp.user.v1.GetRegionRequest + 143, // 231: hyapp.user.v1.RegionAdminService.UpdateRegion:input_type -> hyapp.user.v1.UpdateRegionRequest + 144, // 232: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:input_type -> hyapp.user.v1.ReplaceRegionCountriesRequest + 147, // 233: hyapp.user.v1.UserIdentityService.GetUserIdentity:input_type -> hyapp.user.v1.GetUserIdentityRequest + 149, // 234: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:input_type -> hyapp.user.v1.ResolveDisplayUserIDRequest + 151, // 235: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:input_type -> hyapp.user.v1.ChangeDisplayUserIDRequest + 153, // 236: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:input_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDRequest + 160, // 237: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:input_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest + 162, // 238: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:input_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest + 155, // 239: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:input_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDRequest + 164, // 240: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:input_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsRequest + 166, // 241: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:input_type -> hyapp.user.v1.CreatePrettyDisplayIDPoolRequest + 167, // 242: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:input_type -> hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest + 169, // 243: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:input_type -> hyapp.user.v1.GeneratePrettyDisplayIDsRequest + 171, // 244: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:input_type -> hyapp.user.v1.ListPrettyDisplayIDsRequest + 173, // 245: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:input_type -> hyapp.user.v1.SetPrettyDisplayIDStatusRequest + 174, // 246: hyapp.user.v1.UserPrettyDisplayIDAdminService.RecyclePrettyDisplayID:input_type -> hyapp.user.v1.RecyclePrettyDisplayIDRequest + 176, // 247: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:input_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDRequest + 18, // 248: hyapp.user.v1.UserService.GetUser:output_type -> hyapp.user.v1.GetUserResponse + 10, // 249: hyapp.user.v1.UserService.GetInviteAttribution:output_type -> hyapp.user.v1.GetInviteAttributionResponse + 21, // 250: hyapp.user.v1.UserService.BusinessUserLookup:output_type -> hyapp.user.v1.BusinessUserLookupResponse + 24, // 251: hyapp.user.v1.UserService.GetMyProfileStats:output_type -> hyapp.user.v1.GetMyProfileStatsResponse + 82, // 252: hyapp.user.v1.UserService.BatchGetUsers:output_type -> hyapp.user.v1.BatchGetUsersResponse + 86, // 253: hyapp.user.v1.UserService.BatchGetUserAdminProfiles:output_type -> hyapp.user.v1.BatchGetUserAdminProfilesResponse + 88, // 254: hyapp.user.v1.UserService.AdminIssueUserAccessToken:output_type -> hyapp.user.v1.AdminIssueUserAccessTokenResponse + 91, // 255: hyapp.user.v1.UserService.BatchGetRoomBasicUsers:output_type -> hyapp.user.v1.BatchGetRoomBasicUsersResponse + 93, // 256: hyapp.user.v1.UserService.ListUserIDs:output_type -> hyapp.user.v1.ListUserIDsResponse + 13, // 257: hyapp.user.v1.UserService.GetUserMicLifetimeStats:output_type -> hyapp.user.v1.GetUserMicLifetimeStatsResponse + 95, // 258: hyapp.user.v1.UserService.UpdateUserProfile:output_type -> hyapp.user.v1.UpdateUserProfileResponse + 97, // 259: hyapp.user.v1.UserService.UpdateUserProfileBackground:output_type -> hyapp.user.v1.UpdateUserProfileBackgroundResponse + 99, // 260: hyapp.user.v1.UserService.UpdateUserContactInfo:output_type -> hyapp.user.v1.UpdateUserContactInfoResponse + 101, // 261: hyapp.user.v1.UserService.UpdateUserWithdrawAddress:output_type -> hyapp.user.v1.UpdateUserWithdrawAddressResponse + 104, // 262: hyapp.user.v1.UserService.ChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse + 104, // 263: hyapp.user.v1.UserService.AdminChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse + 106, // 264: hyapp.user.v1.UserService.SetUserStatus:output_type -> hyapp.user.v1.SetUserStatusResponse + 109, // 265: hyapp.user.v1.UserService.AdminBanUser:output_type -> hyapp.user.v1.AdminBanUserResponse + 111, // 266: hyapp.user.v1.UserService.AdminUnbanUser:output_type -> hyapp.user.v1.AdminUnbanUserResponse + 114, // 267: hyapp.user.v1.UserService.CreateManagerUserBlock:output_type -> hyapp.user.v1.CreateManagerUserBlockResponse + 116, // 268: hyapp.user.v1.UserService.ListManagerUserBlocks:output_type -> hyapp.user.v1.ListManagerUserBlocksResponse + 118, // 269: hyapp.user.v1.UserService.UnblockManagerUser:output_type -> hyapp.user.v1.UnblockManagerUserResponse + 120, // 270: hyapp.user.v1.UserService.CompleteOnboarding:output_type -> hyapp.user.v1.CompleteOnboardingResponse + 122, // 271: hyapp.user.v1.UserService.SearchInviteReferrer:output_type -> hyapp.user.v1.SearchInviteReferrerResponse + 124, // 272: hyapp.user.v1.UserService.BindInviteReferrer:output_type -> hyapp.user.v1.BindInviteReferrerResponse + 26, // 273: hyapp.user.v1.UserSocialService.RecordProfileVisit:output_type -> hyapp.user.v1.RecordProfileVisitResponse + 29, // 274: hyapp.user.v1.UserSocialService.ListProfileVisitors:output_type -> hyapp.user.v1.ListProfileVisitorsResponse + 31, // 275: hyapp.user.v1.UserSocialService.FollowUser:output_type -> hyapp.user.v1.FollowUserResponse + 33, // 276: hyapp.user.v1.UserSocialService.UnfollowUser:output_type -> hyapp.user.v1.UnfollowUserResponse + 36, // 277: hyapp.user.v1.UserSocialService.ListFollowing:output_type -> hyapp.user.v1.ListFollowingResponse + 38, // 278: hyapp.user.v1.UserSocialService.ApplyFriend:output_type -> hyapp.user.v1.ApplyFriendResponse + 40, // 279: hyapp.user.v1.UserSocialService.AcceptFriendApplication:output_type -> hyapp.user.v1.AcceptFriendApplicationResponse + 42, // 280: hyapp.user.v1.UserSocialService.DeleteFriend:output_type -> hyapp.user.v1.DeleteFriendResponse + 45, // 281: hyapp.user.v1.UserSocialService.ListFriends:output_type -> hyapp.user.v1.ListFriendsResponse + 48, // 282: hyapp.user.v1.UserSocialService.ListFriendApplications:output_type -> hyapp.user.v1.ListFriendApplicationsResponse + 80, // 283: hyapp.user.v1.UserSocialService.SubmitReport:output_type -> hyapp.user.v1.SubmitReportResponse + 60, // 284: hyapp.user.v1.UserCPService.ListCPApplications:output_type -> hyapp.user.v1.ListCPApplicationsResponse + 62, // 285: hyapp.user.v1.UserCPService.AcceptCPApplication:output_type -> hyapp.user.v1.AcceptCPApplicationResponse + 64, // 286: hyapp.user.v1.UserCPService.RejectCPApplication:output_type -> hyapp.user.v1.RejectCPApplicationResponse + 66, // 287: hyapp.user.v1.UserCPService.ListCPRelationships:output_type -> hyapp.user.v1.ListCPRelationshipsResponse + 68, // 288: hyapp.user.v1.UserCPService.ListCPIntimacyLeaderboard:output_type -> hyapp.user.v1.ListCPIntimacyLeaderboardResponse + 70, // 289: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:output_type -> hyapp.user.v1.PrepareBreakCPRelationshipResponse + 72, // 290: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:output_type -> hyapp.user.v1.ConfirmBreakCPRelationshipResponse + 74, // 291: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:output_type -> hyapp.user.v1.CancelBreakCPRelationshipResponse + 77, // 292: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:output_type -> hyapp.user.v1.ConsumeRoomGiftCPEventResponse + 58, // 293: hyapp.user.v1.UserCPInternalService.ListCPWeeklyRankEntries:output_type -> hyapp.user.v1.ListCPWeeklyRankEntriesResponse + 15, // 294: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:output_type -> hyapp.user.v1.CronBatchResponse + 15, // 295: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:output_type -> hyapp.user.v1.CronBatchResponse + 15, // 296: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:output_type -> hyapp.user.v1.CronBatchResponse + 15, // 297: hyapp.user.v1.UserCronService.CompensateRoomOpenSessions:output_type -> hyapp.user.v1.CronBatchResponse + 15, // 298: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:output_type -> hyapp.user.v1.CronBatchResponse + 15, // 299: hyapp.user.v1.UserCronService.ExpireAdminUserBans:output_type -> hyapp.user.v1.CronBatchResponse + 15, // 300: hyapp.user.v1.UserCronService.RefreshCPIntimacyLeaderboard:output_type -> hyapp.user.v1.CronBatchResponse + 126, // 301: hyapp.user.v1.UserDeviceService.BindPushToken:output_type -> hyapp.user.v1.BindPushTokenResponse + 128, // 302: hyapp.user.v1.UserDeviceService.DeletePushToken:output_type -> hyapp.user.v1.DeletePushTokenResponse + 4, // 303: hyapp.user.v1.AppRegistryService.ResolveApp:output_type -> hyapp.user.v1.ResolveAppResponse + 179, // 304: hyapp.user.v1.AppRegistryService.ListApps:output_type -> hyapp.user.v1.ListAppsResponse + 132, // 305: hyapp.user.v1.CountryAdminService.ListCountries:output_type -> hyapp.user.v1.ListCountriesResponse + 134, // 306: hyapp.user.v1.CountryAdminService.UpdateCountry:output_type -> hyapp.user.v1.CountryResponse + 136, // 307: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:output_type -> hyapp.user.v1.ListRegistrationCountriesResponse + 139, // 308: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:output_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesResponse + 141, // 309: hyapp.user.v1.RegionAdminService.ListRegions:output_type -> hyapp.user.v1.ListRegionsResponse + 145, // 310: hyapp.user.v1.RegionAdminService.GetRegion:output_type -> hyapp.user.v1.RegionResponse + 145, // 311: hyapp.user.v1.RegionAdminService.UpdateRegion:output_type -> hyapp.user.v1.RegionResponse + 145, // 312: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:output_type -> hyapp.user.v1.RegionResponse + 148, // 313: hyapp.user.v1.UserIdentityService.GetUserIdentity:output_type -> hyapp.user.v1.GetUserIdentityResponse + 150, // 314: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:output_type -> hyapp.user.v1.ResolveDisplayUserIDResponse + 152, // 315: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:output_type -> hyapp.user.v1.ChangeDisplayUserIDResponse + 154, // 316: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:output_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDResponse + 161, // 317: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:output_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse + 163, // 318: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:output_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse + 156, // 319: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:output_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDResponse + 165, // 320: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:output_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsResponse + 168, // 321: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse + 168, // 322: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse + 170, // 323: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:output_type -> hyapp.user.v1.GeneratePrettyDisplayIDsResponse + 172, // 324: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:output_type -> hyapp.user.v1.ListPrettyDisplayIDsResponse + 175, // 325: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:output_type -> hyapp.user.v1.PrettyDisplayIDResponse + 175, // 326: hyapp.user.v1.UserPrettyDisplayIDAdminService.RecyclePrettyDisplayID:output_type -> hyapp.user.v1.PrettyDisplayIDResponse + 177, // 327: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:output_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDResponse + 248, // [248:328] is the sub-list for method output_type + 168, // [168:248] is the sub-list for method input_type + 168, // [168:168] is the sub-list for extension type_name + 168, // [168:168] is the sub-list for extension extendee + 0, // [0:168] is the sub-list for field type_name } func init() { file_proto_user_v1_user_proto_init() } @@ -15446,15 +16714,15 @@ func file_proto_user_v1_user_proto_init() { if File_proto_user_v1_user_proto != nil { return } - file_proto_user_v1_user_proto_msgTypes[87].OneofWrappers = []any{} - file_proto_user_v1_user_proto_msgTypes[119].OneofWrappers = []any{} + file_proto_user_v1_user_proto_msgTypes[93].OneofWrappers = []any{} + file_proto_user_v1_user_proto_msgTypes[130].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_user_v1_user_proto_rawDesc, NumEnums: 1, - NumMessages: 170, + NumMessages: 182, NumExtensions: 0, NumServices: 12, }, diff --git a/api/proto/user/v1/user.proto b/api/proto/user/v1/user.proto index 1bf21a8f..cdaea925 100644 --- a/api/proto/user/v1/user.proto +++ b/api/proto/user/v1/user.proto @@ -19,6 +19,16 @@ message RequestMeta { string platform = 11; string language = 12; string timezone = 13; + // app_version/build_number 记录发起本次 RPC 的客户端构建;认证链路依赖它们把每次成功登录归档到 login_audit。 + string app_version = 14; + string build_number = 15; + // device_model/device_manufacturer/os_version/imei 是客户端设备资料快照,来自 gateway 的 + // X-Device-Model/X-Device-Manufacturer/X-OS-Version/X-Device-IMEI 头;认证成功后按 device_id + // 聚合进 user_devices,支撑后台用户详情的设备 tab。非认证链路可留空,IMEI 在 Android 10+ 通常为空。 + string device_model = 16; + string device_manufacturer = 17; + string os_version = 18; + string imei = 19; } // App 是 user-service 维护的 App 注册表投影,用于把客户端包名映射到内部租户键。 @@ -160,6 +170,7 @@ message CronBatchRequest { int64 lock_ttl_ms = 5; int64 pending_publish_max_age_ms = 6; int64 publishing_session_max_age_ms = 7; + int64 open_session_max_age_ms = 8; } // CronBatchResponse 返回一个批处理 run 的执行摘要。 @@ -691,6 +702,66 @@ message BatchGetUsersResponse { map users = 1; } +// ActiveUserBanSummary 是后台用户列表读取的有效封禁摘要。 +// expires_at_ms=0 只在 permanent=true 时表示永久;source 区分 admin、manager 和无新事实表记录的 legacy。 +message ActiveUserBanSummary { + bool active = 1; + string source = 2; + string ban_id = 3; + bool permanent = 4; + int64 expires_at_ms = 5; + UserStatus user_status = 6; + string operator_type = 7; + int64 operator_user_id = 8; + string reason = 9; + int64 created_at_ms = 10; + int32 active_ban_count = 11; +} + +// UserAdminProfile 聚合后台用户列表和详情需要的 user-service owner 事实。 +// roles 只返回当前有效身份:host、agency、bd、bd_leader、coin_seller、manager。 +message UserAdminProfile { + User user = 1; + string birth = 2; + string register_device = 3; + int64 last_success_login_at_ms = 4; + repeated string roles = 5; + ActiveUserBanSummary ban = 6; + string last_operator_type = 7; + int64 last_operator_user_id = 8; + string last_operation_reason = 9; + int64 last_operation_at_ms = 10; +} + +message BatchGetUserAdminProfilesRequest { + RequestMeta meta = 1; + repeated int64 user_ids = 2; +} + +message BatchGetUserAdminProfilesResponse { + map profiles = 1; +} + +// AdminIssueUserAccessTokenRequest 供后台按用户当前最新 active session 重签 access token。 +// 该链路不写 login_audit,也不修改 session 行——后台取 token 不能污染用户“最近活跃/最近登录”事实; +// 审计责任由 admin 侧操作日志承担。 +message AdminIssueUserAccessTokenRequest { + RequestMeta meta = 1; + string app_code = 2; + int64 user_id = 3; +} + +// AdminIssueUserAccessTokenResponse 返回重签的 access token 和所属 session 的定位信息。 +// 不返回 refresh token:后台没有轮换会话的理由,refresh 原文也不允许离开登录主链路。 +message AdminIssueUserAccessTokenResponse { + string access_token = 1; + string token_type = 2; + int64 expires_in_sec = 3; + string session_id = 4; + string device_id = 5; + int64 session_last_heartbeat_at_ms = 6; +} + // RoomBasicUser 是房间首屏只需要的用户展示基础资料。 message RoomBasicUser { int64 user_id = 1; @@ -831,6 +902,56 @@ message SetUserStatusResponse { string access_token_revoke_error = 11; } +// AdminUserBan 是后台管理员封禁的独立事实;expires_at_ms=0 表示永久封禁。 +message AdminUserBan { + string ban_id = 1; + string command_id = 2; + int64 target_user_id = 3; + int64 expires_at_ms = 4; + bool permanent = 5; + string status = 6; + string reason = 7; + int64 operator_admin_id = 8; + int64 created_at_ms = 9; + int64 updated_at_ms = 10; + int64 released_by_admin_id = 11; + string released_reason = 12; + int64 released_at_ms = 13; +} + +message AdminBanUserRequest { + RequestMeta meta = 1; + string command_id = 2; + int64 target_user_id = 3; + int64 expires_at_ms = 4; + int64 operator_admin_id = 5; + string reason = 6; +} + +message AdminBanUserResponse { + AdminUserBan ban = 1; + SetUserStatusResponse status = 2; +} + +// AdminUnbanUserRequest 由后台管理员强制释放目标用户全部管理员、经理和 legacy 封禁来源。 +message AdminUnbanUserRequest { + RequestMeta meta = 1; + // ban_id 已废弃;保留字段号用于滚动发布兼容,强制解封始终按 target_user_id 处理全部有效事实。 + string ban_id = 2; + int64 target_user_id = 3; + int64 operator_admin_id = 4; + string reason = 5; +} + +message AdminUnbanUserResponse { + // ban 仅为旧客户端保留;强制解封可能只释放经理或 legacy 来源,因此新调用方不应依赖该字段。 + AdminUserBan ban = 1; + SetUserStatusResponse status = 2; + int64 released_admin_ban_count = 3; + int64 released_manager_block_count = 4; + bool status_restored = 5; +} + message ManagerUserBlock { string block_id = 1; int64 manager_user_id = 2; @@ -1343,6 +1464,8 @@ service UserService { rpc BusinessUserLookup(BusinessUserLookupRequest) returns (BusinessUserLookupResponse); rpc GetMyProfileStats(GetMyProfileStatsRequest) returns (GetMyProfileStatsResponse); rpc BatchGetUsers(BatchGetUsersRequest) returns (BatchGetUsersResponse); + rpc BatchGetUserAdminProfiles(BatchGetUserAdminProfilesRequest) returns (BatchGetUserAdminProfilesResponse); + rpc AdminIssueUserAccessToken(AdminIssueUserAccessTokenRequest) returns (AdminIssueUserAccessTokenResponse); rpc BatchGetRoomBasicUsers(BatchGetRoomBasicUsersRequest) returns (BatchGetRoomBasicUsersResponse); rpc ListUserIDs(ListUserIDsRequest) returns (ListUserIDsResponse); rpc GetUserMicLifetimeStats(GetUserMicLifetimeStatsRequest) returns (GetUserMicLifetimeStatsResponse); @@ -1353,6 +1476,8 @@ service UserService { rpc ChangeUserCountry(ChangeUserCountryRequest) returns (ChangeUserCountryResponse); rpc AdminChangeUserCountry(AdminChangeUserCountryRequest) returns (ChangeUserCountryResponse); rpc SetUserStatus(SetUserStatusRequest) returns (SetUserStatusResponse); + rpc AdminBanUser(AdminBanUserRequest) returns (AdminBanUserResponse); + rpc AdminUnbanUser(AdminUnbanUserRequest) returns (AdminUnbanUserResponse); rpc CreateManagerUserBlock(CreateManagerUserBlockRequest) returns (CreateManagerUserBlockResponse); rpc ListManagerUserBlocks(ListManagerUserBlocksRequest) returns (ListManagerUserBlocksResponse); rpc UnblockManagerUser(UnblockManagerUserRequest) returns (UnblockManagerUserResponse); @@ -1399,7 +1524,9 @@ service UserCronService { rpc ProcessLoginIPRiskBatch(CronBatchRequest) returns (CronBatchResponse); rpc ProcessRegionRebuildBatch(CronBatchRequest) returns (CronBatchResponse); rpc CompensateMicOpenSessions(CronBatchRequest) returns (CronBatchResponse); + rpc CompensateRoomOpenSessions(CronBatchRequest) returns (CronBatchResponse); rpc ExpireManagerUserBlocks(CronBatchRequest) returns (CronBatchResponse); + rpc ExpireAdminUserBans(CronBatchRequest) returns (CronBatchResponse); rpc RefreshCPIntimacyLeaderboard(CronBatchRequest) returns (CronBatchResponse); } diff --git a/api/proto/user/v1/user_grpc.pb.go b/api/proto/user/v1/user_grpc.pb.go index e8e2fd97..dc6bb468 100644 --- a/api/proto/user/v1/user_grpc.pb.go +++ b/api/proto/user/v1/user_grpc.pb.go @@ -24,6 +24,8 @@ const ( UserService_BusinessUserLookup_FullMethodName = "/hyapp.user.v1.UserService/BusinessUserLookup" UserService_GetMyProfileStats_FullMethodName = "/hyapp.user.v1.UserService/GetMyProfileStats" UserService_BatchGetUsers_FullMethodName = "/hyapp.user.v1.UserService/BatchGetUsers" + UserService_BatchGetUserAdminProfiles_FullMethodName = "/hyapp.user.v1.UserService/BatchGetUserAdminProfiles" + UserService_AdminIssueUserAccessToken_FullMethodName = "/hyapp.user.v1.UserService/AdminIssueUserAccessToken" UserService_BatchGetRoomBasicUsers_FullMethodName = "/hyapp.user.v1.UserService/BatchGetRoomBasicUsers" UserService_ListUserIDs_FullMethodName = "/hyapp.user.v1.UserService/ListUserIDs" UserService_GetUserMicLifetimeStats_FullMethodName = "/hyapp.user.v1.UserService/GetUserMicLifetimeStats" @@ -34,6 +36,8 @@ const ( UserService_ChangeUserCountry_FullMethodName = "/hyapp.user.v1.UserService/ChangeUserCountry" UserService_AdminChangeUserCountry_FullMethodName = "/hyapp.user.v1.UserService/AdminChangeUserCountry" UserService_SetUserStatus_FullMethodName = "/hyapp.user.v1.UserService/SetUserStatus" + UserService_AdminBanUser_FullMethodName = "/hyapp.user.v1.UserService/AdminBanUser" + UserService_AdminUnbanUser_FullMethodName = "/hyapp.user.v1.UserService/AdminUnbanUser" UserService_CreateManagerUserBlock_FullMethodName = "/hyapp.user.v1.UserService/CreateManagerUserBlock" UserService_ListManagerUserBlocks_FullMethodName = "/hyapp.user.v1.UserService/ListManagerUserBlocks" UserService_UnblockManagerUser_FullMethodName = "/hyapp.user.v1.UserService/UnblockManagerUser" @@ -53,6 +57,8 @@ type UserServiceClient interface { BusinessUserLookup(ctx context.Context, in *BusinessUserLookupRequest, opts ...grpc.CallOption) (*BusinessUserLookupResponse, error) GetMyProfileStats(ctx context.Context, in *GetMyProfileStatsRequest, opts ...grpc.CallOption) (*GetMyProfileStatsResponse, error) BatchGetUsers(ctx context.Context, in *BatchGetUsersRequest, opts ...grpc.CallOption) (*BatchGetUsersResponse, error) + BatchGetUserAdminProfiles(ctx context.Context, in *BatchGetUserAdminProfilesRequest, opts ...grpc.CallOption) (*BatchGetUserAdminProfilesResponse, error) + AdminIssueUserAccessToken(ctx context.Context, in *AdminIssueUserAccessTokenRequest, opts ...grpc.CallOption) (*AdminIssueUserAccessTokenResponse, error) BatchGetRoomBasicUsers(ctx context.Context, in *BatchGetRoomBasicUsersRequest, opts ...grpc.CallOption) (*BatchGetRoomBasicUsersResponse, error) ListUserIDs(ctx context.Context, in *ListUserIDsRequest, opts ...grpc.CallOption) (*ListUserIDsResponse, error) GetUserMicLifetimeStats(ctx context.Context, in *GetUserMicLifetimeStatsRequest, opts ...grpc.CallOption) (*GetUserMicLifetimeStatsResponse, error) @@ -63,6 +69,8 @@ type UserServiceClient interface { ChangeUserCountry(ctx context.Context, in *ChangeUserCountryRequest, opts ...grpc.CallOption) (*ChangeUserCountryResponse, error) AdminChangeUserCountry(ctx context.Context, in *AdminChangeUserCountryRequest, opts ...grpc.CallOption) (*ChangeUserCountryResponse, error) SetUserStatus(ctx context.Context, in *SetUserStatusRequest, opts ...grpc.CallOption) (*SetUserStatusResponse, error) + AdminBanUser(ctx context.Context, in *AdminBanUserRequest, opts ...grpc.CallOption) (*AdminBanUserResponse, error) + AdminUnbanUser(ctx context.Context, in *AdminUnbanUserRequest, opts ...grpc.CallOption) (*AdminUnbanUserResponse, error) CreateManagerUserBlock(ctx context.Context, in *CreateManagerUserBlockRequest, opts ...grpc.CallOption) (*CreateManagerUserBlockResponse, error) ListManagerUserBlocks(ctx context.Context, in *ListManagerUserBlocksRequest, opts ...grpc.CallOption) (*ListManagerUserBlocksResponse, error) UnblockManagerUser(ctx context.Context, in *UnblockManagerUserRequest, opts ...grpc.CallOption) (*UnblockManagerUserResponse, error) @@ -129,6 +137,26 @@ func (c *userServiceClient) BatchGetUsers(ctx context.Context, in *BatchGetUsers return out, nil } +func (c *userServiceClient) BatchGetUserAdminProfiles(ctx context.Context, in *BatchGetUserAdminProfilesRequest, opts ...grpc.CallOption) (*BatchGetUserAdminProfilesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(BatchGetUserAdminProfilesResponse) + err := c.cc.Invoke(ctx, UserService_BatchGetUserAdminProfiles_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *userServiceClient) AdminIssueUserAccessToken(ctx context.Context, in *AdminIssueUserAccessTokenRequest, opts ...grpc.CallOption) (*AdminIssueUserAccessTokenResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AdminIssueUserAccessTokenResponse) + err := c.cc.Invoke(ctx, UserService_AdminIssueUserAccessToken_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *userServiceClient) BatchGetRoomBasicUsers(ctx context.Context, in *BatchGetRoomBasicUsersRequest, opts ...grpc.CallOption) (*BatchGetRoomBasicUsersResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(BatchGetRoomBasicUsersResponse) @@ -229,6 +257,26 @@ func (c *userServiceClient) SetUserStatus(ctx context.Context, in *SetUserStatus return out, nil } +func (c *userServiceClient) AdminBanUser(ctx context.Context, in *AdminBanUserRequest, opts ...grpc.CallOption) (*AdminBanUserResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AdminBanUserResponse) + err := c.cc.Invoke(ctx, UserService_AdminBanUser_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *userServiceClient) AdminUnbanUser(ctx context.Context, in *AdminUnbanUserRequest, opts ...grpc.CallOption) (*AdminUnbanUserResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AdminUnbanUserResponse) + err := c.cc.Invoke(ctx, UserService_AdminUnbanUser_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *userServiceClient) CreateManagerUserBlock(ctx context.Context, in *CreateManagerUserBlockRequest, opts ...grpc.CallOption) (*CreateManagerUserBlockResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(CreateManagerUserBlockResponse) @@ -300,6 +348,8 @@ type UserServiceServer interface { BusinessUserLookup(context.Context, *BusinessUserLookupRequest) (*BusinessUserLookupResponse, error) GetMyProfileStats(context.Context, *GetMyProfileStatsRequest) (*GetMyProfileStatsResponse, error) BatchGetUsers(context.Context, *BatchGetUsersRequest) (*BatchGetUsersResponse, error) + BatchGetUserAdminProfiles(context.Context, *BatchGetUserAdminProfilesRequest) (*BatchGetUserAdminProfilesResponse, error) + AdminIssueUserAccessToken(context.Context, *AdminIssueUserAccessTokenRequest) (*AdminIssueUserAccessTokenResponse, error) BatchGetRoomBasicUsers(context.Context, *BatchGetRoomBasicUsersRequest) (*BatchGetRoomBasicUsersResponse, error) ListUserIDs(context.Context, *ListUserIDsRequest) (*ListUserIDsResponse, error) GetUserMicLifetimeStats(context.Context, *GetUserMicLifetimeStatsRequest) (*GetUserMicLifetimeStatsResponse, error) @@ -310,6 +360,8 @@ type UserServiceServer interface { ChangeUserCountry(context.Context, *ChangeUserCountryRequest) (*ChangeUserCountryResponse, error) AdminChangeUserCountry(context.Context, *AdminChangeUserCountryRequest) (*ChangeUserCountryResponse, error) SetUserStatus(context.Context, *SetUserStatusRequest) (*SetUserStatusResponse, error) + AdminBanUser(context.Context, *AdminBanUserRequest) (*AdminBanUserResponse, error) + AdminUnbanUser(context.Context, *AdminUnbanUserRequest) (*AdminUnbanUserResponse, error) CreateManagerUserBlock(context.Context, *CreateManagerUserBlockRequest) (*CreateManagerUserBlockResponse, error) ListManagerUserBlocks(context.Context, *ListManagerUserBlocksRequest) (*ListManagerUserBlocksResponse, error) UnblockManagerUser(context.Context, *UnblockManagerUserRequest) (*UnblockManagerUserResponse, error) @@ -341,6 +393,12 @@ func (UnimplementedUserServiceServer) GetMyProfileStats(context.Context, *GetMyP func (UnimplementedUserServiceServer) BatchGetUsers(context.Context, *BatchGetUsersRequest) (*BatchGetUsersResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BatchGetUsers not implemented") } +func (UnimplementedUserServiceServer) BatchGetUserAdminProfiles(context.Context, *BatchGetUserAdminProfilesRequest) (*BatchGetUserAdminProfilesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BatchGetUserAdminProfiles not implemented") +} +func (UnimplementedUserServiceServer) AdminIssueUserAccessToken(context.Context, *AdminIssueUserAccessTokenRequest) (*AdminIssueUserAccessTokenResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AdminIssueUserAccessToken not implemented") +} func (UnimplementedUserServiceServer) BatchGetRoomBasicUsers(context.Context, *BatchGetRoomBasicUsersRequest) (*BatchGetRoomBasicUsersResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BatchGetRoomBasicUsers not implemented") } @@ -371,6 +429,12 @@ func (UnimplementedUserServiceServer) AdminChangeUserCountry(context.Context, *A func (UnimplementedUserServiceServer) SetUserStatus(context.Context, *SetUserStatusRequest) (*SetUserStatusResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SetUserStatus not implemented") } +func (UnimplementedUserServiceServer) AdminBanUser(context.Context, *AdminBanUserRequest) (*AdminBanUserResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AdminBanUser not implemented") +} +func (UnimplementedUserServiceServer) AdminUnbanUser(context.Context, *AdminUnbanUserRequest) (*AdminUnbanUserResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AdminUnbanUser not implemented") +} func (UnimplementedUserServiceServer) CreateManagerUserBlock(context.Context, *CreateManagerUserBlockRequest) (*CreateManagerUserBlockResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CreateManagerUserBlock not implemented") } @@ -500,6 +564,42 @@ func _UserService_BatchGetUsers_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } +func _UserService_BatchGetUserAdminProfiles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BatchGetUserAdminProfilesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserServiceServer).BatchGetUserAdminProfiles(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserService_BatchGetUserAdminProfiles_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserServiceServer).BatchGetUserAdminProfiles(ctx, req.(*BatchGetUserAdminProfilesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _UserService_AdminIssueUserAccessToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AdminIssueUserAccessTokenRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserServiceServer).AdminIssueUserAccessToken(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserService_AdminIssueUserAccessToken_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserServiceServer).AdminIssueUserAccessToken(ctx, req.(*AdminIssueUserAccessTokenRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _UserService_BatchGetRoomBasicUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(BatchGetRoomBasicUsersRequest) if err := dec(in); err != nil { @@ -680,6 +780,42 @@ func _UserService_SetUserStatus_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } +func _UserService_AdminBanUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AdminBanUserRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserServiceServer).AdminBanUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserService_AdminBanUser_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserServiceServer).AdminBanUser(ctx, req.(*AdminBanUserRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _UserService_AdminUnbanUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AdminUnbanUserRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserServiceServer).AdminUnbanUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserService_AdminUnbanUser_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserServiceServer).AdminUnbanUser(ctx, req.(*AdminUnbanUserRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _UserService_CreateManagerUserBlock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CreateManagerUserBlockRequest) if err := dec(in); err != nil { @@ -815,6 +951,14 @@ var UserService_ServiceDesc = grpc.ServiceDesc{ MethodName: "BatchGetUsers", Handler: _UserService_BatchGetUsers_Handler, }, + { + MethodName: "BatchGetUserAdminProfiles", + Handler: _UserService_BatchGetUserAdminProfiles_Handler, + }, + { + MethodName: "AdminIssueUserAccessToken", + Handler: _UserService_AdminIssueUserAccessToken_Handler, + }, { MethodName: "BatchGetRoomBasicUsers", Handler: _UserService_BatchGetRoomBasicUsers_Handler, @@ -855,6 +999,14 @@ var UserService_ServiceDesc = grpc.ServiceDesc{ MethodName: "SetUserStatus", Handler: _UserService_SetUserStatus_Handler, }, + { + MethodName: "AdminBanUser", + Handler: _UserService_AdminBanUser_Handler, + }, + { + MethodName: "AdminUnbanUser", + Handler: _UserService_AdminUnbanUser_Handler, + }, { MethodName: "CreateManagerUserBlock", Handler: _UserService_CreateManagerUserBlock_Handler, @@ -1890,7 +2042,9 @@ const ( UserCronService_ProcessLoginIPRiskBatch_FullMethodName = "/hyapp.user.v1.UserCronService/ProcessLoginIPRiskBatch" UserCronService_ProcessRegionRebuildBatch_FullMethodName = "/hyapp.user.v1.UserCronService/ProcessRegionRebuildBatch" UserCronService_CompensateMicOpenSessions_FullMethodName = "/hyapp.user.v1.UserCronService/CompensateMicOpenSessions" + UserCronService_CompensateRoomOpenSessions_FullMethodName = "/hyapp.user.v1.UserCronService/CompensateRoomOpenSessions" UserCronService_ExpireManagerUserBlocks_FullMethodName = "/hyapp.user.v1.UserCronService/ExpireManagerUserBlocks" + UserCronService_ExpireAdminUserBans_FullMethodName = "/hyapp.user.v1.UserCronService/ExpireAdminUserBans" UserCronService_RefreshCPIntimacyLeaderboard_FullMethodName = "/hyapp.user.v1.UserCronService/RefreshCPIntimacyLeaderboard" ) @@ -1903,7 +2057,9 @@ type UserCronServiceClient interface { ProcessLoginIPRiskBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) ProcessRegionRebuildBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) CompensateMicOpenSessions(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) + CompensateRoomOpenSessions(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) ExpireManagerUserBlocks(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) + ExpireAdminUserBans(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) RefreshCPIntimacyLeaderboard(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) } @@ -1945,6 +2101,16 @@ func (c *userCronServiceClient) CompensateMicOpenSessions(ctx context.Context, i return out, nil } +func (c *userCronServiceClient) CompensateRoomOpenSessions(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CronBatchResponse) + err := c.cc.Invoke(ctx, UserCronService_CompensateRoomOpenSessions_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *userCronServiceClient) ExpireManagerUserBlocks(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(CronBatchResponse) @@ -1955,6 +2121,16 @@ func (c *userCronServiceClient) ExpireManagerUserBlocks(ctx context.Context, in return out, nil } +func (c *userCronServiceClient) ExpireAdminUserBans(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CronBatchResponse) + err := c.cc.Invoke(ctx, UserCronService_ExpireAdminUserBans_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *userCronServiceClient) RefreshCPIntimacyLeaderboard(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(CronBatchResponse) @@ -1974,7 +2150,9 @@ type UserCronServiceServer interface { ProcessLoginIPRiskBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) ProcessRegionRebuildBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) CompensateMicOpenSessions(context.Context, *CronBatchRequest) (*CronBatchResponse, error) + CompensateRoomOpenSessions(context.Context, *CronBatchRequest) (*CronBatchResponse, error) ExpireManagerUserBlocks(context.Context, *CronBatchRequest) (*CronBatchResponse, error) + ExpireAdminUserBans(context.Context, *CronBatchRequest) (*CronBatchResponse, error) RefreshCPIntimacyLeaderboard(context.Context, *CronBatchRequest) (*CronBatchResponse, error) mustEmbedUnimplementedUserCronServiceServer() } @@ -1995,9 +2173,15 @@ func (UnimplementedUserCronServiceServer) ProcessRegionRebuildBatch(context.Cont func (UnimplementedUserCronServiceServer) CompensateMicOpenSessions(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CompensateMicOpenSessions not implemented") } +func (UnimplementedUserCronServiceServer) CompensateRoomOpenSessions(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CompensateRoomOpenSessions not implemented") +} func (UnimplementedUserCronServiceServer) ExpireManagerUserBlocks(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ExpireManagerUserBlocks not implemented") } +func (UnimplementedUserCronServiceServer) ExpireAdminUserBans(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ExpireAdminUserBans not implemented") +} func (UnimplementedUserCronServiceServer) RefreshCPIntimacyLeaderboard(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RefreshCPIntimacyLeaderboard not implemented") } @@ -2076,6 +2260,24 @@ func _UserCronService_CompensateMicOpenSessions_Handler(srv interface{}, ctx con return interceptor(ctx, in, info, handler) } +func _UserCronService_CompensateRoomOpenSessions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CronBatchRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserCronServiceServer).CompensateRoomOpenSessions(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserCronService_CompensateRoomOpenSessions_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserCronServiceServer).CompensateRoomOpenSessions(ctx, req.(*CronBatchRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _UserCronService_ExpireManagerUserBlocks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CronBatchRequest) if err := dec(in); err != nil { @@ -2094,6 +2296,24 @@ func _UserCronService_ExpireManagerUserBlocks_Handler(srv interface{}, ctx conte return interceptor(ctx, in, info, handler) } +func _UserCronService_ExpireAdminUserBans_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CronBatchRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserCronServiceServer).ExpireAdminUserBans(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserCronService_ExpireAdminUserBans_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserCronServiceServer).ExpireAdminUserBans(ctx, req.(*CronBatchRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _UserCronService_RefreshCPIntimacyLeaderboard_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CronBatchRequest) if err := dec(in); err != nil { @@ -2131,10 +2351,18 @@ var UserCronService_ServiceDesc = grpc.ServiceDesc{ MethodName: "CompensateMicOpenSessions", Handler: _UserCronService_CompensateMicOpenSessions_Handler, }, + { + MethodName: "CompensateRoomOpenSessions", + Handler: _UserCronService_CompensateRoomOpenSessions_Handler, + }, { MethodName: "ExpireManagerUserBlocks", Handler: _UserCronService_ExpireManagerUserBlocks_Handler, }, + { + MethodName: "ExpireAdminUserBans", + Handler: _UserCronService_ExpireAdminUserBans_Handler, + }, { MethodName: "RefreshCPIntimacyLeaderboard", Handler: _UserCronService_RefreshCPIntimacyLeaderboard_Handler, diff --git a/api/proto/wallet/v1/wallet.pb.go b/api/proto/wallet/v1/wallet.pb.go index cb5cb70a..eb3946a6 100644 --- a/api/proto/wallet/v1/wallet.pb.go +++ b/api/proto/wallet/v1/wallet.pb.go @@ -381,6 +381,8 @@ type DebitGiftResponse struct { HostPointPolicyInstanceCode string `protobuf:"bytes,25,opt,name=host_point_policy_instance_code,json=hostPointPolicyInstanceCode,proto3" json:"host_point_policy_instance_code,omitempty"` HostPointTemplateCode string `protobuf:"bytes,26,opt,name=host_point_template_code,json=hostPointTemplateCode,proto3" json:"host_point_template_code,omitempty"` HostPointTemplateVersion string `protobuf:"bytes,27,opt,name=host_point_template_version,json=hostPointTemplateVersion,proto3" json:"host_point_template_version,omitempty"` + // balance_version 是 sender 被扣费资产在本次账务完成后的版本;同 command_id 重放返回首次版本。 + BalanceVersion int64 `protobuf:"varint,28,opt,name=balance_version,json=balanceVersion,proto3" json:"balance_version,omitempty"` } func (x *DebitGiftResponse) Reset() { @@ -602,6 +604,13 @@ func (x *DebitGiftResponse) GetHostPointTemplateVersion() string { return "" } +func (x *DebitGiftResponse) GetBalanceVersion() int64 { + if x != nil { + return x.BalanceVersion + } + return 0 +} + // DebitGiftTarget 是一笔批量送礼中的单个接收方账务快照。 type DebitGiftTarget struct { state protoimpl.MessageState @@ -11180,6 +11189,198 @@ func (x *GetRechargeBillSummaryResponse) GetCoinSeller() *RechargeBillSummaryBuc return nil } +// UserRechargeStats 是 wallet-service 已维护的用户累计充值聚合,不允许后台回扫充值流水临时计算。 +type UserRechargeStats struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + RechargeCount int64 `protobuf:"varint,2,opt,name=recharge_count,json=rechargeCount,proto3" json:"recharge_count,omitempty"` + TotalCoinAmount int64 `protobuf:"varint,3,opt,name=total_coin_amount,json=totalCoinAmount,proto3" json:"total_coin_amount,omitempty"` + TotalUsdMinorAmount int64 `protobuf:"varint,4,opt,name=total_usd_minor_amount,json=totalUsdMinorAmount,proto3" json:"total_usd_minor_amount,omitempty"` + FirstRechargedAtMs int64 `protobuf:"varint,5,opt,name=first_recharged_at_ms,json=firstRechargedAtMs,proto3" json:"first_recharged_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,6,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` +} + +func (x *UserRechargeStats) Reset() { + *x = UserRechargeStats{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[120] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UserRechargeStats) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UserRechargeStats) ProtoMessage() {} + +func (x *UserRechargeStats) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[120] + 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 UserRechargeStats.ProtoReflect.Descriptor instead. +func (*UserRechargeStats) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{120} +} + +func (x *UserRechargeStats) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *UserRechargeStats) GetRechargeCount() int64 { + if x != nil { + return x.RechargeCount + } + return 0 +} + +func (x *UserRechargeStats) GetTotalCoinAmount() int64 { + if x != nil { + return x.TotalCoinAmount + } + return 0 +} + +func (x *UserRechargeStats) GetTotalUsdMinorAmount() int64 { + if x != nil { + return x.TotalUsdMinorAmount + } + return 0 +} + +func (x *UserRechargeStats) GetFirstRechargedAtMs() int64 { + if x != nil { + return x.FirstRechargedAtMs + } + return 0 +} + +func (x *UserRechargeStats) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +type BatchGetUserRechargeStatsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + UserIds []int64 `protobuf:"varint,3,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"` +} + +func (x *BatchGetUserRechargeStatsRequest) Reset() { + *x = BatchGetUserRechargeStatsRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[121] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchGetUserRechargeStatsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchGetUserRechargeStatsRequest) ProtoMessage() {} + +func (x *BatchGetUserRechargeStatsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[121] + 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 BatchGetUserRechargeStatsRequest.ProtoReflect.Descriptor instead. +func (*BatchGetUserRechargeStatsRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{121} +} + +func (x *BatchGetUserRechargeStatsRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *BatchGetUserRechargeStatsRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *BatchGetUserRechargeStatsRequest) GetUserIds() []int64 { + if x != nil { + return x.UserIds + } + return nil +} + +type BatchGetUserRechargeStatsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Items []*UserRechargeStats `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *BatchGetUserRechargeStatsResponse) Reset() { + *x = BatchGetUserRechargeStatsResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[122] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchGetUserRechargeStatsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchGetUserRechargeStatsResponse) ProtoMessage() {} + +func (x *BatchGetUserRechargeStatsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[122] + 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 BatchGetUserRechargeStatsResponse.ProtoReflect.Descriptor instead. +func (*BatchGetUserRechargeStatsResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{122} +} + +func (x *BatchGetUserRechargeStatsResponse) GetItems() []*UserRechargeStats { + if x != nil { + return x.Items + } + return nil +} + type GetRechargeBillOverviewRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -11198,7 +11399,7 @@ type GetRechargeBillOverviewRequest struct { func (x *GetRechargeBillOverviewRequest) Reset() { *x = GetRechargeBillOverviewRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[120] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11210,7 +11411,7 @@ func (x *GetRechargeBillOverviewRequest) String() string { func (*GetRechargeBillOverviewRequest) ProtoMessage() {} func (x *GetRechargeBillOverviewRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[120] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11223,7 +11424,7 @@ func (x *GetRechargeBillOverviewRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRechargeBillOverviewRequest.ProtoReflect.Descriptor instead. func (*GetRechargeBillOverviewRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{120} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{123} } func (x *GetRechargeBillOverviewRequest) GetRequestId() string { @@ -11299,7 +11500,7 @@ type RechargeBillDailyBucket struct { func (x *RechargeBillDailyBucket) Reset() { *x = RechargeBillDailyBucket{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[121] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11311,7 +11512,7 @@ func (x *RechargeBillDailyBucket) String() string { func (*RechargeBillDailyBucket) ProtoMessage() {} func (x *RechargeBillDailyBucket) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[121] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11324,7 +11525,7 @@ func (x *RechargeBillDailyBucket) ProtoReflect() protoreflect.Message { // Deprecated: Use RechargeBillDailyBucket.ProtoReflect.Descriptor instead. func (*RechargeBillDailyBucket) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{121} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{124} } func (x *RechargeBillDailyBucket) GetDate() string { @@ -11388,7 +11589,7 @@ type RechargeBillRegionBucket struct { func (x *RechargeBillRegionBucket) Reset() { *x = RechargeBillRegionBucket{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[122] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11400,7 +11601,7 @@ func (x *RechargeBillRegionBucket) String() string { func (*RechargeBillRegionBucket) ProtoMessage() {} func (x *RechargeBillRegionBucket) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[122] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11413,7 +11614,7 @@ func (x *RechargeBillRegionBucket) ProtoReflect() protoreflect.Message { // Deprecated: Use RechargeBillRegionBucket.ProtoReflect.Descriptor instead. func (*RechargeBillRegionBucket) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{122} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{125} } func (x *RechargeBillRegionBucket) GetRegionId() int64 { @@ -11455,7 +11656,7 @@ type RechargeBillGooglePaidStats struct { func (x *RechargeBillGooglePaidStats) Reset() { *x = RechargeBillGooglePaidStats{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[123] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11467,7 +11668,7 @@ func (x *RechargeBillGooglePaidStats) String() string { func (*RechargeBillGooglePaidStats) ProtoMessage() {} func (x *RechargeBillGooglePaidStats) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[123] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11480,7 +11681,7 @@ func (x *RechargeBillGooglePaidStats) ProtoReflect() protoreflect.Message { // Deprecated: Use RechargeBillGooglePaidStats.ProtoReflect.Descriptor instead. func (*RechargeBillGooglePaidStats) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{123} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{126} } func (x *RechargeBillGooglePaidStats) GetGoogleBillCount() int64 { @@ -11545,7 +11746,7 @@ type RechargeBillSalaryTransferStats struct { func (x *RechargeBillSalaryTransferStats) Reset() { *x = RechargeBillSalaryTransferStats{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[124] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11557,7 +11758,7 @@ func (x *RechargeBillSalaryTransferStats) String() string { func (*RechargeBillSalaryTransferStats) ProtoMessage() {} func (x *RechargeBillSalaryTransferStats) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[124] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11570,7 +11771,7 @@ func (x *RechargeBillSalaryTransferStats) ProtoReflect() protoreflect.Message { // Deprecated: Use RechargeBillSalaryTransferStats.ProtoReflect.Descriptor instead. func (*RechargeBillSalaryTransferStats) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{124} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{127} } func (x *RechargeBillSalaryTransferStats) GetTransferCount() int64 { @@ -11600,7 +11801,7 @@ type GetRechargeBillOverviewResponse struct { func (x *GetRechargeBillOverviewResponse) Reset() { *x = GetRechargeBillOverviewResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[125] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11612,7 +11813,7 @@ func (x *GetRechargeBillOverviewResponse) String() string { func (*GetRechargeBillOverviewResponse) ProtoMessage() {} func (x *GetRechargeBillOverviewResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[125] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11625,7 +11826,7 @@ func (x *GetRechargeBillOverviewResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRechargeBillOverviewResponse.ProtoReflect.Descriptor instead. func (*GetRechargeBillOverviewResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{125} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{128} } func (x *GetRechargeBillOverviewResponse) GetDaily() []*RechargeBillDailyBucket { @@ -11668,7 +11869,7 @@ type RefreshGooglePaymentPricesRequest struct { func (x *RefreshGooglePaymentPricesRequest) Reset() { *x = RefreshGooglePaymentPricesRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[126] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11680,7 +11881,7 @@ func (x *RefreshGooglePaymentPricesRequest) String() string { func (*RefreshGooglePaymentPricesRequest) ProtoMessage() {} func (x *RefreshGooglePaymentPricesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[126] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11693,7 +11894,7 @@ func (x *RefreshGooglePaymentPricesRequest) ProtoReflect() protoreflect.Message // Deprecated: Use RefreshGooglePaymentPricesRequest.ProtoReflect.Descriptor instead. func (*RefreshGooglePaymentPricesRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{126} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{129} } func (x *RefreshGooglePaymentPricesRequest) GetRequestId() string { @@ -11733,7 +11934,7 @@ type GooglePaymentPrice struct { func (x *GooglePaymentPrice) Reset() { *x = GooglePaymentPrice{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[127] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11745,7 +11946,7 @@ func (x *GooglePaymentPrice) String() string { func (*GooglePaymentPrice) ProtoMessage() {} func (x *GooglePaymentPrice) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[127] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11758,7 +11959,7 @@ func (x *GooglePaymentPrice) ProtoReflect() protoreflect.Message { // Deprecated: Use GooglePaymentPrice.ProtoReflect.Descriptor instead. func (*GooglePaymentPrice) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{127} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{130} } func (x *GooglePaymentPrice) GetTransactionId() string { @@ -11820,7 +12021,7 @@ type RefreshGooglePaymentPricesResponse struct { func (x *RefreshGooglePaymentPricesResponse) Reset() { *x = RefreshGooglePaymentPricesResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[128] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11832,7 +12033,7 @@ func (x *RefreshGooglePaymentPricesResponse) String() string { func (*RefreshGooglePaymentPricesResponse) ProtoMessage() {} func (x *RefreshGooglePaymentPricesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[128] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11845,7 +12046,7 @@ func (x *RefreshGooglePaymentPricesResponse) ProtoReflect() protoreflect.Message // Deprecated: Use RefreshGooglePaymentPricesResponse.ProtoReflect.Descriptor instead. func (*RefreshGooglePaymentPricesResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{128} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{131} } func (x *RefreshGooglePaymentPricesResponse) GetPrices() []*GooglePaymentPrice { @@ -11867,7 +12068,7 @@ type WalletFeatureFlags struct { func (x *WalletFeatureFlags) Reset() { *x = WalletFeatureFlags{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[129] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11879,7 +12080,7 @@ func (x *WalletFeatureFlags) String() string { func (*WalletFeatureFlags) ProtoMessage() {} func (x *WalletFeatureFlags) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[129] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11892,7 +12093,7 @@ func (x *WalletFeatureFlags) ProtoReflect() protoreflect.Message { // Deprecated: Use WalletFeatureFlags.ProtoReflect.Descriptor instead. func (*WalletFeatureFlags) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{129} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{132} } func (x *WalletFeatureFlags) GetRechargeEnabled() bool { @@ -11921,7 +12122,7 @@ type GetWalletOverviewRequest struct { func (x *GetWalletOverviewRequest) Reset() { *x = GetWalletOverviewRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[130] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11933,7 +12134,7 @@ func (x *GetWalletOverviewRequest) String() string { func (*GetWalletOverviewRequest) ProtoMessage() {} func (x *GetWalletOverviewRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[130] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11946,7 +12147,7 @@ func (x *GetWalletOverviewRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWalletOverviewRequest.ProtoReflect.Descriptor instead. func (*GetWalletOverviewRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{130} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{133} } func (x *GetWalletOverviewRequest) GetRequestId() string { @@ -11981,7 +12182,7 @@ type GetWalletOverviewResponse struct { func (x *GetWalletOverviewResponse) Reset() { *x = GetWalletOverviewResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[131] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11993,7 +12194,7 @@ func (x *GetWalletOverviewResponse) String() string { func (*GetWalletOverviewResponse) ProtoMessage() {} func (x *GetWalletOverviewResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[131] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12006,7 +12207,7 @@ func (x *GetWalletOverviewResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWalletOverviewResponse.ProtoReflect.Descriptor instead. func (*GetWalletOverviewResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{131} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{134} } func (x *GetWalletOverviewResponse) GetBalances() []*AssetBalance { @@ -12035,7 +12236,7 @@ type WalletValueSummary struct { func (x *WalletValueSummary) Reset() { *x = WalletValueSummary{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[132] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12047,7 +12248,7 @@ func (x *WalletValueSummary) String() string { func (*WalletValueSummary) ProtoMessage() {} func (x *WalletValueSummary) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[132] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12060,7 +12261,7 @@ func (x *WalletValueSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use WalletValueSummary.ProtoReflect.Descriptor instead. func (*WalletValueSummary) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{132} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{135} } func (x *WalletValueSummary) GetCoinAmount() int64 { @@ -12089,7 +12290,7 @@ type GetWalletValueSummaryRequest struct { func (x *GetWalletValueSummaryRequest) Reset() { *x = GetWalletValueSummaryRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[133] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12101,7 +12302,7 @@ func (x *GetWalletValueSummaryRequest) String() string { func (*GetWalletValueSummaryRequest) ProtoMessage() {} func (x *GetWalletValueSummaryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[133] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12114,7 +12315,7 @@ func (x *GetWalletValueSummaryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWalletValueSummaryRequest.ProtoReflect.Descriptor instead. func (*GetWalletValueSummaryRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{133} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{136} } func (x *GetWalletValueSummaryRequest) GetRequestId() string { @@ -12148,7 +12349,7 @@ type GetWalletValueSummaryResponse struct { func (x *GetWalletValueSummaryResponse) Reset() { *x = GetWalletValueSummaryResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[134] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12160,7 +12361,7 @@ func (x *GetWalletValueSummaryResponse) String() string { func (*GetWalletValueSummaryResponse) ProtoMessage() {} func (x *GetWalletValueSummaryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[134] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12173,7 +12374,7 @@ func (x *GetWalletValueSummaryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWalletValueSummaryResponse.ProtoReflect.Descriptor instead. func (*GetWalletValueSummaryResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{134} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{137} } func (x *GetWalletValueSummaryResponse) GetSummary() *WalletValueSummary { @@ -12212,7 +12413,7 @@ type GiftWallItem struct { func (x *GiftWallItem) Reset() { *x = GiftWallItem{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[135] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12224,7 +12425,7 @@ func (x *GiftWallItem) String() string { func (*GiftWallItem) ProtoMessage() {} func (x *GiftWallItem) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[135] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12237,7 +12438,7 @@ func (x *GiftWallItem) ProtoReflect() protoreflect.Message { // Deprecated: Use GiftWallItem.ProtoReflect.Descriptor instead. func (*GiftWallItem) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{135} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{138} } func (x *GiftWallItem) GetGiftId() string { @@ -12378,7 +12579,7 @@ type GetUserGiftWallRequest struct { func (x *GetUserGiftWallRequest) Reset() { *x = GetUserGiftWallRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[136] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12390,7 +12591,7 @@ func (x *GetUserGiftWallRequest) String() string { func (*GetUserGiftWallRequest) ProtoMessage() {} func (x *GetUserGiftWallRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[136] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12403,7 +12604,7 @@ func (x *GetUserGiftWallRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserGiftWallRequest.ProtoReflect.Descriptor instead. func (*GetUserGiftWallRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{136} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{139} } func (x *GetUserGiftWallRequest) GetRequestId() string { @@ -12445,7 +12646,7 @@ type GetUserGiftWallResponse struct { func (x *GetUserGiftWallResponse) Reset() { *x = GetUserGiftWallResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[137] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12457,7 +12658,7 @@ func (x *GetUserGiftWallResponse) String() string { func (*GetUserGiftWallResponse) ProtoMessage() {} func (x *GetUserGiftWallResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[137] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12470,7 +12671,7 @@ func (x *GetUserGiftWallResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserGiftWallResponse.ProtoReflect.Descriptor instead. func (*GetUserGiftWallResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{137} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{140} } func (x *GetUserGiftWallResponse) GetItems() []*GiftWallItem { @@ -12564,7 +12765,7 @@ type RechargeProduct struct { func (x *RechargeProduct) Reset() { *x = RechargeProduct{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[138] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12576,7 +12777,7 @@ func (x *RechargeProduct) String() string { func (*RechargeProduct) ProtoMessage() {} func (x *RechargeProduct) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[138] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12589,7 +12790,7 @@ func (x *RechargeProduct) ProtoReflect() protoreflect.Message { // Deprecated: Use RechargeProduct.ProtoReflect.Descriptor instead. func (*RechargeProduct) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{138} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{141} } func (x *RechargeProduct) GetProductId() int64 { @@ -12770,7 +12971,7 @@ type ListRechargeProductsRequest struct { func (x *ListRechargeProductsRequest) Reset() { *x = ListRechargeProductsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[139] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12782,7 +12983,7 @@ func (x *ListRechargeProductsRequest) String() string { func (*ListRechargeProductsRequest) ProtoMessage() {} func (x *ListRechargeProductsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[139] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12795,7 +12996,7 @@ func (x *ListRechargeProductsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRechargeProductsRequest.ProtoReflect.Descriptor instead. func (*ListRechargeProductsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{139} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{142} } func (x *ListRechargeProductsRequest) GetRequestId() string { @@ -12851,7 +13052,7 @@ type ListRechargeProductsResponse struct { func (x *ListRechargeProductsResponse) Reset() { *x = ListRechargeProductsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[140] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12863,7 +13064,7 @@ func (x *ListRechargeProductsResponse) String() string { func (*ListRechargeProductsResponse) ProtoMessage() {} func (x *ListRechargeProductsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[140] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12876,7 +13077,7 @@ func (x *ListRechargeProductsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRechargeProductsResponse.ProtoReflect.Descriptor instead. func (*ListRechargeProductsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{140} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{143} } func (x *ListRechargeProductsResponse) GetProducts() []*RechargeProduct { @@ -12915,7 +13116,7 @@ type ConfirmGooglePaymentRequest struct { func (x *ConfirmGooglePaymentRequest) Reset() { *x = ConfirmGooglePaymentRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[141] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12927,7 +13128,7 @@ func (x *ConfirmGooglePaymentRequest) String() string { func (*ConfirmGooglePaymentRequest) ProtoMessage() {} func (x *ConfirmGooglePaymentRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[141] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12940,7 +13141,7 @@ func (x *ConfirmGooglePaymentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfirmGooglePaymentRequest.ProtoReflect.Descriptor instead. func (*ConfirmGooglePaymentRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{141} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{144} } func (x *ConfirmGooglePaymentRequest) GetRequestId() string { @@ -13045,7 +13246,7 @@ type ConfirmGooglePaymentResponse struct { func (x *ConfirmGooglePaymentResponse) Reset() { *x = ConfirmGooglePaymentResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[142] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13057,7 +13258,7 @@ func (x *ConfirmGooglePaymentResponse) String() string { func (*ConfirmGooglePaymentResponse) ProtoMessage() {} func (x *ConfirmGooglePaymentResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[142] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13070,7 +13271,7 @@ func (x *ConfirmGooglePaymentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfirmGooglePaymentResponse.ProtoReflect.Descriptor instead. func (*ConfirmGooglePaymentResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{142} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{145} } func (x *ConfirmGooglePaymentResponse) GetPaymentOrderId() string { @@ -13155,7 +13356,7 @@ type ListAdminRechargeProductsRequest struct { func (x *ListAdminRechargeProductsRequest) Reset() { *x = ListAdminRechargeProductsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[143] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13167,7 +13368,7 @@ func (x *ListAdminRechargeProductsRequest) String() string { func (*ListAdminRechargeProductsRequest) ProtoMessage() {} func (x *ListAdminRechargeProductsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[143] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13180,7 +13381,7 @@ func (x *ListAdminRechargeProductsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAdminRechargeProductsRequest.ProtoReflect.Descriptor instead. func (*ListAdminRechargeProductsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{143} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{146} } func (x *ListAdminRechargeProductsRequest) GetRequestId() string { @@ -13257,7 +13458,7 @@ type ListAdminRechargeProductsResponse struct { func (x *ListAdminRechargeProductsResponse) Reset() { *x = ListAdminRechargeProductsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[144] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13269,7 +13470,7 @@ func (x *ListAdminRechargeProductsResponse) String() string { func (*ListAdminRechargeProductsResponse) ProtoMessage() {} func (x *ListAdminRechargeProductsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[144] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13282,7 +13483,7 @@ func (x *ListAdminRechargeProductsResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ListAdminRechargeProductsResponse.ProtoReflect.Descriptor instead. func (*ListAdminRechargeProductsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{144} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{147} } func (x *ListAdminRechargeProductsResponse) GetProducts() []*RechargeProduct { @@ -13322,7 +13523,7 @@ type CreateRechargeProductRequest struct { func (x *CreateRechargeProductRequest) Reset() { *x = CreateRechargeProductRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[145] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13334,7 +13535,7 @@ func (x *CreateRechargeProductRequest) String() string { func (*CreateRechargeProductRequest) ProtoMessage() {} func (x *CreateRechargeProductRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[145] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13347,7 +13548,7 @@ func (x *CreateRechargeProductRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateRechargeProductRequest.ProtoReflect.Descriptor instead. func (*CreateRechargeProductRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{145} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{148} } func (x *CreateRechargeProductRequest) GetRequestId() string { @@ -13457,7 +13658,7 @@ type UpdateRechargeProductRequest struct { func (x *UpdateRechargeProductRequest) Reset() { *x = UpdateRechargeProductRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[146] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13469,7 +13670,7 @@ func (x *UpdateRechargeProductRequest) String() string { func (*UpdateRechargeProductRequest) ProtoMessage() {} func (x *UpdateRechargeProductRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[146] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13482,7 +13683,7 @@ func (x *UpdateRechargeProductRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRechargeProductRequest.ProtoReflect.Descriptor instead. func (*UpdateRechargeProductRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{146} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{149} } func (x *UpdateRechargeProductRequest) GetRequestId() string { @@ -13589,7 +13790,7 @@ type DeleteRechargeProductRequest struct { func (x *DeleteRechargeProductRequest) Reset() { *x = DeleteRechargeProductRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[147] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13601,7 +13802,7 @@ func (x *DeleteRechargeProductRequest) String() string { func (*DeleteRechargeProductRequest) ProtoMessage() {} func (x *DeleteRechargeProductRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[147] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13614,7 +13815,7 @@ func (x *DeleteRechargeProductRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteRechargeProductRequest.ProtoReflect.Descriptor instead. func (*DeleteRechargeProductRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{147} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{150} } func (x *DeleteRechargeProductRequest) GetRequestId() string { @@ -13655,7 +13856,7 @@ type RechargeProductResponse struct { func (x *RechargeProductResponse) Reset() { *x = RechargeProductResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[148] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13667,7 +13868,7 @@ func (x *RechargeProductResponse) String() string { func (*RechargeProductResponse) ProtoMessage() {} func (x *RechargeProductResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[148] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13680,7 +13881,7 @@ func (x *RechargeProductResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RechargeProductResponse.ProtoReflect.Descriptor instead. func (*RechargeProductResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{148} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{151} } func (x *RechargeProductResponse) GetProduct() *RechargeProduct { @@ -13700,7 +13901,7 @@ type DeleteRechargeProductResponse struct { func (x *DeleteRechargeProductResponse) Reset() { *x = DeleteRechargeProductResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[149] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13712,7 +13913,7 @@ func (x *DeleteRechargeProductResponse) String() string { func (*DeleteRechargeProductResponse) ProtoMessage() {} func (x *DeleteRechargeProductResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[149] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13725,7 +13926,7 @@ func (x *DeleteRechargeProductResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteRechargeProductResponse.ProtoReflect.Descriptor instead. func (*DeleteRechargeProductResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{149} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{152} } func (x *DeleteRechargeProductResponse) GetDeleted() bool { @@ -13761,7 +13962,7 @@ type ThirdPartyPaymentMethod struct { func (x *ThirdPartyPaymentMethod) Reset() { *x = ThirdPartyPaymentMethod{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[150] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13773,7 +13974,7 @@ func (x *ThirdPartyPaymentMethod) String() string { func (*ThirdPartyPaymentMethod) ProtoMessage() {} func (x *ThirdPartyPaymentMethod) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[150] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13786,7 +13987,7 @@ func (x *ThirdPartyPaymentMethod) ProtoReflect() protoreflect.Message { // Deprecated: Use ThirdPartyPaymentMethod.ProtoReflect.Descriptor instead. func (*ThirdPartyPaymentMethod) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{150} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{153} } func (x *ThirdPartyPaymentMethod) GetMethodId() int64 { @@ -13916,7 +14117,7 @@ type ThirdPartyPaymentChannel struct { func (x *ThirdPartyPaymentChannel) Reset() { *x = ThirdPartyPaymentChannel{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[151] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13928,7 +14129,7 @@ func (x *ThirdPartyPaymentChannel) String() string { func (*ThirdPartyPaymentChannel) ProtoMessage() {} func (x *ThirdPartyPaymentChannel) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[151] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[154] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13941,7 +14142,7 @@ func (x *ThirdPartyPaymentChannel) ProtoReflect() protoreflect.Message { // Deprecated: Use ThirdPartyPaymentChannel.ProtoReflect.Descriptor instead. func (*ThirdPartyPaymentChannel) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{151} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{154} } func (x *ThirdPartyPaymentChannel) GetAppCode() string { @@ -14000,7 +14201,7 @@ type ListThirdPartyPaymentChannelsRequest struct { func (x *ListThirdPartyPaymentChannelsRequest) Reset() { *x = ListThirdPartyPaymentChannelsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[152] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14012,7 +14213,7 @@ func (x *ListThirdPartyPaymentChannelsRequest) String() string { func (*ListThirdPartyPaymentChannelsRequest) ProtoMessage() {} func (x *ListThirdPartyPaymentChannelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[152] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[155] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14025,7 +14226,7 @@ func (x *ListThirdPartyPaymentChannelsRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use ListThirdPartyPaymentChannelsRequest.ProtoReflect.Descriptor instead. func (*ListThirdPartyPaymentChannelsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{152} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{155} } func (x *ListThirdPartyPaymentChannelsRequest) GetRequestId() string { @@ -14073,7 +14274,7 @@ type ListThirdPartyPaymentChannelsResponse struct { func (x *ListThirdPartyPaymentChannelsResponse) Reset() { *x = ListThirdPartyPaymentChannelsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[153] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[156] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14085,7 +14286,7 @@ func (x *ListThirdPartyPaymentChannelsResponse) String() string { func (*ListThirdPartyPaymentChannelsResponse) ProtoMessage() {} func (x *ListThirdPartyPaymentChannelsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[153] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[156] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14098,7 +14299,7 @@ func (x *ListThirdPartyPaymentChannelsResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use ListThirdPartyPaymentChannelsResponse.ProtoReflect.Descriptor instead. func (*ListThirdPartyPaymentChannelsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{153} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{156} } func (x *ListThirdPartyPaymentChannelsResponse) GetChannels() []*ThirdPartyPaymentChannel { @@ -14122,7 +14323,7 @@ type SetThirdPartyPaymentMethodStatusRequest struct { func (x *SetThirdPartyPaymentMethodStatusRequest) Reset() { *x = SetThirdPartyPaymentMethodStatusRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[154] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[157] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14134,7 +14335,7 @@ func (x *SetThirdPartyPaymentMethodStatusRequest) String() string { func (*SetThirdPartyPaymentMethodStatusRequest) ProtoMessage() {} func (x *SetThirdPartyPaymentMethodStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[154] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[157] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14147,7 +14348,7 @@ func (x *SetThirdPartyPaymentMethodStatusRequest) ProtoReflect() protoreflect.Me // Deprecated: Use SetThirdPartyPaymentMethodStatusRequest.ProtoReflect.Descriptor instead. func (*SetThirdPartyPaymentMethodStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{154} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{157} } func (x *SetThirdPartyPaymentMethodStatusRequest) GetRequestId() string { @@ -14195,7 +14396,7 @@ type ThirdPartyPaymentMethodResponse struct { func (x *ThirdPartyPaymentMethodResponse) Reset() { *x = ThirdPartyPaymentMethodResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[155] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[158] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14207,7 +14408,7 @@ func (x *ThirdPartyPaymentMethodResponse) String() string { func (*ThirdPartyPaymentMethodResponse) ProtoMessage() {} func (x *ThirdPartyPaymentMethodResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[155] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[158] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14220,7 +14421,7 @@ func (x *ThirdPartyPaymentMethodResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ThirdPartyPaymentMethodResponse.ProtoReflect.Descriptor instead. func (*ThirdPartyPaymentMethodResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{155} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{158} } func (x *ThirdPartyPaymentMethodResponse) GetMethod() *ThirdPartyPaymentMethod { @@ -14244,7 +14445,7 @@ type UpdateThirdPartyPaymentRateRequest struct { func (x *UpdateThirdPartyPaymentRateRequest) Reset() { *x = UpdateThirdPartyPaymentRateRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[156] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[159] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14256,7 +14457,7 @@ func (x *UpdateThirdPartyPaymentRateRequest) String() string { func (*UpdateThirdPartyPaymentRateRequest) ProtoMessage() {} func (x *UpdateThirdPartyPaymentRateRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[156] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[159] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14269,7 +14470,7 @@ func (x *UpdateThirdPartyPaymentRateRequest) ProtoReflect() protoreflect.Message // Deprecated: Use UpdateThirdPartyPaymentRateRequest.ProtoReflect.Descriptor instead. func (*UpdateThirdPartyPaymentRateRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{156} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{159} } func (x *UpdateThirdPartyPaymentRateRequest) GetRequestId() string { @@ -14320,7 +14521,7 @@ type ThirdPartyPaymentMethodSyncIssue struct { func (x *ThirdPartyPaymentMethodSyncIssue) Reset() { *x = ThirdPartyPaymentMethodSyncIssue{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[157] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[160] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14332,7 +14533,7 @@ func (x *ThirdPartyPaymentMethodSyncIssue) String() string { func (*ThirdPartyPaymentMethodSyncIssue) ProtoMessage() {} func (x *ThirdPartyPaymentMethodSyncIssue) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[157] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[160] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14345,7 +14546,7 @@ func (x *ThirdPartyPaymentMethodSyncIssue) ProtoReflect() protoreflect.Message { // Deprecated: Use ThirdPartyPaymentMethodSyncIssue.ProtoReflect.Descriptor instead. func (*ThirdPartyPaymentMethodSyncIssue) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{157} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{160} } func (x *ThirdPartyPaymentMethodSyncIssue) GetCountryCode() string { @@ -14389,7 +14590,7 @@ type SyncThirdPartyPaymentMethodsRequest struct { func (x *SyncThirdPartyPaymentMethodsRequest) Reset() { *x = SyncThirdPartyPaymentMethodsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[158] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[161] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14401,7 +14602,7 @@ func (x *SyncThirdPartyPaymentMethodsRequest) String() string { func (*SyncThirdPartyPaymentMethodsRequest) ProtoMessage() {} func (x *SyncThirdPartyPaymentMethodsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[158] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[161] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14414,7 +14615,7 @@ func (x *SyncThirdPartyPaymentMethodsRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use SyncThirdPartyPaymentMethodsRequest.ProtoReflect.Descriptor instead. func (*SyncThirdPartyPaymentMethodsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{158} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{161} } func (x *SyncThirdPartyPaymentMethodsRequest) GetRequestId() string { @@ -14461,7 +14662,7 @@ type SyncThirdPartyPaymentMethodsResponse struct { func (x *SyncThirdPartyPaymentMethodsResponse) Reset() { *x = SyncThirdPartyPaymentMethodsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[159] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[162] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14473,7 +14674,7 @@ func (x *SyncThirdPartyPaymentMethodsResponse) String() string { func (*SyncThirdPartyPaymentMethodsResponse) ProtoMessage() {} func (x *SyncThirdPartyPaymentMethodsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[159] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[162] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14486,7 +14687,7 @@ func (x *SyncThirdPartyPaymentMethodsResponse) ProtoReflect() protoreflect.Messa // Deprecated: Use SyncThirdPartyPaymentMethodsResponse.ProtoReflect.Descriptor instead. func (*SyncThirdPartyPaymentMethodsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{159} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{162} } func (x *SyncThirdPartyPaymentMethodsResponse) GetProviderCode() string { @@ -14553,7 +14754,7 @@ type H5RechargeOptionsRequest struct { func (x *H5RechargeOptionsRequest) Reset() { *x = H5RechargeOptionsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[160] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[163] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14565,7 +14766,7 @@ func (x *H5RechargeOptionsRequest) String() string { func (*H5RechargeOptionsRequest) ProtoMessage() {} func (x *H5RechargeOptionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[160] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[163] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14578,7 +14779,7 @@ func (x *H5RechargeOptionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use H5RechargeOptionsRequest.ProtoReflect.Descriptor instead. func (*H5RechargeOptionsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{160} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{163} } func (x *H5RechargeOptionsRequest) GetRequestId() string { @@ -14636,7 +14837,7 @@ type H5RechargeOptionsResponse struct { func (x *H5RechargeOptionsResponse) Reset() { *x = H5RechargeOptionsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[161] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[164] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14648,7 +14849,7 @@ func (x *H5RechargeOptionsResponse) String() string { func (*H5RechargeOptionsResponse) ProtoMessage() {} func (x *H5RechargeOptionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[161] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[164] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14661,7 +14862,7 @@ func (x *H5RechargeOptionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use H5RechargeOptionsResponse.ProtoReflect.Descriptor instead. func (*H5RechargeOptionsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{161} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{164} } func (x *H5RechargeOptionsResponse) GetProducts() []*RechargeProduct { @@ -14730,7 +14931,7 @@ type ExternalRechargeOrder struct { func (x *ExternalRechargeOrder) Reset() { *x = ExternalRechargeOrder{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[162] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[165] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14742,7 +14943,7 @@ func (x *ExternalRechargeOrder) String() string { func (*ExternalRechargeOrder) ProtoMessage() {} func (x *ExternalRechargeOrder) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[162] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[165] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14755,7 +14956,7 @@ func (x *ExternalRechargeOrder) ProtoReflect() protoreflect.Message { // Deprecated: Use ExternalRechargeOrder.ProtoReflect.Descriptor instead. func (*ExternalRechargeOrder) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{162} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{165} } func (x *ExternalRechargeOrder) GetOrderId() string { @@ -14983,7 +15184,7 @@ type CreateH5RechargeOrderRequest struct { func (x *CreateH5RechargeOrderRequest) Reset() { *x = CreateH5RechargeOrderRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[163] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[166] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14995,7 +15196,7 @@ func (x *CreateH5RechargeOrderRequest) String() string { func (*CreateH5RechargeOrderRequest) ProtoMessage() {} func (x *CreateH5RechargeOrderRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[163] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[166] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15008,7 +15209,7 @@ func (x *CreateH5RechargeOrderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateH5RechargeOrderRequest.ProtoReflect.Descriptor instead. func (*CreateH5RechargeOrderRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{163} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{166} } func (x *CreateH5RechargeOrderRequest) GetRequestId() string { @@ -15160,7 +15361,7 @@ type CreateTemporaryRechargeOrderRequest struct { func (x *CreateTemporaryRechargeOrderRequest) Reset() { *x = CreateTemporaryRechargeOrderRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[164] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[167] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15172,7 +15373,7 @@ func (x *CreateTemporaryRechargeOrderRequest) String() string { func (*CreateTemporaryRechargeOrderRequest) ProtoMessage() {} func (x *CreateTemporaryRechargeOrderRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[164] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[167] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15185,7 +15386,7 @@ func (x *CreateTemporaryRechargeOrderRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use CreateTemporaryRechargeOrderRequest.ProtoReflect.Descriptor instead. func (*CreateTemporaryRechargeOrderRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{164} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{167} } func (x *CreateTemporaryRechargeOrderRequest) GetRequestId() string { @@ -15298,7 +15499,7 @@ type GetTemporaryRechargeOrderRequest struct { func (x *GetTemporaryRechargeOrderRequest) Reset() { *x = GetTemporaryRechargeOrderRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[165] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[168] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15310,7 +15511,7 @@ func (x *GetTemporaryRechargeOrderRequest) String() string { func (*GetTemporaryRechargeOrderRequest) ProtoMessage() {} func (x *GetTemporaryRechargeOrderRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[165] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[168] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15323,7 +15524,7 @@ func (x *GetTemporaryRechargeOrderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetTemporaryRechargeOrderRequest.ProtoReflect.Descriptor instead. func (*GetTemporaryRechargeOrderRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{165} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{168} } func (x *GetTemporaryRechargeOrderRequest) GetRequestId() string { @@ -15363,7 +15564,7 @@ type ListTemporaryRechargeOrdersRequest struct { func (x *ListTemporaryRechargeOrdersRequest) Reset() { *x = ListTemporaryRechargeOrdersRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[166] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[169] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15375,7 +15576,7 @@ func (x *ListTemporaryRechargeOrdersRequest) String() string { func (*ListTemporaryRechargeOrdersRequest) ProtoMessage() {} func (x *ListTemporaryRechargeOrdersRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[166] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[169] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15388,7 +15589,7 @@ func (x *ListTemporaryRechargeOrdersRequest) ProtoReflect() protoreflect.Message // Deprecated: Use ListTemporaryRechargeOrdersRequest.ProtoReflect.Descriptor instead. func (*ListTemporaryRechargeOrdersRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{166} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{169} } func (x *ListTemporaryRechargeOrdersRequest) GetRequestId() string { @@ -15451,7 +15652,7 @@ type ListTemporaryRechargeOrdersResponse struct { func (x *ListTemporaryRechargeOrdersResponse) Reset() { *x = ListTemporaryRechargeOrdersResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[167] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[170] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15463,7 +15664,7 @@ func (x *ListTemporaryRechargeOrdersResponse) String() string { func (*ListTemporaryRechargeOrdersResponse) ProtoMessage() {} func (x *ListTemporaryRechargeOrdersResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[167] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[170] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15476,7 +15677,7 @@ func (x *ListTemporaryRechargeOrdersResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use ListTemporaryRechargeOrdersResponse.ProtoReflect.Descriptor instead. func (*ListTemporaryRechargeOrdersResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{167} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{170} } func (x *ListTemporaryRechargeOrdersResponse) GetOrders() []*ExternalRechargeOrder { @@ -15507,7 +15708,7 @@ type SubmitH5RechargeTxRequest struct { func (x *SubmitH5RechargeTxRequest) Reset() { *x = SubmitH5RechargeTxRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[168] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[171] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15519,7 +15720,7 @@ func (x *SubmitH5RechargeTxRequest) String() string { func (*SubmitH5RechargeTxRequest) ProtoMessage() {} func (x *SubmitH5RechargeTxRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[168] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[171] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15532,7 +15733,7 @@ func (x *SubmitH5RechargeTxRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitH5RechargeTxRequest.ProtoReflect.Descriptor instead. func (*SubmitH5RechargeTxRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{168} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{171} } func (x *SubmitH5RechargeTxRequest) GetRequestId() string { @@ -15583,7 +15784,7 @@ type GetH5RechargeOrderRequest struct { func (x *GetH5RechargeOrderRequest) Reset() { *x = GetH5RechargeOrderRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[169] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[172] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15595,7 +15796,7 @@ func (x *GetH5RechargeOrderRequest) String() string { func (*GetH5RechargeOrderRequest) ProtoMessage() {} func (x *GetH5RechargeOrderRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[169] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[172] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15608,7 +15809,7 @@ func (x *GetH5RechargeOrderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetH5RechargeOrderRequest.ProtoReflect.Descriptor instead. func (*GetH5RechargeOrderRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{169} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{172} } func (x *GetH5RechargeOrderRequest) GetRequestId() string { @@ -15649,7 +15850,7 @@ type H5RechargeOrderResponse struct { func (x *H5RechargeOrderResponse) Reset() { *x = H5RechargeOrderResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[170] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[173] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15661,7 +15862,7 @@ func (x *H5RechargeOrderResponse) String() string { func (*H5RechargeOrderResponse) ProtoMessage() {} func (x *H5RechargeOrderResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[170] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[173] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15674,7 +15875,7 @@ func (x *H5RechargeOrderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use H5RechargeOrderResponse.ProtoReflect.Descriptor instead. func (*H5RechargeOrderResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{170} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{173} } func (x *H5RechargeOrderResponse) GetOrder() *ExternalRechargeOrder { @@ -15704,7 +15905,7 @@ type VerifyCoinSellerRechargeReceiptRequest struct { func (x *VerifyCoinSellerRechargeReceiptRequest) Reset() { *x = VerifyCoinSellerRechargeReceiptRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[171] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[174] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15716,7 +15917,7 @@ func (x *VerifyCoinSellerRechargeReceiptRequest) String() string { func (*VerifyCoinSellerRechargeReceiptRequest) ProtoMessage() {} func (x *VerifyCoinSellerRechargeReceiptRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[171] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[174] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15729,7 +15930,7 @@ func (x *VerifyCoinSellerRechargeReceiptRequest) ProtoReflect() protoreflect.Mes // Deprecated: Use VerifyCoinSellerRechargeReceiptRequest.ProtoReflect.Descriptor instead. func (*VerifyCoinSellerRechargeReceiptRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{171} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{174} } func (x *VerifyCoinSellerRechargeReceiptRequest) GetRequestId() string { @@ -15830,7 +16031,7 @@ type VerifyCoinSellerRechargeReceiptResponse struct { func (x *VerifyCoinSellerRechargeReceiptResponse) Reset() { *x = VerifyCoinSellerRechargeReceiptResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[172] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[175] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15842,7 +16043,7 @@ func (x *VerifyCoinSellerRechargeReceiptResponse) String() string { func (*VerifyCoinSellerRechargeReceiptResponse) ProtoMessage() {} func (x *VerifyCoinSellerRechargeReceiptResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[172] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[175] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15855,7 +16056,7 @@ func (x *VerifyCoinSellerRechargeReceiptResponse) ProtoReflect() protoreflect.Me // Deprecated: Use VerifyCoinSellerRechargeReceiptResponse.ProtoReflect.Descriptor instead. func (*VerifyCoinSellerRechargeReceiptResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{172} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{175} } func (x *VerifyCoinSellerRechargeReceiptResponse) GetVerified() bool { @@ -15956,7 +16157,7 @@ type HandleMifapayNotifyRequest struct { func (x *HandleMifapayNotifyRequest) Reset() { *x = HandleMifapayNotifyRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[173] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[176] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15968,7 +16169,7 @@ func (x *HandleMifapayNotifyRequest) String() string { func (*HandleMifapayNotifyRequest) ProtoMessage() {} func (x *HandleMifapayNotifyRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[173] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[176] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15981,7 +16182,7 @@ func (x *HandleMifapayNotifyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HandleMifapayNotifyRequest.ProtoReflect.Descriptor instead. func (*HandleMifapayNotifyRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{173} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{176} } func (x *HandleMifapayNotifyRequest) GetRequestId() string { @@ -16031,7 +16232,7 @@ type HandleMifapayNotifyResponse struct { func (x *HandleMifapayNotifyResponse) Reset() { *x = HandleMifapayNotifyResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[174] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[177] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16043,7 +16244,7 @@ func (x *HandleMifapayNotifyResponse) String() string { func (*HandleMifapayNotifyResponse) ProtoMessage() {} func (x *HandleMifapayNotifyResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[174] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[177] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16056,7 +16257,7 @@ func (x *HandleMifapayNotifyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HandleMifapayNotifyResponse.ProtoReflect.Descriptor instead. func (*HandleMifapayNotifyResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{174} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{177} } func (x *HandleMifapayNotifyResponse) GetAccepted() bool { @@ -16093,7 +16294,7 @@ type HandleV5PayNotifyRequest struct { func (x *HandleV5PayNotifyRequest) Reset() { *x = HandleV5PayNotifyRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[175] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[178] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16105,7 +16306,7 @@ func (x *HandleV5PayNotifyRequest) String() string { func (*HandleV5PayNotifyRequest) ProtoMessage() {} func (x *HandleV5PayNotifyRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[175] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[178] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16118,7 +16319,7 @@ func (x *HandleV5PayNotifyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HandleV5PayNotifyRequest.ProtoReflect.Descriptor instead. func (*HandleV5PayNotifyRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{175} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{178} } func (x *HandleV5PayNotifyRequest) GetRequestId() string { @@ -16161,7 +16362,7 @@ type HandleV5PayNotifyResponse struct { func (x *HandleV5PayNotifyResponse) Reset() { *x = HandleV5PayNotifyResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[176] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[179] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16173,7 +16374,7 @@ func (x *HandleV5PayNotifyResponse) String() string { func (*HandleV5PayNotifyResponse) ProtoMessage() {} func (x *HandleV5PayNotifyResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[176] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[179] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16186,7 +16387,7 @@ func (x *HandleV5PayNotifyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HandleV5PayNotifyResponse.ProtoReflect.Descriptor instead. func (*HandleV5PayNotifyResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{176} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{179} } func (x *HandleV5PayNotifyResponse) GetAccepted() bool { @@ -16225,7 +16426,7 @@ type DiamondExchangeRule struct { func (x *DiamondExchangeRule) Reset() { *x = DiamondExchangeRule{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[177] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[180] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16237,7 +16438,7 @@ func (x *DiamondExchangeRule) String() string { func (*DiamondExchangeRule) ProtoMessage() {} func (x *DiamondExchangeRule) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[177] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[180] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16250,7 +16451,7 @@ func (x *DiamondExchangeRule) ProtoReflect() protoreflect.Message { // Deprecated: Use DiamondExchangeRule.ProtoReflect.Descriptor instead. func (*DiamondExchangeRule) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{177} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{180} } func (x *DiamondExchangeRule) GetExchangeType() string { @@ -16307,7 +16508,7 @@ type GetDiamondExchangeConfigRequest struct { func (x *GetDiamondExchangeConfigRequest) Reset() { *x = GetDiamondExchangeConfigRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[178] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[181] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16319,7 +16520,7 @@ func (x *GetDiamondExchangeConfigRequest) String() string { func (*GetDiamondExchangeConfigRequest) ProtoMessage() {} func (x *GetDiamondExchangeConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[178] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[181] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16332,7 +16533,7 @@ func (x *GetDiamondExchangeConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDiamondExchangeConfigRequest.ProtoReflect.Descriptor instead. func (*GetDiamondExchangeConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{178} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{181} } func (x *GetDiamondExchangeConfigRequest) GetRequestId() string { @@ -16366,7 +16567,7 @@ type GetDiamondExchangeConfigResponse struct { func (x *GetDiamondExchangeConfigResponse) Reset() { *x = GetDiamondExchangeConfigResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[179] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[182] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16378,7 +16579,7 @@ func (x *GetDiamondExchangeConfigResponse) String() string { func (*GetDiamondExchangeConfigResponse) ProtoMessage() {} func (x *GetDiamondExchangeConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[179] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[182] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16391,7 +16592,7 @@ func (x *GetDiamondExchangeConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDiamondExchangeConfigResponse.ProtoReflect.Descriptor instead. func (*GetDiamondExchangeConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{179} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{182} } func (x *GetDiamondExchangeConfigResponse) GetRules() []*DiamondExchangeRule { @@ -16424,7 +16625,7 @@ type WalletTransaction struct { func (x *WalletTransaction) Reset() { *x = WalletTransaction{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[180] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[183] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16436,7 +16637,7 @@ func (x *WalletTransaction) String() string { func (*WalletTransaction) ProtoMessage() {} func (x *WalletTransaction) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[180] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[183] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16449,7 +16650,7 @@ func (x *WalletTransaction) ProtoReflect() protoreflect.Message { // Deprecated: Use WalletTransaction.ProtoReflect.Descriptor instead. func (*WalletTransaction) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{180} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{183} } func (x *WalletTransaction) GetEntryId() int64 { @@ -16558,7 +16759,7 @@ type ListWalletTransactionsRequest struct { func (x *ListWalletTransactionsRequest) Reset() { *x = ListWalletTransactionsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[181] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[184] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16570,7 +16771,7 @@ func (x *ListWalletTransactionsRequest) String() string { func (*ListWalletTransactionsRequest) ProtoMessage() {} func (x *ListWalletTransactionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[181] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[184] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16583,7 +16784,7 @@ func (x *ListWalletTransactionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWalletTransactionsRequest.ProtoReflect.Descriptor instead. func (*ListWalletTransactionsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{181} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{184} } func (x *ListWalletTransactionsRequest) GetRequestId() string { @@ -16639,7 +16840,7 @@ type ListWalletTransactionsResponse struct { func (x *ListWalletTransactionsResponse) Reset() { *x = ListWalletTransactionsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[182] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[185] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16651,7 +16852,7 @@ func (x *ListWalletTransactionsResponse) String() string { func (*ListWalletTransactionsResponse) ProtoMessage() {} func (x *ListWalletTransactionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[182] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[185] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16664,7 +16865,7 @@ func (x *ListWalletTransactionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWalletTransactionsResponse.ProtoReflect.Descriptor instead. func (*ListWalletTransactionsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{182} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{185} } func (x *ListWalletTransactionsResponse) GetTransactions() []*WalletTransaction { @@ -16699,7 +16900,7 @@ type VipRewardItem struct { func (x *VipRewardItem) Reset() { *x = VipRewardItem{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[183] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[186] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16711,7 +16912,7 @@ func (x *VipRewardItem) String() string { func (*VipRewardItem) ProtoMessage() {} func (x *VipRewardItem) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[183] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[186] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16724,7 +16925,7 @@ func (x *VipRewardItem) ProtoReflect() protoreflect.Message { // Deprecated: Use VipRewardItem.ProtoReflect.Descriptor instead. func (*VipRewardItem) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{183} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{186} } func (x *VipRewardItem) GetResourceId() int64 { @@ -16790,6 +16991,318 @@ func (x *VipRewardItem) GetAnimationUrl() string { return "" } +// VipProgramConfig 是按 App 隔离的 VIP 规则开关。业务服务只能依据这些显式策略执行, +// 禁止通过 app_code 分支推断升级有效期、权益继承或后台发放语义。 +type VipProgramConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + // program_type: legacy_timed/tiered_privilege_v1。 + ProgramType string `protobuf:"bytes,2,opt,name=program_type,json=programType,proto3" json:"program_type,omitempty"` + LevelCount int32 `protobuf:"varint,3,opt,name=level_count,json=levelCount,proto3" json:"level_count,omitempty"` + // same_level_expiry_policy: extend_remaining;同级购买从当前有效截止时间续期。 + SameLevelExpiryPolicy string `protobuf:"bytes,4,opt,name=same_level_expiry_policy,json=sameLevelExpiryPolicy,proto3" json:"same_level_expiry_policy,omitempty"` + // upgrade_expiry_policy: extend_remaining/replace_from_now。 + UpgradeExpiryPolicy string `protobuf:"bytes,5,opt,name=upgrade_expiry_policy,json=upgradeExpiryPolicy,proto3" json:"upgrade_expiry_policy,omitempty"` + // downgrade_purchase_policy 首版固定 reject,保留字段用于后台完整展示当前规则。 + DowngradePurchasePolicy string `protobuf:"bytes,6,opt,name=downgrade_purchase_policy,json=downgradePurchasePolicy,proto3" json:"downgrade_purchase_policy,omitempty"` + // benefit_inheritance_policy 当前固定 target_only;每级提交最终完整集合,不做自动继承。 + BenefitInheritancePolicy string `protobuf:"bytes,7,opt,name=benefit_inheritance_policy,json=benefitInheritancePolicy,proto3" json:"benefit_inheritance_policy,omitempty"` + // grant_mode: direct_membership/trial_card,决定后台“赠送 VIP”的真实账务对象。 + GrantMode string `protobuf:"bytes,8,opt,name=grant_mode,json=grantMode,proto3" json:"grant_mode,omitempty"` + TrialCardEnabled bool `protobuf:"varint,9,opt,name=trial_card_enabled,json=trialCardEnabled,proto3" json:"trial_card_enabled,omitempty"` + Status string `protobuf:"bytes,10,opt,name=status,proto3" json:"status,omitempty"` + ConfigVersion int64 `protobuf:"varint,11,opt,name=config_version,json=configVersion,proto3" json:"config_version,omitempty"` + UpdatedByAdminId int64 `protobuf:"varint,12,opt,name=updated_by_admin_id,json=updatedByAdminId,proto3" json:"updated_by_admin_id,omitempty"` + CreatedAtMs int64 `protobuf:"varint,13,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,14,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` +} + +func (x *VipProgramConfig) Reset() { + *x = VipProgramConfig{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[187] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VipProgramConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VipProgramConfig) ProtoMessage() {} + +func (x *VipProgramConfig) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[187] + 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 VipProgramConfig.ProtoReflect.Descriptor instead. +func (*VipProgramConfig) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{187} +} + +func (x *VipProgramConfig) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *VipProgramConfig) GetProgramType() string { + if x != nil { + return x.ProgramType + } + return "" +} + +func (x *VipProgramConfig) GetLevelCount() int32 { + if x != nil { + return x.LevelCount + } + return 0 +} + +func (x *VipProgramConfig) GetSameLevelExpiryPolicy() string { + if x != nil { + return x.SameLevelExpiryPolicy + } + return "" +} + +func (x *VipProgramConfig) GetUpgradeExpiryPolicy() string { + if x != nil { + return x.UpgradeExpiryPolicy + } + return "" +} + +func (x *VipProgramConfig) GetDowngradePurchasePolicy() string { + if x != nil { + return x.DowngradePurchasePolicy + } + return "" +} + +func (x *VipProgramConfig) GetBenefitInheritancePolicy() string { + if x != nil { + return x.BenefitInheritancePolicy + } + return "" +} + +func (x *VipProgramConfig) GetGrantMode() string { + if x != nil { + return x.GrantMode + } + return "" +} + +func (x *VipProgramConfig) GetTrialCardEnabled() bool { + if x != nil { + return x.TrialCardEnabled + } + return false +} + +func (x *VipProgramConfig) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *VipProgramConfig) GetConfigVersion() int64 { + if x != nil { + return x.ConfigVersion + } + return 0 +} + +func (x *VipProgramConfig) GetUpdatedByAdminId() int64 { + if x != nil { + return x.UpdatedByAdminId + } + return 0 +} + +func (x *VipProgramConfig) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *VipProgramConfig) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +// VipBenefit 是服务端可执行的权益事实。resource_id 为 0 表示纯功能特权,非 0 时 +// 指向钱包资源;trial_enabled=false 的权益不会进入体验卡产生的 effective_benefits。 +type VipBenefit struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BenefitCode string `protobuf:"bytes,1,opt,name=benefit_code,json=benefitCode,proto3" json:"benefit_code,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // benefit_type: decoration/function。 + BenefitType string `protobuf:"bytes,3,opt,name=benefit_type,json=benefitType,proto3" json:"benefit_type,omitempty"` + // unlock_level 只作为 P1 默认矩阵的来源信息;后台仍按每个等级提交完整权益集合, + // 运行时不得据此自动补齐高等级权益。 + UnlockLevel int32 `protobuf:"varint,4,opt,name=unlock_level,json=unlockLevel,proto3" json:"unlock_level,omitempty"` + Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` + TrialEnabled bool `protobuf:"varint,6,opt,name=trial_enabled,json=trialEnabled,proto3" json:"trial_enabled,omitempty"` + ResourceId int64 `protobuf:"varint,7,opt,name=resource_id,json=resourceId,proto3" json:"resource_id,omitempty"` + ResourceType string `protobuf:"bytes,8,opt,name=resource_type,json=resourceType,proto3" json:"resource_type,omitempty"` + // execution_scope 标识 wallet/room/user/notice 等实际权限执行方,不代表服务间直连。 + ExecutionScope string `protobuf:"bytes,9,opt,name=execution_scope,json=executionScope,proto3" json:"execution_scope,omitempty"` + AutoEquip bool `protobuf:"varint,10,opt,name=auto_equip,json=autoEquip,proto3" json:"auto_equip,omitempty"` + SortOrder int32 `protobuf:"varint,11,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + MetadataJson string `protobuf:"bytes,12,opt,name=metadata_json,json=metadataJson,proto3" json:"metadata_json,omitempty"` + CreatedAtMs int64 `protobuf:"varint,13,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,14,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` +} + +func (x *VipBenefit) Reset() { + *x = VipBenefit{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[188] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VipBenefit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VipBenefit) ProtoMessage() {} + +func (x *VipBenefit) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[188] + 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 VipBenefit.ProtoReflect.Descriptor instead. +func (*VipBenefit) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{188} +} + +func (x *VipBenefit) GetBenefitCode() string { + if x != nil { + return x.BenefitCode + } + return "" +} + +func (x *VipBenefit) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *VipBenefit) GetBenefitType() string { + if x != nil { + return x.BenefitType + } + return "" +} + +func (x *VipBenefit) GetUnlockLevel() int32 { + if x != nil { + return x.UnlockLevel + } + return 0 +} + +func (x *VipBenefit) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *VipBenefit) GetTrialEnabled() bool { + if x != nil { + return x.TrialEnabled + } + return false +} + +func (x *VipBenefit) GetResourceId() int64 { + if x != nil { + return x.ResourceId + } + return 0 +} + +func (x *VipBenefit) GetResourceType() string { + if x != nil { + return x.ResourceType + } + return "" +} + +func (x *VipBenefit) GetExecutionScope() string { + if x != nil { + return x.ExecutionScope + } + return "" +} + +func (x *VipBenefit) GetAutoEquip() bool { + if x != nil { + return x.AutoEquip + } + return false +} + +func (x *VipBenefit) GetSortOrder() int32 { + if x != nil { + return x.SortOrder + } + return 0 +} + +func (x *VipBenefit) GetMetadataJson() string { + if x != nil { + return x.MetadataJson + } + return "" +} + +func (x *VipBenefit) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *VipBenefit) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + type VipLevel struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -16810,11 +17323,14 @@ type VipLevel struct { PurchaseLockedReason string `protobuf:"bytes,13,opt,name=purchase_locked_reason,json=purchaseLockedReason,proto3" json:"purchase_locked_reason,omitempty"` CreatedAtMs int64 `protobuf:"varint,14,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` UpdatedAtMs int64 `protobuf:"varint,15,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + // benefits 是后台为该等级显式保存的最终权益集合,运行时不按 unlock_level 自动继承。 + Benefits []*VipBenefit `protobuf:"bytes,16,rep,name=benefits,proto3" json:"benefits,omitempty"` + ConfigVersion int64 `protobuf:"varint,17,opt,name=config_version,json=configVersion,proto3" json:"config_version,omitempty"` } func (x *VipLevel) Reset() { *x = VipLevel{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[184] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[189] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16826,7 +17342,7 @@ func (x *VipLevel) String() string { func (*VipLevel) ProtoMessage() {} func (x *VipLevel) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[184] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[189] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16839,7 +17355,7 @@ func (x *VipLevel) ProtoReflect() protoreflect.Message { // Deprecated: Use VipLevel.ProtoReflect.Descriptor instead. func (*VipLevel) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{184} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{189} } func (x *VipLevel) GetLevel() int32 { @@ -16947,23 +17463,39 @@ func (x *VipLevel) GetUpdatedAtMs() int64 { return 0 } +func (x *VipLevel) GetBenefits() []*VipBenefit { + if x != nil { + return x.Benefits + } + return nil +} + +func (x *VipLevel) GetConfigVersion() int64 { + if x != nil { + return x.ConfigVersion + } + return 0 +} + type UserVip struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - Level int32 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"` - Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` - Active bool `protobuf:"varint,4,opt,name=active,proto3" json:"active,omitempty"` - StartedAtMs int64 `protobuf:"varint,5,opt,name=started_at_ms,json=startedAtMs,proto3" json:"started_at_ms,omitempty"` - ExpiresAtMs int64 `protobuf:"varint,6,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` - UpdatedAtMs int64 `protobuf:"varint,7,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Level int32 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Active bool `protobuf:"varint,4,opt,name=active,proto3" json:"active,omitempty"` + StartedAtMs int64 `protobuf:"varint,5,opt,name=started_at_ms,json=startedAtMs,proto3" json:"started_at_ms,omitempty"` + ExpiresAtMs int64 `protobuf:"varint,6,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,7,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + ProgramType string `protobuf:"bytes,8,opt,name=program_type,json=programType,proto3" json:"program_type,omitempty"` + ConfigVersion int64 `protobuf:"varint,9,opt,name=config_version,json=configVersion,proto3" json:"config_version,omitempty"` } func (x *UserVip) Reset() { *x = UserVip{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[185] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[190] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16975,7 +17507,7 @@ func (x *UserVip) String() string { func (*UserVip) ProtoMessage() {} func (x *UserVip) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[185] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[190] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16988,7 +17520,7 @@ func (x *UserVip) ProtoReflect() protoreflect.Message { // Deprecated: Use UserVip.ProtoReflect.Descriptor instead. func (*UserVip) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{185} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{190} } func (x *UserVip) GetUserId() int64 { @@ -17040,6 +17572,371 @@ func (x *UserVip) GetUpdatedAtMs() int64 { return 0 } +func (x *UserVip) GetProgramType() string { + if x != nil { + return x.ProgramType + } + return "" +} + +func (x *UserVip) GetConfigVersion() int64 { + if x != nil { + return x.ConfigVersion + } + return 0 +} + +// VipTrialCard 是体验卡背包权益的 VIP 投影。entitlement_id 复用通用背包佩戴事实, +// 切换卡片只改变装备记录,不修改任何卡片自身的 expires_at_ms。 +type VipTrialCard struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TrialCardId string `protobuf:"bytes,1,opt,name=trial_card_id,json=trialCardId,proto3" json:"trial_card_id,omitempty"` + EntitlementId string `protobuf:"bytes,2,opt,name=entitlement_id,json=entitlementId,proto3" json:"entitlement_id,omitempty"` + ResourceId int64 `protobuf:"varint,3,opt,name=resource_id,json=resourceId,proto3" json:"resource_id,omitempty"` + UserId int64 `protobuf:"varint,4,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Level int32 `protobuf:"varint,5,opt,name=level,proto3" json:"level,omitempty"` + Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"` + Status string `protobuf:"bytes,7,opt,name=status,proto3" json:"status,omitempty"` + Equipped bool `protobuf:"varint,8,opt,name=equipped,proto3" json:"equipped,omitempty"` + DurationMs int64 `protobuf:"varint,9,opt,name=duration_ms,json=durationMs,proto3" json:"duration_ms,omitempty"` + EffectiveAtMs int64 `protobuf:"varint,10,opt,name=effective_at_ms,json=effectiveAtMs,proto3" json:"effective_at_ms,omitempty"` + ExpiresAtMs int64 `protobuf:"varint,11,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` + RemainingDurationMs int64 `protobuf:"varint,12,opt,name=remaining_duration_ms,json=remainingDurationMs,proto3" json:"remaining_duration_ms,omitempty"` + GrantSource string `protobuf:"bytes,13,opt,name=grant_source,json=grantSource,proto3" json:"grant_source,omitempty"` + SourceGrantId string `protobuf:"bytes,14,opt,name=source_grant_id,json=sourceGrantId,proto3" json:"source_grant_id,omitempty"` + CreatedAtMs int64 `protobuf:"varint,15,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,16,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` +} + +func (x *VipTrialCard) Reset() { + *x = VipTrialCard{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[191] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VipTrialCard) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VipTrialCard) ProtoMessage() {} + +func (x *VipTrialCard) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[191] + 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 VipTrialCard.ProtoReflect.Descriptor instead. +func (*VipTrialCard) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{191} +} + +func (x *VipTrialCard) GetTrialCardId() string { + if x != nil { + return x.TrialCardId + } + return "" +} + +func (x *VipTrialCard) GetEntitlementId() string { + if x != nil { + return x.EntitlementId + } + return "" +} + +func (x *VipTrialCard) GetResourceId() int64 { + if x != nil { + return x.ResourceId + } + return 0 +} + +func (x *VipTrialCard) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *VipTrialCard) GetLevel() int32 { + if x != nil { + return x.Level + } + return 0 +} + +func (x *VipTrialCard) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *VipTrialCard) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *VipTrialCard) GetEquipped() bool { + if x != nil { + return x.Equipped + } + return false +} + +func (x *VipTrialCard) GetDurationMs() int64 { + if x != nil { + return x.DurationMs + } + return 0 +} + +func (x *VipTrialCard) GetEffectiveAtMs() int64 { + if x != nil { + return x.EffectiveAtMs + } + return 0 +} + +func (x *VipTrialCard) GetExpiresAtMs() int64 { + if x != nil { + return x.ExpiresAtMs + } + return 0 +} + +func (x *VipTrialCard) GetRemainingDurationMs() int64 { + if x != nil { + return x.RemainingDurationMs + } + return 0 +} + +func (x *VipTrialCard) GetGrantSource() string { + if x != nil { + return x.GrantSource + } + return "" +} + +func (x *VipTrialCard) GetSourceGrantId() string { + if x != nil { + return x.SourceGrantId + } + return "" +} + +func (x *VipTrialCard) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *VipTrialCard) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +// VipUserSettings 是用户按 App 隔离的可关闭 VIP 功能偏好。没有持久记录时 wallet +// 仍返回两个默认开启值,updated_at_ms=0;设置本身不授予任何 VIP 权益。 +type VipUserSettings struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + RoomEntryNoticeEnabled bool `protobuf:"varint,3,opt,name=room_entry_notice_enabled,json=roomEntryNoticeEnabled,proto3" json:"room_entry_notice_enabled,omitempty"` + OnlineGlobalNoticeEnabled bool `protobuf:"varint,4,opt,name=online_global_notice_enabled,json=onlineGlobalNoticeEnabled,proto3" json:"online_global_notice_enabled,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,5,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` +} + +func (x *VipUserSettings) Reset() { + *x = VipUserSettings{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[192] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VipUserSettings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VipUserSettings) ProtoMessage() {} + +func (x *VipUserSettings) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[192] + 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 VipUserSettings.ProtoReflect.Descriptor instead. +func (*VipUserSettings) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{192} +} + +func (x *VipUserSettings) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *VipUserSettings) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *VipUserSettings) GetRoomEntryNoticeEnabled() bool { + if x != nil { + return x.RoomEntryNoticeEnabled + } + return false +} + +func (x *VipUserSettings) GetOnlineGlobalNoticeEnabled() bool { + if x != nil { + return x.OnlineGlobalNoticeEnabled + } + return false +} + +func (x *VipUserSettings) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +// VipState 同时返回付费会员、当前佩戴体验卡和最终生效会员,避免调用方自行合并两层状态。 +// effective_benefits 是钱包按 paid/trial 来源、该等级显式矩阵和 trial_enabled 过滤后的资格事实; +// 对可关闭通知,执行方还必须结合 user_settings 或调用 CheckVipBenefit。 +type VipState struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PaidVip *UserVip `protobuf:"bytes,1,opt,name=paid_vip,json=paidVip,proto3" json:"paid_vip,omitempty"` + EquippedTrialCard *VipTrialCard `protobuf:"bytes,2,opt,name=equipped_trial_card,json=equippedTrialCard,proto3" json:"equipped_trial_card,omitempty"` + EffectiveVip *UserVip `protobuf:"bytes,3,opt,name=effective_vip,json=effectiveVip,proto3" json:"effective_vip,omitempty"` + // effective_source: paid/trial/none。 + EffectiveSource string `protobuf:"bytes,4,opt,name=effective_source,json=effectiveSource,proto3" json:"effective_source,omitempty"` + EffectiveBenefits []*VipBenefit `protobuf:"bytes,5,rep,name=effective_benefits,json=effectiveBenefits,proto3" json:"effective_benefits,omitempty"` + EvaluatedAtMs int64 `protobuf:"varint,6,opt,name=evaluated_at_ms,json=evaluatedAtMs,proto3" json:"evaluated_at_ms,omitempty"` + ProgramConfig *VipProgramConfig `protobuf:"bytes,7,opt,name=program_config,json=programConfig,proto3" json:"program_config,omitempty"` + UserSettings *VipUserSettings `protobuf:"bytes,8,opt,name=user_settings,json=userSettings,proto3" json:"user_settings,omitempty"` +} + +func (x *VipState) Reset() { + *x = VipState{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[193] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VipState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VipState) ProtoMessage() {} + +func (x *VipState) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[193] + 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 VipState.ProtoReflect.Descriptor instead. +func (*VipState) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{193} +} + +func (x *VipState) GetPaidVip() *UserVip { + if x != nil { + return x.PaidVip + } + return nil +} + +func (x *VipState) GetEquippedTrialCard() *VipTrialCard { + if x != nil { + return x.EquippedTrialCard + } + return nil +} + +func (x *VipState) GetEffectiveVip() *UserVip { + if x != nil { + return x.EffectiveVip + } + return nil +} + +func (x *VipState) GetEffectiveSource() string { + if x != nil { + return x.EffectiveSource + } + return "" +} + +func (x *VipState) GetEffectiveBenefits() []*VipBenefit { + if x != nil { + return x.EffectiveBenefits + } + return nil +} + +func (x *VipState) GetEvaluatedAtMs() int64 { + if x != nil { + return x.EvaluatedAtMs + } + return 0 +} + +func (x *VipState) GetProgramConfig() *VipProgramConfig { + if x != nil { + return x.ProgramConfig + } + return nil +} + +func (x *VipState) GetUserSettings() *VipUserSettings { + if x != nil { + return x.UserSettings + } + return nil +} + type ListVipPackagesRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -17052,7 +17949,7 @@ type ListVipPackagesRequest struct { func (x *ListVipPackagesRequest) Reset() { *x = ListVipPackagesRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[186] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[194] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17064,7 +17961,7 @@ func (x *ListVipPackagesRequest) String() string { func (*ListVipPackagesRequest) ProtoMessage() {} func (x *ListVipPackagesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[186] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[194] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17077,7 +17974,7 @@ func (x *ListVipPackagesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListVipPackagesRequest.ProtoReflect.Descriptor instead. func (*ListVipPackagesRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{186} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{194} } func (x *ListVipPackagesRequest) GetRequestId() string { @@ -17106,13 +18003,15 @@ type ListVipPackagesResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - CurrentVip *UserVip `protobuf:"bytes,1,opt,name=current_vip,json=currentVip,proto3" json:"current_vip,omitempty"` - Packages []*VipLevel `protobuf:"bytes,2,rep,name=packages,proto3" json:"packages,omitempty"` + CurrentVip *UserVip `protobuf:"bytes,1,opt,name=current_vip,json=currentVip,proto3" json:"current_vip,omitempty"` + Packages []*VipLevel `protobuf:"bytes,2,rep,name=packages,proto3" json:"packages,omitempty"` + State *VipState `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` + ProgramConfig *VipProgramConfig `protobuf:"bytes,4,opt,name=program_config,json=programConfig,proto3" json:"program_config,omitempty"` } func (x *ListVipPackagesResponse) Reset() { *x = ListVipPackagesResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[187] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[195] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17124,7 +18023,7 @@ func (x *ListVipPackagesResponse) String() string { func (*ListVipPackagesResponse) ProtoMessage() {} func (x *ListVipPackagesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[187] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[195] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17137,7 +18036,7 @@ func (x *ListVipPackagesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListVipPackagesResponse.ProtoReflect.Descriptor instead. func (*ListVipPackagesResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{187} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{195} } func (x *ListVipPackagesResponse) GetCurrentVip() *UserVip { @@ -17154,6 +18053,20 @@ func (x *ListVipPackagesResponse) GetPackages() []*VipLevel { return nil } +func (x *ListVipPackagesResponse) GetState() *VipState { + if x != nil { + return x.State + } + return nil +} + +func (x *ListVipPackagesResponse) GetProgramConfig() *VipProgramConfig { + if x != nil { + return x.ProgramConfig + } + return nil +} + type GetMyVipRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -17166,7 +18079,7 @@ type GetMyVipRequest struct { func (x *GetMyVipRequest) Reset() { *x = GetMyVipRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[188] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[196] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17178,7 +18091,7 @@ func (x *GetMyVipRequest) String() string { func (*GetMyVipRequest) ProtoMessage() {} func (x *GetMyVipRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[188] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[196] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17191,7 +18104,7 @@ func (x *GetMyVipRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMyVipRequest.ProtoReflect.Descriptor instead. func (*GetMyVipRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{188} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{196} } func (x *GetMyVipRequest) GetRequestId() string { @@ -17220,12 +18133,13 @@ type GetMyVipResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Vip *UserVip `protobuf:"bytes,1,opt,name=vip,proto3" json:"vip,omitempty"` + Vip *UserVip `protobuf:"bytes,1,opt,name=vip,proto3" json:"vip,omitempty"` + State *VipState `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` } func (x *GetMyVipResponse) Reset() { *x = GetMyVipResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[189] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[197] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17237,7 +18151,7 @@ func (x *GetMyVipResponse) String() string { func (*GetMyVipResponse) ProtoMessage() {} func (x *GetMyVipResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[189] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[197] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17250,7 +18164,7 @@ func (x *GetMyVipResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMyVipResponse.ProtoReflect.Descriptor instead. func (*GetMyVipResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{189} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{197} } func (x *GetMyVipResponse) GetVip() *UserVip { @@ -17260,6 +18174,13 @@ func (x *GetMyVipResponse) GetVip() *UserVip { return nil } +func (x *GetMyVipResponse) GetState() *VipState { + if x != nil { + return x.State + } + return nil +} + type PurchaseVipRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -17273,7 +18194,7 @@ type PurchaseVipRequest struct { func (x *PurchaseVipRequest) Reset() { *x = PurchaseVipRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[190] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[198] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17285,7 +18206,7 @@ func (x *PurchaseVipRequest) String() string { func (*PurchaseVipRequest) ProtoMessage() {} func (x *PurchaseVipRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[190] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[198] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17298,7 +18219,7 @@ func (x *PurchaseVipRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PurchaseVipRequest.ProtoReflect.Descriptor instead. func (*PurchaseVipRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{190} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{198} } func (x *PurchaseVipRequest) GetCommandId() string { @@ -17340,11 +18261,12 @@ type PurchaseVipResponse struct { CoinSpent int64 `protobuf:"varint,4,opt,name=coin_spent,json=coinSpent,proto3" json:"coin_spent,omitempty"` CoinBalanceAfter int64 `protobuf:"varint,5,opt,name=coin_balance_after,json=coinBalanceAfter,proto3" json:"coin_balance_after,omitempty"` RewardItems []*VipRewardItem `protobuf:"bytes,6,rep,name=reward_items,json=rewardItems,proto3" json:"reward_items,omitempty"` + State *VipState `protobuf:"bytes,7,opt,name=state,proto3" json:"state,omitempty"` } func (x *PurchaseVipResponse) Reset() { *x = PurchaseVipResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[191] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[199] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17356,7 +18278,7 @@ func (x *PurchaseVipResponse) String() string { func (*PurchaseVipResponse) ProtoMessage() {} func (x *PurchaseVipResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[191] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[199] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17369,7 +18291,7 @@ func (x *PurchaseVipResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PurchaseVipResponse.ProtoReflect.Descriptor instead. func (*PurchaseVipResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{191} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{199} } func (x *PurchaseVipResponse) GetOrderId() string { @@ -17414,6 +18336,13 @@ func (x *PurchaseVipResponse) GetRewardItems() []*VipRewardItem { return nil } +func (x *PurchaseVipResponse) GetState() *VipState { + if x != nil { + return x.State + } + return nil +} + type DebitCPBreakupFeeRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -17429,7 +18358,7 @@ type DebitCPBreakupFeeRequest struct { func (x *DebitCPBreakupFeeRequest) Reset() { *x = DebitCPBreakupFeeRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[192] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[200] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17441,7 +18370,7 @@ func (x *DebitCPBreakupFeeRequest) String() string { func (*DebitCPBreakupFeeRequest) ProtoMessage() {} func (x *DebitCPBreakupFeeRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[192] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[200] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17454,7 +18383,7 @@ func (x *DebitCPBreakupFeeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DebitCPBreakupFeeRequest.ProtoReflect.Descriptor instead. func (*DebitCPBreakupFeeRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{192} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{200} } func (x *DebitCPBreakupFeeRequest) GetCommandId() string { @@ -17512,7 +18441,7 @@ type DebitCPBreakupFeeResponse struct { func (x *DebitCPBreakupFeeResponse) Reset() { *x = DebitCPBreakupFeeResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[193] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[201] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17524,7 +18453,7 @@ func (x *DebitCPBreakupFeeResponse) String() string { func (*DebitCPBreakupFeeResponse) ProtoMessage() {} func (x *DebitCPBreakupFeeResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[193] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[201] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17537,7 +18466,7 @@ func (x *DebitCPBreakupFeeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DebitCPBreakupFeeResponse.ProtoReflect.Descriptor instead. func (*DebitCPBreakupFeeResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{193} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{201} } func (x *DebitCPBreakupFeeResponse) GetTransactionId() string { @@ -17583,7 +18512,7 @@ type DebitWheelDrawRequest struct { func (x *DebitWheelDrawRequest) Reset() { *x = DebitWheelDrawRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[194] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[202] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17595,7 +18524,7 @@ func (x *DebitWheelDrawRequest) String() string { func (*DebitWheelDrawRequest) ProtoMessage() {} func (x *DebitWheelDrawRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[194] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[202] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17608,7 +18537,7 @@ func (x *DebitWheelDrawRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DebitWheelDrawRequest.ProtoReflect.Descriptor instead. func (*DebitWheelDrawRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{194} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{202} } func (x *DebitWheelDrawRequest) GetCommandId() string { @@ -17666,7 +18595,7 @@ type DebitWheelDrawResponse struct { func (x *DebitWheelDrawResponse) Reset() { *x = DebitWheelDrawResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[195] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[203] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17678,7 +18607,7 @@ func (x *DebitWheelDrawResponse) String() string { func (*DebitWheelDrawResponse) ProtoMessage() {} func (x *DebitWheelDrawResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[195] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[203] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17691,7 +18620,7 @@ func (x *DebitWheelDrawResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DebitWheelDrawResponse.ProtoReflect.Descriptor instead. func (*DebitWheelDrawResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{195} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{203} } func (x *DebitWheelDrawResponse) GetTransactionId() string { @@ -17738,7 +18667,7 @@ type GrantVipRequest struct { func (x *GrantVipRequest) Reset() { *x = GrantVipRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[196] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[204] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17750,7 +18679,7 @@ func (x *GrantVipRequest) String() string { func (*GrantVipRequest) ProtoMessage() {} func (x *GrantVipRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[196] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[204] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17763,7 +18692,7 @@ func (x *GrantVipRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GrantVipRequest.ProtoReflect.Descriptor instead. func (*GrantVipRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{196} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{204} } func (x *GrantVipRequest) GetCommandId() string { @@ -17824,11 +18753,12 @@ type GrantVipResponse struct { Vip *UserVip `protobuf:"bytes,2,opt,name=vip,proto3" json:"vip,omitempty"` RewardItems []*VipRewardItem `protobuf:"bytes,3,rep,name=reward_items,json=rewardItems,proto3" json:"reward_items,omitempty"` ServerTimeMs int64 `protobuf:"varint,4,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + State *VipState `protobuf:"bytes,5,opt,name=state,proto3" json:"state,omitempty"` } func (x *GrantVipResponse) Reset() { *x = GrantVipResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[197] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[205] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17840,7 +18770,7 @@ func (x *GrantVipResponse) String() string { func (*GrantVipResponse) ProtoMessage() {} func (x *GrantVipResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[197] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[205] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17853,7 +18783,7 @@ func (x *GrantVipResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GrantVipResponse.ProtoReflect.Descriptor instead. func (*GrantVipResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{197} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{205} } func (x *GrantVipResponse) GetTransactionId() string { @@ -17884,6 +18814,1804 @@ func (x *GrantVipResponse) GetServerTimeMs() int64 { return 0 } +func (x *GrantVipResponse) GetState() *VipState { + if x != nil { + return x.State + } + return nil +} + +type GetVipProgramConfigRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` +} + +func (x *GetVipProgramConfigRequest) Reset() { + *x = GetVipProgramConfigRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[206] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetVipProgramConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetVipProgramConfigRequest) ProtoMessage() {} + +func (x *GetVipProgramConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[206] + 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 GetVipProgramConfigRequest.ProtoReflect.Descriptor instead. +func (*GetVipProgramConfigRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{206} +} + +func (x *GetVipProgramConfigRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *GetVipProgramConfigRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +type GetVipProgramConfigResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Config *VipProgramConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + Benefits []*VipBenefit `protobuf:"bytes,2,rep,name=benefits,proto3" json:"benefits,omitempty"` + ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *GetVipProgramConfigResponse) Reset() { + *x = GetVipProgramConfigResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[207] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetVipProgramConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetVipProgramConfigResponse) ProtoMessage() {} + +func (x *GetVipProgramConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[207] + 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 GetVipProgramConfigResponse.ProtoReflect.Descriptor instead. +func (*GetVipProgramConfigResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{207} +} + +func (x *GetVipProgramConfigResponse) GetConfig() *VipProgramConfig { + if x != nil { + return x.Config + } + return nil +} + +func (x *GetVipProgramConfigResponse) GetBenefits() []*VipBenefit { + if x != nil { + return x.Benefits + } + return nil +} + +func (x *GetVipProgramConfigResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +// UpdateAdminVipProgramConfigRequest 以完整配置替换单 App 当前规则;config_version 由服务端递增, +// 客户端提交的审计时间和操作者字段不会被信任。 +type UpdateAdminVipProgramConfigRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + Config *VipProgramConfig `protobuf:"bytes,3,opt,name=config,proto3" json:"config,omitempty"` + OperatorUserId int64 `protobuf:"varint,4,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` +} + +func (x *UpdateAdminVipProgramConfigRequest) Reset() { + *x = UpdateAdminVipProgramConfigRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[208] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateAdminVipProgramConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateAdminVipProgramConfigRequest) ProtoMessage() {} + +func (x *UpdateAdminVipProgramConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[208] + 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 UpdateAdminVipProgramConfigRequest.ProtoReflect.Descriptor instead. +func (*UpdateAdminVipProgramConfigRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{208} +} + +func (x *UpdateAdminVipProgramConfigRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *UpdateAdminVipProgramConfigRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *UpdateAdminVipProgramConfigRequest) GetConfig() *VipProgramConfig { + if x != nil { + return x.Config + } + return nil +} + +func (x *UpdateAdminVipProgramConfigRequest) GetOperatorUserId() int64 { + if x != nil { + return x.OperatorUserId + } + return 0 +} + +type UpdateAdminVipProgramConfigResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Config *VipProgramConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *UpdateAdminVipProgramConfigResponse) Reset() { + *x = UpdateAdminVipProgramConfigResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[209] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateAdminVipProgramConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateAdminVipProgramConfigResponse) ProtoMessage() {} + +func (x *UpdateAdminVipProgramConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[209] + 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 UpdateAdminVipProgramConfigResponse.ProtoReflect.Descriptor instead. +func (*UpdateAdminVipProgramConfigResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{209} +} + +func (x *UpdateAdminVipProgramConfigResponse) GetConfig() *VipProgramConfig { + if x != nil { + return x.Config + } + return nil +} + +func (x *UpdateAdminVipProgramConfigResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +// UpdateMyVipSettingsRequest 使用 optional 保留 PATCH 语义;显式 false 不能被当成未提交。 +type UpdateMyVipSettingsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + RoomEntryNoticeEnabled *bool `protobuf:"varint,4,opt,name=room_entry_notice_enabled,json=roomEntryNoticeEnabled,proto3,oneof" json:"room_entry_notice_enabled,omitempty"` + OnlineGlobalNoticeEnabled *bool `protobuf:"varint,5,opt,name=online_global_notice_enabled,json=onlineGlobalNoticeEnabled,proto3,oneof" json:"online_global_notice_enabled,omitempty"` +} + +func (x *UpdateMyVipSettingsRequest) Reset() { + *x = UpdateMyVipSettingsRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[210] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateMyVipSettingsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateMyVipSettingsRequest) ProtoMessage() {} + +func (x *UpdateMyVipSettingsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[210] + 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 UpdateMyVipSettingsRequest.ProtoReflect.Descriptor instead. +func (*UpdateMyVipSettingsRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{210} +} + +func (x *UpdateMyVipSettingsRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *UpdateMyVipSettingsRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *UpdateMyVipSettingsRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *UpdateMyVipSettingsRequest) GetRoomEntryNoticeEnabled() bool { + if x != nil && x.RoomEntryNoticeEnabled != nil { + return *x.RoomEntryNoticeEnabled + } + return false +} + +func (x *UpdateMyVipSettingsRequest) GetOnlineGlobalNoticeEnabled() bool { + if x != nil && x.OnlineGlobalNoticeEnabled != nil { + return *x.OnlineGlobalNoticeEnabled + } + return false +} + +type UpdateMyVipSettingsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Settings *VipUserSettings `protobuf:"bytes,1,opt,name=settings,proto3" json:"settings,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *UpdateMyVipSettingsResponse) Reset() { + *x = UpdateMyVipSettingsResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[211] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateMyVipSettingsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateMyVipSettingsResponse) ProtoMessage() {} + +func (x *UpdateMyVipSettingsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[211] + 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 UpdateMyVipSettingsResponse.ProtoReflect.Descriptor instead. +func (*UpdateMyVipSettingsResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{211} +} + +func (x *UpdateMyVipSettingsResponse) GetSettings() *VipUserSettings { + if x != nil { + return x.Settings + } + return nil +} + +func (x *UpdateMyVipSettingsResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +// VipDailyCoinRebateRun 固化某个 UTC 自然日的返现金额矩阵。批任务开始后即使后台 +// 修改等级权益,后续分页也只能复用本 run 的 config_version/level_amounts_json。 +type VipDailyCoinRebateRun struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RunId string `protobuf:"bytes,1,opt,name=run_id,json=runId,proto3" json:"run_id,omitempty"` + TaskDay string `protobuf:"bytes,2,opt,name=task_day,json=taskDay,proto3" json:"task_day,omitempty"` + DayStartMs int64 `protobuf:"varint,3,opt,name=day_start_ms,json=dayStartMs,proto3" json:"day_start_ms,omitempty"` + DayEndMs int64 `protobuf:"varint,4,opt,name=day_end_ms,json=dayEndMs,proto3" json:"day_end_ms,omitempty"` + ConfigVersion int64 `protobuf:"varint,5,opt,name=config_version,json=configVersion,proto3" json:"config_version,omitempty"` + LevelAmountsJson string `protobuf:"bytes,6,opt,name=level_amounts_json,json=levelAmountsJson,proto3" json:"level_amounts_json,omitempty"` + Status string `protobuf:"bytes,7,opt,name=status,proto3" json:"status,omitempty"` + LastUserId int64 `protobuf:"varint,8,opt,name=last_user_id,json=lastUserId,proto3" json:"last_user_id,omitempty"` + ScannedCount int64 `protobuf:"varint,9,opt,name=scanned_count,json=scannedCount,proto3" json:"scanned_count,omitempty"` + CreatedCount int64 `protobuf:"varint,10,opt,name=created_count,json=createdCount,proto3" json:"created_count,omitempty"` + CreatedAtMs int64 `protobuf:"varint,11,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,12,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + CompletedAtMs int64 `protobuf:"varint,13,opt,name=completed_at_ms,json=completedAtMs,proto3" json:"completed_at_ms,omitempty"` +} + +func (x *VipDailyCoinRebateRun) Reset() { + *x = VipDailyCoinRebateRun{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[212] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VipDailyCoinRebateRun) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VipDailyCoinRebateRun) ProtoMessage() {} + +func (x *VipDailyCoinRebateRun) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[212] + 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 VipDailyCoinRebateRun.ProtoReflect.Descriptor instead. +func (*VipDailyCoinRebateRun) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{212} +} + +func (x *VipDailyCoinRebateRun) GetRunId() string { + if x != nil { + return x.RunId + } + return "" +} + +func (x *VipDailyCoinRebateRun) GetTaskDay() string { + if x != nil { + return x.TaskDay + } + return "" +} + +func (x *VipDailyCoinRebateRun) GetDayStartMs() int64 { + if x != nil { + return x.DayStartMs + } + return 0 +} + +func (x *VipDailyCoinRebateRun) GetDayEndMs() int64 { + if x != nil { + return x.DayEndMs + } + return 0 +} + +func (x *VipDailyCoinRebateRun) GetConfigVersion() int64 { + if x != nil { + return x.ConfigVersion + } + return 0 +} + +func (x *VipDailyCoinRebateRun) GetLevelAmountsJson() string { + if x != nil { + return x.LevelAmountsJson + } + return "" +} + +func (x *VipDailyCoinRebateRun) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *VipDailyCoinRebateRun) GetLastUserId() int64 { + if x != nil { + return x.LastUserId + } + return 0 +} + +func (x *VipDailyCoinRebateRun) GetScannedCount() int64 { + if x != nil { + return x.ScannedCount + } + return 0 +} + +func (x *VipDailyCoinRebateRun) GetCreatedCount() int64 { + if x != nil { + return x.CreatedCount + } + return 0 +} + +func (x *VipDailyCoinRebateRun) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *VipDailyCoinRebateRun) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +func (x *VipDailyCoinRebateRun) GetCompletedAtMs() int64 { + if x != nil { + return x.CompletedAtMs + } + return 0 +} + +// VipDailyCoinRebate 是付费 VIP 在 UTC 日切时生成的可领取金币事实。体验卡不会生成; +// status 返回 claimable/claimed/expired,expired 可由查询时钟投影而不改写历史行。 +type VipDailyCoinRebate struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RebateId string `protobuf:"bytes,1,opt,name=rebate_id,json=rebateId,proto3" json:"rebate_id,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + TaskDay string `protobuf:"bytes,3,opt,name=task_day,json=taskDay,proto3" json:"task_day,omitempty"` + VipLevel int32 `protobuf:"varint,4,opt,name=vip_level,json=vipLevel,proto3" json:"vip_level,omitempty"` + VipName string `protobuf:"bytes,5,opt,name=vip_name,json=vipName,proto3" json:"vip_name,omitempty"` + CoinAmount int64 `protobuf:"varint,6,opt,name=coin_amount,json=coinAmount,proto3" json:"coin_amount,omitempty"` + Status string `protobuf:"bytes,7,opt,name=status,proto3" json:"status,omitempty"` + AvailableAtMs int64 `protobuf:"varint,8,opt,name=available_at_ms,json=availableAtMs,proto3" json:"available_at_ms,omitempty"` + ExpiresAtMs int64 `protobuf:"varint,9,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` + ConfigVersion int64 `protobuf:"varint,10,opt,name=config_version,json=configVersion,proto3" json:"config_version,omitempty"` + ClaimedAtMs int64 `protobuf:"varint,11,opt,name=claimed_at_ms,json=claimedAtMs,proto3" json:"claimed_at_ms,omitempty"` + WalletTransactionId string `protobuf:"bytes,12,opt,name=wallet_transaction_id,json=walletTransactionId,proto3" json:"wallet_transaction_id,omitempty"` + CreatedAtMs int64 `protobuf:"varint,13,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,14,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` +} + +func (x *VipDailyCoinRebate) Reset() { + *x = VipDailyCoinRebate{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[213] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VipDailyCoinRebate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VipDailyCoinRebate) ProtoMessage() {} + +func (x *VipDailyCoinRebate) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[213] + 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 VipDailyCoinRebate.ProtoReflect.Descriptor instead. +func (*VipDailyCoinRebate) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{213} +} + +func (x *VipDailyCoinRebate) GetRebateId() string { + if x != nil { + return x.RebateId + } + return "" +} + +func (x *VipDailyCoinRebate) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *VipDailyCoinRebate) GetTaskDay() string { + if x != nil { + return x.TaskDay + } + return "" +} + +func (x *VipDailyCoinRebate) GetVipLevel() int32 { + if x != nil { + return x.VipLevel + } + return 0 +} + +func (x *VipDailyCoinRebate) GetVipName() string { + if x != nil { + return x.VipName + } + return "" +} + +func (x *VipDailyCoinRebate) GetCoinAmount() int64 { + if x != nil { + return x.CoinAmount + } + return 0 +} + +func (x *VipDailyCoinRebate) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *VipDailyCoinRebate) GetAvailableAtMs() int64 { + if x != nil { + return x.AvailableAtMs + } + return 0 +} + +func (x *VipDailyCoinRebate) GetExpiresAtMs() int64 { + if x != nil { + return x.ExpiresAtMs + } + return 0 +} + +func (x *VipDailyCoinRebate) GetConfigVersion() int64 { + if x != nil { + return x.ConfigVersion + } + return 0 +} + +func (x *VipDailyCoinRebate) GetClaimedAtMs() int64 { + if x != nil { + return x.ClaimedAtMs + } + return 0 +} + +func (x *VipDailyCoinRebate) GetWalletTransactionId() string { + if x != nil { + return x.WalletTransactionId + } + return "" +} + +func (x *VipDailyCoinRebate) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *VipDailyCoinRebate) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +// ProcessVipDailyCoinRebateBatchRequest 由 WalletCronService 接收。task_day 为空时按 +// wallet-service 当前 UTC 日期处理;同 App+task_day 始终创建或复用同一个 run。 +type ProcessVipDailyCoinRebateBatchRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + TaskDay string `protobuf:"bytes,3,opt,name=task_day,json=taskDay,proto3" json:"task_day,omitempty"` + BatchSize int32 `protobuf:"varint,4,opt,name=batch_size,json=batchSize,proto3" json:"batch_size,omitempty"` +} + +func (x *ProcessVipDailyCoinRebateBatchRequest) Reset() { + *x = ProcessVipDailyCoinRebateBatchRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[214] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProcessVipDailyCoinRebateBatchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProcessVipDailyCoinRebateBatchRequest) ProtoMessage() {} + +func (x *ProcessVipDailyCoinRebateBatchRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[214] + 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 ProcessVipDailyCoinRebateBatchRequest.ProtoReflect.Descriptor instead. +func (*ProcessVipDailyCoinRebateBatchRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{214} +} + +func (x *ProcessVipDailyCoinRebateBatchRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *ProcessVipDailyCoinRebateBatchRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *ProcessVipDailyCoinRebateBatchRequest) GetTaskDay() string { + if x != nil { + return x.TaskDay + } + return "" +} + +func (x *ProcessVipDailyCoinRebateBatchRequest) GetBatchSize() int32 { + if x != nil { + return x.BatchSize + } + return 0 +} + +type ProcessVipDailyCoinRebateBatchResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Run *VipDailyCoinRebateRun `protobuf:"bytes,1,opt,name=run,proto3" json:"run,omitempty"` + ScannedCount int32 `protobuf:"varint,2,opt,name=scanned_count,json=scannedCount,proto3" json:"scanned_count,omitempty"` + CreatedCount int32 `protobuf:"varint,3,opt,name=created_count,json=createdCount,proto3" json:"created_count,omitempty"` + HasMore bool `protobuf:"varint,4,opt,name=has_more,json=hasMore,proto3" json:"has_more,omitempty"` + ServerTimeMs int64 `protobuf:"varint,5,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *ProcessVipDailyCoinRebateBatchResponse) Reset() { + *x = ProcessVipDailyCoinRebateBatchResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[215] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProcessVipDailyCoinRebateBatchResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProcessVipDailyCoinRebateBatchResponse) ProtoMessage() {} + +func (x *ProcessVipDailyCoinRebateBatchResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[215] + 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 ProcessVipDailyCoinRebateBatchResponse.ProtoReflect.Descriptor instead. +func (*ProcessVipDailyCoinRebateBatchResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{215} +} + +func (x *ProcessVipDailyCoinRebateBatchResponse) GetRun() *VipDailyCoinRebateRun { + if x != nil { + return x.Run + } + return nil +} + +func (x *ProcessVipDailyCoinRebateBatchResponse) GetScannedCount() int32 { + if x != nil { + return x.ScannedCount + } + return 0 +} + +func (x *ProcessVipDailyCoinRebateBatchResponse) GetCreatedCount() int32 { + if x != nil { + return x.CreatedCount + } + return 0 +} + +func (x *ProcessVipDailyCoinRebateBatchResponse) GetHasMore() bool { + if x != nil { + return x.HasMore + } + return false +} + +func (x *ProcessVipDailyCoinRebateBatchResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type GetMyCurrentVipDailyCoinRebateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` +} + +func (x *GetMyCurrentVipDailyCoinRebateRequest) Reset() { + *x = GetMyCurrentVipDailyCoinRebateRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[216] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetMyCurrentVipDailyCoinRebateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetMyCurrentVipDailyCoinRebateRequest) ProtoMessage() {} + +func (x *GetMyCurrentVipDailyCoinRebateRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[216] + 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 GetMyCurrentVipDailyCoinRebateRequest.ProtoReflect.Descriptor instead. +func (*GetMyCurrentVipDailyCoinRebateRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{216} +} + +func (x *GetMyCurrentVipDailyCoinRebateRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *GetMyCurrentVipDailyCoinRebateRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *GetMyCurrentVipDailyCoinRebateRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +type GetMyCurrentVipDailyCoinRebateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Found bool `protobuf:"varint,1,opt,name=found,proto3" json:"found,omitempty"` + Rebate *VipDailyCoinRebate `protobuf:"bytes,2,opt,name=rebate,proto3" json:"rebate,omitempty"` + ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *GetMyCurrentVipDailyCoinRebateResponse) Reset() { + *x = GetMyCurrentVipDailyCoinRebateResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[217] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetMyCurrentVipDailyCoinRebateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetMyCurrentVipDailyCoinRebateResponse) ProtoMessage() {} + +func (x *GetMyCurrentVipDailyCoinRebateResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[217] + 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 GetMyCurrentVipDailyCoinRebateResponse.ProtoReflect.Descriptor instead. +func (*GetMyCurrentVipDailyCoinRebateResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{217} +} + +func (x *GetMyCurrentVipDailyCoinRebateResponse) GetFound() bool { + if x != nil { + return x.Found + } + return false +} + +func (x *GetMyCurrentVipDailyCoinRebateResponse) GetRebate() *VipDailyCoinRebate { + if x != nil { + return x.Rebate + } + return nil +} + +func (x *GetMyCurrentVipDailyCoinRebateResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type ListMyVipDailyCoinRebateStatusesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Page int32 `protobuf:"varint,4,opt,name=page,proto3" json:"page,omitempty"` + PageSize int32 `protobuf:"varint,5,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // rebate_ids 非空时精确恢复指定通知的领取状态,最多 100 个;仍强制按 user_id 隔离。 + RebateIds []string `protobuf:"bytes,6,rep,name=rebate_ids,json=rebateIds,proto3" json:"rebate_ids,omitempty"` +} + +func (x *ListMyVipDailyCoinRebateStatusesRequest) Reset() { + *x = ListMyVipDailyCoinRebateStatusesRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[218] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListMyVipDailyCoinRebateStatusesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListMyVipDailyCoinRebateStatusesRequest) ProtoMessage() {} + +func (x *ListMyVipDailyCoinRebateStatusesRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[218] + 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 ListMyVipDailyCoinRebateStatusesRequest.ProtoReflect.Descriptor instead. +func (*ListMyVipDailyCoinRebateStatusesRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{218} +} + +func (x *ListMyVipDailyCoinRebateStatusesRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *ListMyVipDailyCoinRebateStatusesRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *ListMyVipDailyCoinRebateStatusesRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *ListMyVipDailyCoinRebateStatusesRequest) GetPage() int32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListMyVipDailyCoinRebateStatusesRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListMyVipDailyCoinRebateStatusesRequest) GetRebateIds() []string { + if x != nil { + return x.RebateIds + } + return nil +} + +type ListMyVipDailyCoinRebateStatusesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Rebates []*VipDailyCoinRebate `protobuf:"bytes,1,rep,name=rebates,proto3" json:"rebates,omitempty"` + Total int64 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *ListMyVipDailyCoinRebateStatusesResponse) Reset() { + *x = ListMyVipDailyCoinRebateStatusesResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[219] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListMyVipDailyCoinRebateStatusesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListMyVipDailyCoinRebateStatusesResponse) ProtoMessage() {} + +func (x *ListMyVipDailyCoinRebateStatusesResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[219] + 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 ListMyVipDailyCoinRebateStatusesResponse.ProtoReflect.Descriptor instead. +func (*ListMyVipDailyCoinRebateStatusesResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{219} +} + +func (x *ListMyVipDailyCoinRebateStatusesResponse) GetRebates() []*VipDailyCoinRebate { + if x != nil { + return x.Rebates + } + return nil +} + +func (x *ListMyVipDailyCoinRebateStatusesResponse) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *ListMyVipDailyCoinRebateStatusesResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type ClaimVipDailyCoinRebateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CommandId string `protobuf:"bytes,1,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + RebateId string `protobuf:"bytes,4,opt,name=rebate_id,json=rebateId,proto3" json:"rebate_id,omitempty"` +} + +func (x *ClaimVipDailyCoinRebateRequest) Reset() { + *x = ClaimVipDailyCoinRebateRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[220] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClaimVipDailyCoinRebateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClaimVipDailyCoinRebateRequest) ProtoMessage() {} + +func (x *ClaimVipDailyCoinRebateRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[220] + 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 ClaimVipDailyCoinRebateRequest.ProtoReflect.Descriptor instead. +func (*ClaimVipDailyCoinRebateRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{220} +} + +func (x *ClaimVipDailyCoinRebateRequest) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *ClaimVipDailyCoinRebateRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *ClaimVipDailyCoinRebateRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *ClaimVipDailyCoinRebateRequest) GetRebateId() string { + if x != nil { + return x.RebateId + } + return "" +} + +type ClaimVipDailyCoinRebateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Rebate *VipDailyCoinRebate `protobuf:"bytes,1,opt,name=rebate,proto3" json:"rebate,omitempty"` + TransactionId string `protobuf:"bytes,2,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` + CoinBalance *AssetBalance `protobuf:"bytes,3,opt,name=coin_balance,json=coinBalance,proto3" json:"coin_balance,omitempty"` + ServerTimeMs int64 `protobuf:"varint,4,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *ClaimVipDailyCoinRebateResponse) Reset() { + *x = ClaimVipDailyCoinRebateResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[221] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClaimVipDailyCoinRebateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClaimVipDailyCoinRebateResponse) ProtoMessage() {} + +func (x *ClaimVipDailyCoinRebateResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[221] + 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 ClaimVipDailyCoinRebateResponse.ProtoReflect.Descriptor instead. +func (*ClaimVipDailyCoinRebateResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{221} +} + +func (x *ClaimVipDailyCoinRebateResponse) GetRebate() *VipDailyCoinRebate { + if x != nil { + return x.Rebate + } + return nil +} + +func (x *ClaimVipDailyCoinRebateResponse) GetTransactionId() string { + if x != nil { + return x.TransactionId + } + return "" +} + +func (x *ClaimVipDailyCoinRebateResponse) GetCoinBalance() *AssetBalance { + if x != nil { + return x.CoinBalance + } + return nil +} + +func (x *ClaimVipDailyCoinRebateResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +// GrantVipTrialCardRequest 允许后台直接按 level+duration_ms 发卡;resource_id 为空时按 +// vip_trial_card_{level} 解析同 App 预置资源,非空时也必须匹配该等级。 +type GrantVipTrialCardRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CommandId string `protobuf:"bytes,1,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + TargetUserId int64 `protobuf:"varint,3,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + Level int32 `protobuf:"varint,4,opt,name=level,proto3" json:"level,omitempty"` + DurationMs int64 `protobuf:"varint,5,opt,name=duration_ms,json=durationMs,proto3" json:"duration_ms,omitempty"` + ResourceId *int64 `protobuf:"varint,6,opt,name=resource_id,json=resourceId,proto3,oneof" json:"resource_id,omitempty"` + GrantSource string `protobuf:"bytes,7,opt,name=grant_source,json=grantSource,proto3" json:"grant_source,omitempty"` + OperatorUserId int64 `protobuf:"varint,8,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + Reason string `protobuf:"bytes,9,opt,name=reason,proto3" json:"reason,omitempty"` +} + +func (x *GrantVipTrialCardRequest) Reset() { + *x = GrantVipTrialCardRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[222] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GrantVipTrialCardRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GrantVipTrialCardRequest) ProtoMessage() {} + +func (x *GrantVipTrialCardRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[222] + 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 GrantVipTrialCardRequest.ProtoReflect.Descriptor instead. +func (*GrantVipTrialCardRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{222} +} + +func (x *GrantVipTrialCardRequest) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *GrantVipTrialCardRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *GrantVipTrialCardRequest) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *GrantVipTrialCardRequest) GetLevel() int32 { + if x != nil { + return x.Level + } + return 0 +} + +func (x *GrantVipTrialCardRequest) GetDurationMs() int64 { + if x != nil { + return x.DurationMs + } + return 0 +} + +func (x *GrantVipTrialCardRequest) GetResourceId() int64 { + if x != nil && x.ResourceId != nil { + return *x.ResourceId + } + return 0 +} + +func (x *GrantVipTrialCardRequest) GetGrantSource() string { + if x != nil { + return x.GrantSource + } + return "" +} + +func (x *GrantVipTrialCardRequest) GetOperatorUserId() int64 { + if x != nil { + return x.OperatorUserId + } + return 0 +} + +func (x *GrantVipTrialCardRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +type GrantVipTrialCardResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TrialCard *VipTrialCard `protobuf:"bytes,1,opt,name=trial_card,json=trialCard,proto3" json:"trial_card,omitempty"` + State *VipState `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` + ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *GrantVipTrialCardResponse) Reset() { + *x = GrantVipTrialCardResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[223] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GrantVipTrialCardResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GrantVipTrialCardResponse) ProtoMessage() {} + +func (x *GrantVipTrialCardResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[223] + 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 GrantVipTrialCardResponse.ProtoReflect.Descriptor instead. +func (*GrantVipTrialCardResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{223} +} + +func (x *GrantVipTrialCardResponse) GetTrialCard() *VipTrialCard { + if x != nil { + return x.TrialCard + } + return nil +} + +func (x *GrantVipTrialCardResponse) GetState() *VipState { + if x != nil { + return x.State + } + return nil +} + +func (x *GrantVipTrialCardResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type EquipVipTrialCardRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + // entitlement_id 是背包实例标识,保证同资源的多张卡也能被精确佩戴。 + EntitlementId string `protobuf:"bytes,4,opt,name=entitlement_id,json=entitlementId,proto3" json:"entitlement_id,omitempty"` +} + +func (x *EquipVipTrialCardRequest) Reset() { + *x = EquipVipTrialCardRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[224] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EquipVipTrialCardRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EquipVipTrialCardRequest) ProtoMessage() {} + +func (x *EquipVipTrialCardRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[224] + 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 EquipVipTrialCardRequest.ProtoReflect.Descriptor instead. +func (*EquipVipTrialCardRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{224} +} + +func (x *EquipVipTrialCardRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *EquipVipTrialCardRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *EquipVipTrialCardRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *EquipVipTrialCardRequest) GetEntitlementId() string { + if x != nil { + return x.EntitlementId + } + return "" +} + +type EquipVipTrialCardResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TrialCard *VipTrialCard `protobuf:"bytes,1,opt,name=trial_card,json=trialCard,proto3" json:"trial_card,omitempty"` + State *VipState `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` + ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *EquipVipTrialCardResponse) Reset() { + *x = EquipVipTrialCardResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[225] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EquipVipTrialCardResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EquipVipTrialCardResponse) ProtoMessage() {} + +func (x *EquipVipTrialCardResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[225] + 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 EquipVipTrialCardResponse.ProtoReflect.Descriptor instead. +func (*EquipVipTrialCardResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{225} +} + +func (x *EquipVipTrialCardResponse) GetTrialCard() *VipTrialCard { + if x != nil { + return x.TrialCard + } + return nil +} + +func (x *EquipVipTrialCardResponse) GetState() *VipState { + if x != nil { + return x.State + } + return nil +} + +func (x *EquipVipTrialCardResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type UnequipVipTrialCardRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` +} + +func (x *UnequipVipTrialCardRequest) Reset() { + *x = UnequipVipTrialCardRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[226] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UnequipVipTrialCardRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnequipVipTrialCardRequest) ProtoMessage() {} + +func (x *UnequipVipTrialCardRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[226] + 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 UnequipVipTrialCardRequest.ProtoReflect.Descriptor instead. +func (*UnequipVipTrialCardRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{226} +} + +func (x *UnequipVipTrialCardRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *UnequipVipTrialCardRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *UnequipVipTrialCardRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +type UnequipVipTrialCardResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Unequipped bool `protobuf:"varint,1,opt,name=unequipped,proto3" json:"unequipped,omitempty"` + State *VipState `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` + ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *UnequipVipTrialCardResponse) Reset() { + *x = UnequipVipTrialCardResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[227] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UnequipVipTrialCardResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnequipVipTrialCardResponse) ProtoMessage() {} + +func (x *UnequipVipTrialCardResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[227] + 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 UnequipVipTrialCardResponse.ProtoReflect.Descriptor instead. +func (*UnequipVipTrialCardResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{227} +} + +func (x *UnequipVipTrialCardResponse) GetUnequipped() bool { + if x != nil { + return x.Unequipped + } + return false +} + +func (x *UnequipVipTrialCardResponse) GetState() *VipState { + if x != nil { + return x.State + } + return nil +} + +func (x *UnequipVipTrialCardResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +// CheckVipBenefit 供 room/user 等权益执行方校验单个特权。调用方不能只比较 VIP level, +// 因为体验卡排除项和 App 配置会改变最终可执行权益集合。 +type CheckVipBenefitRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + BenefitCode string `protobuf:"bytes,4,opt,name=benefit_code,json=benefitCode,proto3" json:"benefit_code,omitempty"` +} + +func (x *CheckVipBenefitRequest) Reset() { + *x = CheckVipBenefitRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[228] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CheckVipBenefitRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckVipBenefitRequest) ProtoMessage() {} + +func (x *CheckVipBenefitRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[228] + 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 CheckVipBenefitRequest.ProtoReflect.Descriptor instead. +func (*CheckVipBenefitRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{228} +} + +func (x *CheckVipBenefitRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *CheckVipBenefitRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *CheckVipBenefitRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *CheckVipBenefitRequest) GetBenefitCode() string { + if x != nil { + return x.BenefitCode + } + return "" +} + +type CheckVipBenefitResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Allowed bool `protobuf:"varint,1,opt,name=allowed,proto3" json:"allowed,omitempty"` + Benefit *VipBenefit `protobuf:"bytes,2,opt,name=benefit,proto3" json:"benefit,omitempty"` + State *VipState `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` + DenialReason string `protobuf:"bytes,4,opt,name=denial_reason,json=denialReason,proto3" json:"denial_reason,omitempty"` + EvaluatedAtMs int64 `protobuf:"varint,5,opt,name=evaluated_at_ms,json=evaluatedAtMs,proto3" json:"evaluated_at_ms,omitempty"` +} + +func (x *CheckVipBenefitResponse) Reset() { + *x = CheckVipBenefitResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[229] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CheckVipBenefitResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckVipBenefitResponse) ProtoMessage() {} + +func (x *CheckVipBenefitResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[229] + 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 CheckVipBenefitResponse.ProtoReflect.Descriptor instead. +func (*CheckVipBenefitResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{229} +} + +func (x *CheckVipBenefitResponse) GetAllowed() bool { + if x != nil { + return x.Allowed + } + return false +} + +func (x *CheckVipBenefitResponse) GetBenefit() *VipBenefit { + if x != nil { + return x.Benefit + } + return nil +} + +func (x *CheckVipBenefitResponse) GetState() *VipState { + if x != nil { + return x.State + } + return nil +} + +func (x *CheckVipBenefitResponse) GetDenialReason() string { + if x != nil { + return x.DenialReason + } + return "" +} + +func (x *CheckVipBenefitResponse) GetEvaluatedAtMs() int64 { + if x != nil { + return x.EvaluatedAtMs + } + return 0 +} + type AdminVipLevelInput struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -17897,11 +20625,13 @@ type AdminVipLevelInput struct { RewardResourceGroupId int64 `protobuf:"varint,6,opt,name=reward_resource_group_id,json=rewardResourceGroupId,proto3" json:"reward_resource_group_id,omitempty"` SortOrder int32 `protobuf:"varint,7,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` RequiredRechargeCoinAmount int64 `protobuf:"varint,8,opt,name=required_recharge_coin_amount,json=requiredRechargeCoinAmount,proto3" json:"required_recharge_coin_amount,omitempty"` + // benefits 是该等级完整、显式的权益集合;服务端不根据 unlock_level 自动继承。 + Benefits []*VipBenefit `protobuf:"bytes,9,rep,name=benefits,proto3" json:"benefits,omitempty"` } func (x *AdminVipLevelInput) Reset() { *x = AdminVipLevelInput{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[198] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[230] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17913,7 +20643,7 @@ func (x *AdminVipLevelInput) String() string { func (*AdminVipLevelInput) ProtoMessage() {} func (x *AdminVipLevelInput) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[198] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[230] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17926,7 +20656,7 @@ func (x *AdminVipLevelInput) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminVipLevelInput.ProtoReflect.Descriptor instead. func (*AdminVipLevelInput) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{198} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{230} } func (x *AdminVipLevelInput) GetLevel() int32 { @@ -17985,6 +20715,13 @@ func (x *AdminVipLevelInput) GetRequiredRechargeCoinAmount() int64 { return 0 } +func (x *AdminVipLevelInput) GetBenefits() []*VipBenefit { + if x != nil { + return x.Benefits + } + return nil +} + type ListAdminVipLevelsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -17996,7 +20733,7 @@ type ListAdminVipLevelsRequest struct { func (x *ListAdminVipLevelsRequest) Reset() { *x = ListAdminVipLevelsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[199] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[231] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18008,7 +20745,7 @@ func (x *ListAdminVipLevelsRequest) String() string { func (*ListAdminVipLevelsRequest) ProtoMessage() {} func (x *ListAdminVipLevelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[199] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[231] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18021,7 +20758,7 @@ func (x *ListAdminVipLevelsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAdminVipLevelsRequest.ProtoReflect.Descriptor instead. func (*ListAdminVipLevelsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{199} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{231} } func (x *ListAdminVipLevelsRequest) GetRequestId() string { @@ -18043,13 +20780,14 @@ type ListAdminVipLevelsResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Levels []*VipLevel `protobuf:"bytes,1,rep,name=levels,proto3" json:"levels,omitempty"` - ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + Levels []*VipLevel `protobuf:"bytes,1,rep,name=levels,proto3" json:"levels,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + ProgramConfig *VipProgramConfig `protobuf:"bytes,3,opt,name=program_config,json=programConfig,proto3" json:"program_config,omitempty"` } func (x *ListAdminVipLevelsResponse) Reset() { *x = ListAdminVipLevelsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[200] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[232] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18061,7 +20799,7 @@ func (x *ListAdminVipLevelsResponse) String() string { func (*ListAdminVipLevelsResponse) ProtoMessage() {} func (x *ListAdminVipLevelsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[200] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[232] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18074,7 +20812,7 @@ func (x *ListAdminVipLevelsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAdminVipLevelsResponse.ProtoReflect.Descriptor instead. func (*ListAdminVipLevelsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{200} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{232} } func (x *ListAdminVipLevelsResponse) GetLevels() []*VipLevel { @@ -18091,6 +20829,13 @@ func (x *ListAdminVipLevelsResponse) GetServerTimeMs() int64 { return 0 } +func (x *ListAdminVipLevelsResponse) GetProgramConfig() *VipProgramConfig { + if x != nil { + return x.ProgramConfig + } + return nil +} + type UpdateAdminVipLevelsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -18104,7 +20849,7 @@ type UpdateAdminVipLevelsRequest struct { func (x *UpdateAdminVipLevelsRequest) Reset() { *x = UpdateAdminVipLevelsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[201] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[233] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18116,7 +20861,7 @@ func (x *UpdateAdminVipLevelsRequest) String() string { func (*UpdateAdminVipLevelsRequest) ProtoMessage() {} func (x *UpdateAdminVipLevelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[201] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[233] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18129,7 +20874,7 @@ func (x *UpdateAdminVipLevelsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateAdminVipLevelsRequest.ProtoReflect.Descriptor instead. func (*UpdateAdminVipLevelsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{201} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{233} } func (x *UpdateAdminVipLevelsRequest) GetRequestId() string { @@ -18165,13 +20910,14 @@ type UpdateAdminVipLevelsResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Levels []*VipLevel `protobuf:"bytes,1,rep,name=levels,proto3" json:"levels,omitempty"` - ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + Levels []*VipLevel `protobuf:"bytes,1,rep,name=levels,proto3" json:"levels,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + ProgramConfig *VipProgramConfig `protobuf:"bytes,3,opt,name=program_config,json=programConfig,proto3" json:"program_config,omitempty"` } func (x *UpdateAdminVipLevelsResponse) Reset() { *x = UpdateAdminVipLevelsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[202] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[234] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18183,7 +20929,7 @@ func (x *UpdateAdminVipLevelsResponse) String() string { func (*UpdateAdminVipLevelsResponse) ProtoMessage() {} func (x *UpdateAdminVipLevelsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[202] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[234] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18196,7 +20942,7 @@ func (x *UpdateAdminVipLevelsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateAdminVipLevelsResponse.ProtoReflect.Descriptor instead. func (*UpdateAdminVipLevelsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{202} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{234} } func (x *UpdateAdminVipLevelsResponse) GetLevels() []*VipLevel { @@ -18213,6 +20959,13 @@ func (x *UpdateAdminVipLevelsResponse) GetServerTimeMs() int64 { return 0 } +func (x *UpdateAdminVipLevelsResponse) GetProgramConfig() *VipProgramConfig { + if x != nil { + return x.ProgramConfig + } + return nil +} + // CreditTaskRewardRequest 是 activity-service 领取任务奖励时调用的钱包入账命令。 type CreditTaskRewardRequest struct { state protoimpl.MessageState @@ -18232,7 +20985,7 @@ type CreditTaskRewardRequest struct { func (x *CreditTaskRewardRequest) Reset() { *x = CreditTaskRewardRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[203] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[235] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18244,7 +20997,7 @@ func (x *CreditTaskRewardRequest) String() string { func (*CreditTaskRewardRequest) ProtoMessage() {} func (x *CreditTaskRewardRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[203] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[235] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18257,7 +21010,7 @@ func (x *CreditTaskRewardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditTaskRewardRequest.ProtoReflect.Descriptor instead. func (*CreditTaskRewardRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{203} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{235} } func (x *CreditTaskRewardRequest) GetCommandId() string { @@ -18337,7 +21090,7 @@ type CreditTaskRewardResponse struct { func (x *CreditTaskRewardResponse) Reset() { *x = CreditTaskRewardResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[204] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[236] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18349,7 +21102,7 @@ func (x *CreditTaskRewardResponse) String() string { func (*CreditTaskRewardResponse) ProtoMessage() {} func (x *CreditTaskRewardResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[204] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[236] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18362,7 +21115,7 @@ func (x *CreditTaskRewardResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditTaskRewardResponse.ProtoReflect.Descriptor instead. func (*CreditTaskRewardResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{204} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{236} } func (x *CreditTaskRewardResponse) GetTransactionId() string { @@ -18415,7 +21168,7 @@ type CreditLuckyGiftRewardRequest struct { func (x *CreditLuckyGiftRewardRequest) Reset() { *x = CreditLuckyGiftRewardRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[205] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[237] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18427,7 +21180,7 @@ func (x *CreditLuckyGiftRewardRequest) String() string { func (*CreditLuckyGiftRewardRequest) ProtoMessage() {} func (x *CreditLuckyGiftRewardRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[205] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[237] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18440,7 +21193,7 @@ func (x *CreditLuckyGiftRewardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditLuckyGiftRewardRequest.ProtoReflect.Descriptor instead. func (*CreditLuckyGiftRewardRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{205} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{237} } func (x *CreditLuckyGiftRewardRequest) GetCommandId() string { @@ -18534,7 +21287,7 @@ type CreditLuckyGiftRewardResponse struct { func (x *CreditLuckyGiftRewardResponse) Reset() { *x = CreditLuckyGiftRewardResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[206] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[238] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18546,7 +21299,7 @@ func (x *CreditLuckyGiftRewardResponse) String() string { func (*CreditLuckyGiftRewardResponse) ProtoMessage() {} func (x *CreditLuckyGiftRewardResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[206] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[238] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18559,7 +21312,7 @@ func (x *CreditLuckyGiftRewardResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditLuckyGiftRewardResponse.ProtoReflect.Descriptor instead. func (*CreditLuckyGiftRewardResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{206} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{238} } func (x *CreditLuckyGiftRewardResponse) GetTransactionId() string { @@ -18609,7 +21362,7 @@ type CreditWheelRewardRequest struct { func (x *CreditWheelRewardRequest) Reset() { *x = CreditWheelRewardRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[207] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[239] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18621,7 +21374,7 @@ func (x *CreditWheelRewardRequest) String() string { func (*CreditWheelRewardRequest) ProtoMessage() {} func (x *CreditWheelRewardRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[207] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[239] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18634,7 +21387,7 @@ func (x *CreditWheelRewardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditWheelRewardRequest.ProtoReflect.Descriptor instead. func (*CreditWheelRewardRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{207} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{239} } func (x *CreditWheelRewardRequest) GetCommandId() string { @@ -18714,7 +21467,7 @@ type CreditWheelRewardResponse struct { func (x *CreditWheelRewardResponse) Reset() { *x = CreditWheelRewardResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[208] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[240] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18726,7 +21479,7 @@ func (x *CreditWheelRewardResponse) String() string { func (*CreditWheelRewardResponse) ProtoMessage() {} func (x *CreditWheelRewardResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[208] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[240] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18739,7 +21492,7 @@ func (x *CreditWheelRewardResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditWheelRewardResponse.ProtoReflect.Descriptor instead. func (*CreditWheelRewardResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{208} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{240} } func (x *CreditWheelRewardResponse) GetTransactionId() string { @@ -18792,7 +21545,7 @@ type CreditRoomTurnoverRewardRequest struct { func (x *CreditRoomTurnoverRewardRequest) Reset() { *x = CreditRoomTurnoverRewardRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[209] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[241] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18804,7 +21557,7 @@ func (x *CreditRoomTurnoverRewardRequest) String() string { func (*CreditRoomTurnoverRewardRequest) ProtoMessage() {} func (x *CreditRoomTurnoverRewardRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[209] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[241] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18817,7 +21570,7 @@ func (x *CreditRoomTurnoverRewardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditRoomTurnoverRewardRequest.ProtoReflect.Descriptor instead. func (*CreditRoomTurnoverRewardRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{209} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{241} } func (x *CreditRoomTurnoverRewardRequest) GetCommandId() string { @@ -18918,7 +21671,7 @@ type CreditRoomTurnoverRewardResponse struct { func (x *CreditRoomTurnoverRewardResponse) Reset() { *x = CreditRoomTurnoverRewardResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[210] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[242] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18930,7 +21683,7 @@ func (x *CreditRoomTurnoverRewardResponse) String() string { func (*CreditRoomTurnoverRewardResponse) ProtoMessage() {} func (x *CreditRoomTurnoverRewardResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[210] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[242] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18943,7 +21696,7 @@ func (x *CreditRoomTurnoverRewardResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditRoomTurnoverRewardResponse.ProtoReflect.Descriptor instead. func (*CreditRoomTurnoverRewardResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{210} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{242} } func (x *CreditRoomTurnoverRewardResponse) GetTransactionId() string { @@ -18995,7 +21748,7 @@ type CreditInviteActivityRewardRequest struct { func (x *CreditInviteActivityRewardRequest) Reset() { *x = CreditInviteActivityRewardRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[211] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[243] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19007,7 +21760,7 @@ func (x *CreditInviteActivityRewardRequest) String() string { func (*CreditInviteActivityRewardRequest) ProtoMessage() {} func (x *CreditInviteActivityRewardRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[211] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[243] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19020,7 +21773,7 @@ func (x *CreditInviteActivityRewardRequest) ProtoReflect() protoreflect.Message // Deprecated: Use CreditInviteActivityRewardRequest.ProtoReflect.Descriptor instead. func (*CreditInviteActivityRewardRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{211} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{243} } func (x *CreditInviteActivityRewardRequest) GetCommandId() string { @@ -19114,7 +21867,7 @@ type CreditInviteActivityRewardResponse struct { func (x *CreditInviteActivityRewardResponse) Reset() { *x = CreditInviteActivityRewardResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[212] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[244] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19126,7 +21879,7 @@ func (x *CreditInviteActivityRewardResponse) String() string { func (*CreditInviteActivityRewardResponse) ProtoMessage() {} func (x *CreditInviteActivityRewardResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[212] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[244] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19139,7 +21892,7 @@ func (x *CreditInviteActivityRewardResponse) ProtoReflect() protoreflect.Message // Deprecated: Use CreditInviteActivityRewardResponse.ProtoReflect.Descriptor instead. func (*CreditInviteActivityRewardResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{212} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{244} } func (x *CreditInviteActivityRewardResponse) GetTransactionId() string { @@ -19190,7 +21943,7 @@ type CreditAgencyOpeningRewardRequest struct { func (x *CreditAgencyOpeningRewardRequest) Reset() { *x = CreditAgencyOpeningRewardRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[213] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[245] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19202,7 +21955,7 @@ func (x *CreditAgencyOpeningRewardRequest) String() string { func (*CreditAgencyOpeningRewardRequest) ProtoMessage() {} func (x *CreditAgencyOpeningRewardRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[213] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[245] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19215,7 +21968,7 @@ func (x *CreditAgencyOpeningRewardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditAgencyOpeningRewardRequest.ProtoReflect.Descriptor instead. func (*CreditAgencyOpeningRewardRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{213} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{245} } func (x *CreditAgencyOpeningRewardRequest) GetCommandId() string { @@ -19302,7 +22055,7 @@ type CreditAgencyOpeningRewardResponse struct { func (x *CreditAgencyOpeningRewardResponse) Reset() { *x = CreditAgencyOpeningRewardResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[214] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[246] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19314,7 +22067,7 @@ func (x *CreditAgencyOpeningRewardResponse) String() string { func (*CreditAgencyOpeningRewardResponse) ProtoMessage() {} func (x *CreditAgencyOpeningRewardResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[214] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[246] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19327,7 +22080,7 @@ func (x *CreditAgencyOpeningRewardResponse) ProtoReflect() protoreflect.Message // Deprecated: Use CreditAgencyOpeningRewardResponse.ProtoReflect.Descriptor instead. func (*CreditAgencyOpeningRewardResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{214} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{246} } func (x *CreditAgencyOpeningRewardResponse) GetTransactionId() string { @@ -19380,7 +22133,7 @@ type ApplyGameCoinChangeRequest struct { func (x *ApplyGameCoinChangeRequest) Reset() { *x = ApplyGameCoinChangeRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[215] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[247] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19392,7 +22145,7 @@ func (x *ApplyGameCoinChangeRequest) String() string { func (*ApplyGameCoinChangeRequest) ProtoMessage() {} func (x *ApplyGameCoinChangeRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[215] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[247] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19405,7 +22158,7 @@ func (x *ApplyGameCoinChangeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyGameCoinChangeRequest.ProtoReflect.Descriptor instead. func (*ApplyGameCoinChangeRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{215} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{247} } func (x *ApplyGameCoinChangeRequest) GetRequestId() string { @@ -19505,7 +22258,7 @@ type ApplyGameCoinChangeResponse struct { func (x *ApplyGameCoinChangeResponse) Reset() { *x = ApplyGameCoinChangeResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[216] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[248] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19517,7 +22270,7 @@ func (x *ApplyGameCoinChangeResponse) String() string { func (*ApplyGameCoinChangeResponse) ProtoMessage() {} func (x *ApplyGameCoinChangeResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[216] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[248] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19530,7 +22283,7 @@ func (x *ApplyGameCoinChangeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyGameCoinChangeResponse.ProtoReflect.Descriptor instead. func (*ApplyGameCoinChangeResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{216} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{248} } func (x *ApplyGameCoinChangeResponse) GetWalletTransactionId() string { @@ -19574,7 +22327,7 @@ type RedPacketConfig struct { func (x *RedPacketConfig) Reset() { *x = RedPacketConfig{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[217] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[249] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19586,7 +22339,7 @@ func (x *RedPacketConfig) String() string { func (*RedPacketConfig) ProtoMessage() {} func (x *RedPacketConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[217] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[249] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19599,7 +22352,7 @@ func (x *RedPacketConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use RedPacketConfig.ProtoReflect.Descriptor instead. func (*RedPacketConfig) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{217} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{249} } func (x *RedPacketConfig) GetAppCode() string { @@ -19699,7 +22452,7 @@ type RedPacketClaim struct { func (x *RedPacketClaim) Reset() { *x = RedPacketClaim{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[218] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[250] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19711,7 +22464,7 @@ func (x *RedPacketClaim) String() string { func (*RedPacketClaim) ProtoMessage() {} func (x *RedPacketClaim) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[218] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[250] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19724,7 +22477,7 @@ func (x *RedPacketClaim) ProtoReflect() protoreflect.Message { // Deprecated: Use RedPacketClaim.ProtoReflect.Descriptor instead. func (*RedPacketClaim) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{218} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{250} } func (x *RedPacketClaim) GetAppCode() string { @@ -19835,7 +22588,7 @@ type RedPacket struct { func (x *RedPacket) Reset() { *x = RedPacket{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[219] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[251] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19847,7 +22600,7 @@ func (x *RedPacket) String() string { func (*RedPacket) ProtoMessage() {} func (x *RedPacket) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[219] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[251] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19860,7 +22613,7 @@ func (x *RedPacket) ProtoReflect() protoreflect.Message { // Deprecated: Use RedPacket.ProtoReflect.Descriptor instead. func (*RedPacket) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{219} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{251} } func (x *RedPacket) GetAppCode() string { @@ -20028,7 +22781,7 @@ type GetRedPacketConfigRequest struct { func (x *GetRedPacketConfigRequest) Reset() { *x = GetRedPacketConfigRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[220] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[252] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20040,7 +22793,7 @@ func (x *GetRedPacketConfigRequest) String() string { func (*GetRedPacketConfigRequest) ProtoMessage() {} func (x *GetRedPacketConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[220] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[252] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20053,7 +22806,7 @@ func (x *GetRedPacketConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRedPacketConfigRequest.ProtoReflect.Descriptor instead. func (*GetRedPacketConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{220} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{252} } func (x *GetRedPacketConfigRequest) GetRequestId() string { @@ -20081,7 +22834,7 @@ type GetRedPacketConfigResponse struct { func (x *GetRedPacketConfigResponse) Reset() { *x = GetRedPacketConfigResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[221] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[253] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20093,7 +22846,7 @@ func (x *GetRedPacketConfigResponse) String() string { func (*GetRedPacketConfigResponse) ProtoMessage() {} func (x *GetRedPacketConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[221] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[253] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20106,7 +22859,7 @@ func (x *GetRedPacketConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRedPacketConfigResponse.ProtoReflect.Descriptor instead. func (*GetRedPacketConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{221} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{253} } func (x *GetRedPacketConfigResponse) GetConfig() *RedPacketConfig { @@ -20142,7 +22895,7 @@ type UpdateRedPacketConfigRequest struct { func (x *UpdateRedPacketConfigRequest) Reset() { *x = UpdateRedPacketConfigRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[222] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[254] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20154,7 +22907,7 @@ func (x *UpdateRedPacketConfigRequest) String() string { func (*UpdateRedPacketConfigRequest) ProtoMessage() {} func (x *UpdateRedPacketConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[222] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[254] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20167,7 +22920,7 @@ func (x *UpdateRedPacketConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRedPacketConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateRedPacketConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{222} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{254} } func (x *UpdateRedPacketConfigRequest) GetRequestId() string { @@ -20251,7 +23004,7 @@ type UpdateRedPacketConfigResponse struct { func (x *UpdateRedPacketConfigResponse) Reset() { *x = UpdateRedPacketConfigResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[223] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[255] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20263,7 +23016,7 @@ func (x *UpdateRedPacketConfigResponse) String() string { func (*UpdateRedPacketConfigResponse) ProtoMessage() {} func (x *UpdateRedPacketConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[223] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[255] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20276,7 +23029,7 @@ func (x *UpdateRedPacketConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRedPacketConfigResponse.ProtoReflect.Descriptor instead. func (*UpdateRedPacketConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{223} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{255} } func (x *UpdateRedPacketConfigResponse) GetConfig() *RedPacketConfig { @@ -20311,7 +23064,7 @@ type CreateRedPacketRequest struct { func (x *CreateRedPacketRequest) Reset() { *x = CreateRedPacketRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[224] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[256] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20323,7 +23076,7 @@ func (x *CreateRedPacketRequest) String() string { func (*CreateRedPacketRequest) ProtoMessage() {} func (x *CreateRedPacketRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[224] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[256] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20336,7 +23089,7 @@ func (x *CreateRedPacketRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateRedPacketRequest.ProtoReflect.Descriptor instead. func (*CreateRedPacketRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{224} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{256} } func (x *CreateRedPacketRequest) GetRequestId() string { @@ -20414,7 +23167,7 @@ type CreateRedPacketResponse struct { func (x *CreateRedPacketResponse) Reset() { *x = CreateRedPacketResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[225] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[257] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20426,7 +23179,7 @@ func (x *CreateRedPacketResponse) String() string { func (*CreateRedPacketResponse) ProtoMessage() {} func (x *CreateRedPacketResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[225] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[257] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20439,7 +23192,7 @@ func (x *CreateRedPacketResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateRedPacketResponse.ProtoReflect.Descriptor instead. func (*CreateRedPacketResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{225} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{257} } func (x *CreateRedPacketResponse) GetPacket() *RedPacket { @@ -20477,7 +23230,7 @@ type ClaimRedPacketRequest struct { func (x *ClaimRedPacketRequest) Reset() { *x = ClaimRedPacketRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[226] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[258] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20489,7 +23242,7 @@ func (x *ClaimRedPacketRequest) String() string { func (*ClaimRedPacketRequest) ProtoMessage() {} func (x *ClaimRedPacketRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[226] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[258] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20502,7 +23255,7 @@ func (x *ClaimRedPacketRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClaimRedPacketRequest.ProtoReflect.Descriptor instead. func (*ClaimRedPacketRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{226} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{258} } func (x *ClaimRedPacketRequest) GetRequestId() string { @@ -20553,7 +23306,7 @@ type ClaimRedPacketResponse struct { func (x *ClaimRedPacketResponse) Reset() { *x = ClaimRedPacketResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[227] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[259] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20565,7 +23318,7 @@ func (x *ClaimRedPacketResponse) String() string { func (*ClaimRedPacketResponse) ProtoMessage() {} func (x *ClaimRedPacketResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[227] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[259] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20578,7 +23331,7 @@ func (x *ClaimRedPacketResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClaimRedPacketResponse.ProtoReflect.Descriptor instead. func (*ClaimRedPacketResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{227} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{259} } func (x *ClaimRedPacketResponse) GetClaim() *RedPacketClaim { @@ -20630,7 +23383,7 @@ type ListRedPacketsRequest struct { func (x *ListRedPacketsRequest) Reset() { *x = ListRedPacketsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[228] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[260] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20642,7 +23395,7 @@ func (x *ListRedPacketsRequest) String() string { func (*ListRedPacketsRequest) ProtoMessage() {} func (x *ListRedPacketsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[228] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[260] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20655,7 +23408,7 @@ func (x *ListRedPacketsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRedPacketsRequest.ProtoReflect.Descriptor instead. func (*ListRedPacketsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{228} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{260} } func (x *ListRedPacketsRequest) GetRequestId() string { @@ -20754,7 +23507,7 @@ type ListRedPacketsResponse struct { func (x *ListRedPacketsResponse) Reset() { *x = ListRedPacketsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[229] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[261] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20766,7 +23519,7 @@ func (x *ListRedPacketsResponse) String() string { func (*ListRedPacketsResponse) ProtoMessage() {} func (x *ListRedPacketsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[229] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[261] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20779,7 +23532,7 @@ func (x *ListRedPacketsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRedPacketsResponse.ProtoReflect.Descriptor instead. func (*ListRedPacketsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{229} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{261} } func (x *ListRedPacketsResponse) GetPackets() []*RedPacket { @@ -20817,7 +23570,7 @@ type GetRedPacketRequest struct { func (x *GetRedPacketRequest) Reset() { *x = GetRedPacketRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[230] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[262] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20829,7 +23582,7 @@ func (x *GetRedPacketRequest) String() string { func (*GetRedPacketRequest) ProtoMessage() {} func (x *GetRedPacketRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[230] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[262] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20842,7 +23595,7 @@ func (x *GetRedPacketRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRedPacketRequest.ProtoReflect.Descriptor instead. func (*GetRedPacketRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{230} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{262} } func (x *GetRedPacketRequest) GetRequestId() string { @@ -20891,7 +23644,7 @@ type GetRedPacketResponse struct { func (x *GetRedPacketResponse) Reset() { *x = GetRedPacketResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[231] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[263] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20903,7 +23656,7 @@ func (x *GetRedPacketResponse) String() string { func (*GetRedPacketResponse) ProtoMessage() {} func (x *GetRedPacketResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[231] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[263] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20916,7 +23669,7 @@ func (x *GetRedPacketResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRedPacketResponse.ProtoReflect.Descriptor instead. func (*GetRedPacketResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{231} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{263} } func (x *GetRedPacketResponse) GetPacket() *RedPacket { @@ -20945,7 +23698,7 @@ type ExpireRedPacketsRequest struct { func (x *ExpireRedPacketsRequest) Reset() { *x = ExpireRedPacketsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[232] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[264] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -20957,7 +23710,7 @@ func (x *ExpireRedPacketsRequest) String() string { func (*ExpireRedPacketsRequest) ProtoMessage() {} func (x *ExpireRedPacketsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[232] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[264] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -20970,7 +23723,7 @@ func (x *ExpireRedPacketsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExpireRedPacketsRequest.ProtoReflect.Descriptor instead. func (*ExpireRedPacketsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{232} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{264} } func (x *ExpireRedPacketsRequest) GetRequestId() string { @@ -21006,7 +23759,7 @@ type ExpireRedPacketsResponse struct { func (x *ExpireRedPacketsResponse) Reset() { *x = ExpireRedPacketsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[233] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[265] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -21018,7 +23771,7 @@ func (x *ExpireRedPacketsResponse) String() string { func (*ExpireRedPacketsResponse) ProtoMessage() {} func (x *ExpireRedPacketsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[233] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[265] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -21031,7 +23784,7 @@ func (x *ExpireRedPacketsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ExpireRedPacketsResponse.ProtoReflect.Descriptor instead. func (*ExpireRedPacketsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{233} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{265} } func (x *ExpireRedPacketsResponse) GetExpiredCount() int32 { @@ -21068,7 +23821,7 @@ type RetryRedPacketRefundRequest struct { func (x *RetryRedPacketRefundRequest) Reset() { *x = RetryRedPacketRefundRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[234] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[266] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -21080,7 +23833,7 @@ func (x *RetryRedPacketRefundRequest) String() string { func (*RetryRedPacketRefundRequest) ProtoMessage() {} func (x *RetryRedPacketRefundRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[234] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[266] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -21093,7 +23846,7 @@ func (x *RetryRedPacketRefundRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RetryRedPacketRefundRequest.ProtoReflect.Descriptor instead. func (*RetryRedPacketRefundRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{234} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{266} } func (x *RetryRedPacketRefundRequest) GetRequestId() string { @@ -21136,7 +23889,7 @@ type RetryRedPacketRefundResponse struct { func (x *RetryRedPacketRefundResponse) Reset() { *x = RetryRedPacketRefundResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[235] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[267] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -21148,7 +23901,7 @@ func (x *RetryRedPacketRefundResponse) String() string { func (*RetryRedPacketRefundResponse) ProtoMessage() {} func (x *RetryRedPacketRefundResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[235] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[267] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -21161,7 +23914,7 @@ func (x *RetryRedPacketRefundResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RetryRedPacketRefundResponse.ProtoReflect.Descriptor instead. func (*RetryRedPacketRefundResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{235} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{267} } func (x *RetryRedPacketRefundResponse) GetPacket() *RedPacket { @@ -21209,7 +23962,7 @@ type CronBatchRequest struct { func (x *CronBatchRequest) Reset() { *x = CronBatchRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[236] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[268] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -21221,7 +23974,7 @@ func (x *CronBatchRequest) String() string { func (*CronBatchRequest) ProtoMessage() {} func (x *CronBatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[236] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[268] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -21234,7 +23987,7 @@ func (x *CronBatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CronBatchRequest.ProtoReflect.Descriptor instead. func (*CronBatchRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{236} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{268} } func (x *CronBatchRequest) GetRequestId() string { @@ -21322,7 +24075,7 @@ type CronBatchResponse struct { func (x *CronBatchResponse) Reset() { *x = CronBatchResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[237] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[269] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -21334,7 +24087,7 @@ func (x *CronBatchResponse) String() string { func (*CronBatchResponse) ProtoMessage() {} func (x *CronBatchResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[237] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[269] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -21347,7 +24100,7 @@ func (x *CronBatchResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CronBatchResponse.ProtoReflect.Descriptor instead. func (*CronBatchResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{237} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{269} } func (x *CronBatchResponse) GetClaimedCount() int32 { @@ -21409,7 +24162,7 @@ type ResourceShopPurchaseOrder struct { func (x *ResourceShopPurchaseOrder) Reset() { *x = ResourceShopPurchaseOrder{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[238] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[270] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -21421,7 +24174,7 @@ func (x *ResourceShopPurchaseOrder) String() string { func (*ResourceShopPurchaseOrder) ProtoMessage() {} func (x *ResourceShopPurchaseOrder) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[238] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[270] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -21434,7 +24187,7 @@ func (x *ResourceShopPurchaseOrder) ProtoReflect() protoreflect.Message { // Deprecated: Use ResourceShopPurchaseOrder.ProtoReflect.Descriptor instead. func (*ResourceShopPurchaseOrder) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{238} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{270} } func (x *ResourceShopPurchaseOrder) GetAppCode() string { @@ -21559,7 +24312,7 @@ type ListResourceShopPurchaseOrdersRequest struct { func (x *ListResourceShopPurchaseOrdersRequest) Reset() { *x = ListResourceShopPurchaseOrdersRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[239] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[271] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -21571,7 +24324,7 @@ func (x *ListResourceShopPurchaseOrdersRequest) String() string { func (*ListResourceShopPurchaseOrdersRequest) ProtoMessage() {} func (x *ListResourceShopPurchaseOrdersRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[239] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[271] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -21584,7 +24337,7 @@ func (x *ListResourceShopPurchaseOrdersRequest) ProtoReflect() protoreflect.Mess // Deprecated: Use ListResourceShopPurchaseOrdersRequest.ProtoReflect.Descriptor instead. func (*ListResourceShopPurchaseOrdersRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{239} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{271} } func (x *ListResourceShopPurchaseOrdersRequest) GetRequestId() string { @@ -21654,7 +24407,7 @@ type ListResourceShopPurchaseOrdersResponse struct { func (x *ListResourceShopPurchaseOrdersResponse) Reset() { *x = ListResourceShopPurchaseOrdersResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[240] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[272] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -21666,7 +24419,7 @@ func (x *ListResourceShopPurchaseOrdersResponse) String() string { func (*ListResourceShopPurchaseOrdersResponse) ProtoMessage() {} func (x *ListResourceShopPurchaseOrdersResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[240] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[272] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -21679,7 +24432,7 @@ func (x *ListResourceShopPurchaseOrdersResponse) ProtoReflect() protoreflect.Mes // Deprecated: Use ListResourceShopPurchaseOrdersResponse.ProtoReflect.Descriptor instead. func (*ListResourceShopPurchaseOrdersResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{240} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{272} } func (x *ListResourceShopPurchaseOrdersResponse) GetOrders() []*ResourceShopPurchaseOrder { @@ -21707,7 +24460,7 @@ type GetGiftCatalogVersionRequest struct { func (x *GetGiftCatalogVersionRequest) Reset() { *x = GetGiftCatalogVersionRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[241] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[273] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -21719,7 +24472,7 @@ func (x *GetGiftCatalogVersionRequest) String() string { func (*GetGiftCatalogVersionRequest) ProtoMessage() {} func (x *GetGiftCatalogVersionRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[241] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[273] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -21732,7 +24485,7 @@ func (x *GetGiftCatalogVersionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetGiftCatalogVersionRequest.ProtoReflect.Descriptor instead. func (*GetGiftCatalogVersionRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{241} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{273} } func (x *GetGiftCatalogVersionRequest) GetRequestId() string { @@ -21759,7 +24512,7 @@ type GetGiftCatalogVersionResponse struct { func (x *GetGiftCatalogVersionResponse) Reset() { *x = GetGiftCatalogVersionResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[242] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[274] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -21771,7 +24524,7 @@ func (x *GetGiftCatalogVersionResponse) String() string { func (*GetGiftCatalogVersionResponse) ProtoMessage() {} func (x *GetGiftCatalogVersionResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[242] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[274] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -21784,7 +24537,7 @@ func (x *GetGiftCatalogVersionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetGiftCatalogVersionResponse.ProtoReflect.Descriptor instead. func (*GetGiftCatalogVersionResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{242} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{274} } func (x *GetGiftCatalogVersionResponse) GetVersion() int64 { @@ -21794,6 +24547,1100 @@ func (x *GetGiftCatalogVersionResponse) GetVersion() int64 { return 0 } +// HostRevenueStats 聚合主播在自然日区间内的收益 POINT 和送礼用户;gateway 对外仍按产品文案展示为钻石。 +type HostRevenueStats struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DiamondEarnings int64 `protobuf:"varint,1,opt,name=diamond_earnings,json=diamondEarnings,proto3" json:"diamond_earnings,omitempty"` + DiamondExchanged int64 `protobuf:"varint,2,opt,name=diamond_exchanged,json=diamondExchanged,proto3" json:"diamond_exchanged,omitempty"` + GiftSenders int64 `protobuf:"varint,3,opt,name=gift_senders,json=giftSenders,proto3" json:"gift_senders,omitempty"` +} + +func (x *HostRevenueStats) Reset() { + *x = HostRevenueStats{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[275] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HostRevenueStats) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HostRevenueStats) ProtoMessage() {} + +func (x *HostRevenueStats) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[275] + 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 HostRevenueStats.ProtoReflect.Descriptor instead. +func (*HostRevenueStats) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{275} +} + +func (x *HostRevenueStats) GetDiamondEarnings() int64 { + if x != nil { + return x.DiamondEarnings + } + return 0 +} + +func (x *HostRevenueStats) GetDiamondExchanged() int64 { + if x != nil { + return x.DiamondExchanged + } + return 0 +} + +func (x *HostRevenueStats) GetGiftSenders() int64 { + if x != nil { + return x.GiftSenders + } + return 0 +} + +type GetHostRevenueStatsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + HostUserId int64 `protobuf:"varint,3,opt,name=host_user_id,json=hostUserId,proto3" json:"host_user_id,omitempty"` + StartAtMs int64 `protobuf:"varint,4,opt,name=start_at_ms,json=startAtMs,proto3" json:"start_at_ms,omitempty"` + EndAtMs int64 `protobuf:"varint,5,opt,name=end_at_ms,json=endAtMs,proto3" json:"end_at_ms,omitempty"` +} + +func (x *GetHostRevenueStatsRequest) Reset() { + *x = GetHostRevenueStatsRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[276] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetHostRevenueStatsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetHostRevenueStatsRequest) ProtoMessage() {} + +func (x *GetHostRevenueStatsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[276] + 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 GetHostRevenueStatsRequest.ProtoReflect.Descriptor instead. +func (*GetHostRevenueStatsRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{276} +} + +func (x *GetHostRevenueStatsRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *GetHostRevenueStatsRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *GetHostRevenueStatsRequest) GetHostUserId() int64 { + if x != nil { + return x.HostUserId + } + return 0 +} + +func (x *GetHostRevenueStatsRequest) GetStartAtMs() int64 { + if x != nil { + return x.StartAtMs + } + return 0 +} + +func (x *GetHostRevenueStatsRequest) GetEndAtMs() int64 { + if x != nil { + return x.EndAtMs + } + return 0 +} + +type GetHostRevenueStatsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Stats *HostRevenueStats `protobuf:"bytes,1,opt,name=stats,proto3" json:"stats,omitempty"` +} + +func (x *GetHostRevenueStatsResponse) Reset() { + *x = GetHostRevenueStatsResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[277] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetHostRevenueStatsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetHostRevenueStatsResponse) ProtoMessage() {} + +func (x *GetHostRevenueStatsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[277] + 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 GetHostRevenueStatsResponse.ProtoReflect.Descriptor instead. +func (*GetHostRevenueStatsResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{277} +} + +func (x *GetHostRevenueStatsResponse) GetStats() *HostRevenueStats { + if x != nil { + return x.Stats + } + return nil +} + +// AgencyPointShareStats 只统计独立 Agency 分成流水,不把 owner 自己的主播礼物收益重复算入分成。 +type AgencyPointShareStats struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ShareIncome int64 `protobuf:"varint,1,opt,name=share_income,json=shareIncome,proto3" json:"share_income,omitempty"` + GiftedHostCount int64 `protobuf:"varint,2,opt,name=gifted_host_count,json=giftedHostCount,proto3" json:"gifted_host_count,omitempty"` +} + +func (x *AgencyPointShareStats) Reset() { + *x = AgencyPointShareStats{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[278] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgencyPointShareStats) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgencyPointShareStats) ProtoMessage() {} + +func (x *AgencyPointShareStats) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[278] + 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 AgencyPointShareStats.ProtoReflect.Descriptor instead. +func (*AgencyPointShareStats) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{278} +} + +func (x *AgencyPointShareStats) GetShareIncome() int64 { + if x != nil { + return x.ShareIncome + } + return 0 +} + +func (x *AgencyPointShareStats) GetGiftedHostCount() int64 { + if x != nil { + return x.GiftedHostCount + } + return 0 +} + +type GetAgencyPointShareStatsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + AgencyOwnerUserId int64 `protobuf:"varint,3,opt,name=agency_owner_user_id,json=agencyOwnerUserId,proto3" json:"agency_owner_user_id,omitempty"` + StartAtMs int64 `protobuf:"varint,4,opt,name=start_at_ms,json=startAtMs,proto3" json:"start_at_ms,omitempty"` + EndAtMs int64 `protobuf:"varint,5,opt,name=end_at_ms,json=endAtMs,proto3" json:"end_at_ms,omitempty"` +} + +func (x *GetAgencyPointShareStatsRequest) Reset() { + *x = GetAgencyPointShareStatsRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[279] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAgencyPointShareStatsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAgencyPointShareStatsRequest) ProtoMessage() {} + +func (x *GetAgencyPointShareStatsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[279] + 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 GetAgencyPointShareStatsRequest.ProtoReflect.Descriptor instead. +func (*GetAgencyPointShareStatsRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{279} +} + +func (x *GetAgencyPointShareStatsRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *GetAgencyPointShareStatsRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *GetAgencyPointShareStatsRequest) GetAgencyOwnerUserId() int64 { + if x != nil { + return x.AgencyOwnerUserId + } + return 0 +} + +func (x *GetAgencyPointShareStatsRequest) GetStartAtMs() int64 { + if x != nil { + return x.StartAtMs + } + return 0 +} + +func (x *GetAgencyPointShareStatsRequest) GetEndAtMs() int64 { + if x != nil { + return x.EndAtMs + } + return 0 +} + +type GetAgencyPointShareStatsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Stats *AgencyPointShareStats `protobuf:"bytes,1,opt,name=stats,proto3" json:"stats,omitempty"` +} + +func (x *GetAgencyPointShareStatsResponse) Reset() { + *x = GetAgencyPointShareStatsResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[280] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAgencyPointShareStatsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAgencyPointShareStatsResponse) ProtoMessage() {} + +func (x *GetAgencyPointShareStatsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[280] + 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 GetAgencyPointShareStatsResponse.ProtoReflect.Descriptor instead. +func (*GetAgencyPointShareStatsResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{280} +} + +func (x *GetAgencyPointShareStatsResponse) GetStats() *AgencyPointShareStats { + if x != nil { + return x.Stats + } + return nil +} + +type AgencyHostGiftStats struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + GiftIncome int64 `protobuf:"varint,1,opt,name=gift_income,json=giftIncome,proto3" json:"gift_income,omitempty"` + GiftedHostCount int64 `protobuf:"varint,2,opt,name=gifted_host_count,json=giftedHostCount,proto3" json:"gifted_host_count,omitempty"` +} + +func (x *AgencyHostGiftStats) Reset() { + *x = AgencyHostGiftStats{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[281] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgencyHostGiftStats) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgencyHostGiftStats) ProtoMessage() {} + +func (x *AgencyHostGiftStats) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[281] + 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 AgencyHostGiftStats.ProtoReflect.Descriptor instead. +func (*AgencyHostGiftStats) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{281} +} + +func (x *AgencyHostGiftStats) GetGiftIncome() int64 { + if x != nil { + return x.GiftIncome + } + return 0 +} + +func (x *AgencyHostGiftStats) GetGiftedHostCount() int64 { + if x != nil { + return x.GiftedHostCount + } + return 0 +} + +type GetAgencyHostGiftStatsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + HostUserIds []int64 `protobuf:"varint,3,rep,packed,name=host_user_ids,json=hostUserIds,proto3" json:"host_user_ids,omitempty"` + StartAtMs int64 `protobuf:"varint,4,opt,name=start_at_ms,json=startAtMs,proto3" json:"start_at_ms,omitempty"` + EndAtMs int64 `protobuf:"varint,5,opt,name=end_at_ms,json=endAtMs,proto3" json:"end_at_ms,omitempty"` +} + +func (x *GetAgencyHostGiftStatsRequest) Reset() { + *x = GetAgencyHostGiftStatsRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[282] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAgencyHostGiftStatsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAgencyHostGiftStatsRequest) ProtoMessage() {} + +func (x *GetAgencyHostGiftStatsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[282] + 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 GetAgencyHostGiftStatsRequest.ProtoReflect.Descriptor instead. +func (*GetAgencyHostGiftStatsRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{282} +} + +func (x *GetAgencyHostGiftStatsRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *GetAgencyHostGiftStatsRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *GetAgencyHostGiftStatsRequest) GetHostUserIds() []int64 { + if x != nil { + return x.HostUserIds + } + return nil +} + +func (x *GetAgencyHostGiftStatsRequest) GetStartAtMs() int64 { + if x != nil { + return x.StartAtMs + } + return 0 +} + +func (x *GetAgencyHostGiftStatsRequest) GetEndAtMs() int64 { + if x != nil { + return x.EndAtMs + } + return 0 +} + +type GetAgencyHostGiftStatsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Stats *AgencyHostGiftStats `protobuf:"bytes,1,opt,name=stats,proto3" json:"stats,omitempty"` +} + +func (x *GetAgencyHostGiftStatsResponse) Reset() { + *x = GetAgencyHostGiftStatsResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[283] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAgencyHostGiftStatsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAgencyHostGiftStatsResponse) ProtoMessage() {} + +func (x *GetAgencyHostGiftStatsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[283] + 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 GetAgencyHostGiftStatsResponse.ProtoReflect.Descriptor instead. +func (*GetAgencyHostGiftStatsResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{283} +} + +func (x *GetAgencyHostGiftStatsResponse) GetStats() *AgencyHostGiftStats { + if x != nil { + return x.Stats + } + return nil +} + +// PointWithdrawalCoinSellerConfig 是按 App 隔离的 POINT 提现币商白名单与兑换比例。 +// 比例用整数分子/分母保存,避免浮点金额在 H5、gateway 和账本间产生舍入分歧。 +type PointWithdrawalCoinSellerConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + SellerUserId int64 `protobuf:"varint,2,opt,name=seller_user_id,json=sellerUserId,proto3" json:"seller_user_id,omitempty"` + SortOrder int32 `protobuf:"varint,3,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + PointAmount int64 `protobuf:"varint,4,opt,name=point_amount,json=pointAmount,proto3" json:"point_amount,omitempty"` + SellerCoinAmount int64 `protobuf:"varint,5,opt,name=seller_coin_amount,json=sellerCoinAmount,proto3" json:"seller_coin_amount,omitempty"` + ServiceCountryCodes []string `protobuf:"bytes,6,rep,name=service_country_codes,json=serviceCountryCodes,proto3" json:"service_country_codes,omitempty"` + Status string `protobuf:"bytes,7,opt,name=status,proto3" json:"status,omitempty"` + CreatedAtMs int64 `protobuf:"varint,8,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,9,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` +} + +func (x *PointWithdrawalCoinSellerConfig) Reset() { + *x = PointWithdrawalCoinSellerConfig{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[284] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PointWithdrawalCoinSellerConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PointWithdrawalCoinSellerConfig) ProtoMessage() {} + +func (x *PointWithdrawalCoinSellerConfig) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[284] + 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 PointWithdrawalCoinSellerConfig.ProtoReflect.Descriptor instead. +func (*PointWithdrawalCoinSellerConfig) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{284} +} + +func (x *PointWithdrawalCoinSellerConfig) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *PointWithdrawalCoinSellerConfig) GetSellerUserId() int64 { + if x != nil { + return x.SellerUserId + } + return 0 +} + +func (x *PointWithdrawalCoinSellerConfig) GetSortOrder() int32 { + if x != nil { + return x.SortOrder + } + return 0 +} + +func (x *PointWithdrawalCoinSellerConfig) GetPointAmount() int64 { + if x != nil { + return x.PointAmount + } + return 0 +} + +func (x *PointWithdrawalCoinSellerConfig) GetSellerCoinAmount() int64 { + if x != nil { + return x.SellerCoinAmount + } + return 0 +} + +func (x *PointWithdrawalCoinSellerConfig) GetServiceCountryCodes() []string { + if x != nil { + return x.ServiceCountryCodes + } + return nil +} + +func (x *PointWithdrawalCoinSellerConfig) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *PointWithdrawalCoinSellerConfig) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *PointWithdrawalCoinSellerConfig) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +type ListPointWithdrawalCoinSellersRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + // country_code 由 gateway 从登录用户资料注入;为空只用于 Admin/内部查询。 + CountryCode string `protobuf:"bytes,3,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"` + IncludeDisabled bool `protobuf:"varint,4,opt,name=include_disabled,json=includeDisabled,proto3" json:"include_disabled,omitempty"` +} + +func (x *ListPointWithdrawalCoinSellersRequest) Reset() { + *x = ListPointWithdrawalCoinSellersRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[285] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListPointWithdrawalCoinSellersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPointWithdrawalCoinSellersRequest) ProtoMessage() {} + +func (x *ListPointWithdrawalCoinSellersRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[285] + 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 ListPointWithdrawalCoinSellersRequest.ProtoReflect.Descriptor instead. +func (*ListPointWithdrawalCoinSellersRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{285} +} + +func (x *ListPointWithdrawalCoinSellersRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *ListPointWithdrawalCoinSellersRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *ListPointWithdrawalCoinSellersRequest) GetCountryCode() string { + if x != nil { + return x.CountryCode + } + return "" +} + +func (x *ListPointWithdrawalCoinSellersRequest) GetIncludeDisabled() bool { + if x != nil { + return x.IncludeDisabled + } + return false +} + +type ListPointWithdrawalCoinSellersResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Sellers []*PointWithdrawalCoinSellerConfig `protobuf:"bytes,1,rep,name=sellers,proto3" json:"sellers,omitempty"` +} + +func (x *ListPointWithdrawalCoinSellersResponse) Reset() { + *x = ListPointWithdrawalCoinSellersResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[286] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListPointWithdrawalCoinSellersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPointWithdrawalCoinSellersResponse) ProtoMessage() {} + +func (x *ListPointWithdrawalCoinSellersResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[286] + 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 ListPointWithdrawalCoinSellersResponse.ProtoReflect.Descriptor instead. +func (*ListPointWithdrawalCoinSellersResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{286} +} + +func (x *ListPointWithdrawalCoinSellersResponse) GetSellers() []*PointWithdrawalCoinSellerConfig { + if x != nil { + return x.Sellers + } + return nil +} + +type TransferPointToCoinSellerRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CommandId string `protobuf:"bytes,1,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + SourceUserId int64 `protobuf:"varint,3,opt,name=source_user_id,json=sourceUserId,proto3" json:"source_user_id,omitempty"` + SellerUserId int64 `protobuf:"varint,4,opt,name=seller_user_id,json=sellerUserId,proto3" json:"seller_user_id,omitempty"` + PointAmount int64 `protobuf:"varint,5,opt,name=point_amount,json=pointAmount,proto3" json:"point_amount,omitempty"` + // source_country_code 由 gateway 从用户资料读取,wallet 会再次校验币商服务国家。 + SourceCountryCode string `protobuf:"bytes,6,opt,name=source_country_code,json=sourceCountryCode,proto3" json:"source_country_code,omitempty"` + Reason string `protobuf:"bytes,7,opt,name=reason,proto3" json:"reason,omitempty"` +} + +func (x *TransferPointToCoinSellerRequest) Reset() { + *x = TransferPointToCoinSellerRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[287] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransferPointToCoinSellerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransferPointToCoinSellerRequest) ProtoMessage() {} + +func (x *TransferPointToCoinSellerRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[287] + 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 TransferPointToCoinSellerRequest.ProtoReflect.Descriptor instead. +func (*TransferPointToCoinSellerRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{287} +} + +func (x *TransferPointToCoinSellerRequest) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *TransferPointToCoinSellerRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *TransferPointToCoinSellerRequest) GetSourceUserId() int64 { + if x != nil { + return x.SourceUserId + } + return 0 +} + +func (x *TransferPointToCoinSellerRequest) GetSellerUserId() int64 { + if x != nil { + return x.SellerUserId + } + return 0 +} + +func (x *TransferPointToCoinSellerRequest) GetPointAmount() int64 { + if x != nil { + return x.PointAmount + } + return 0 +} + +func (x *TransferPointToCoinSellerRequest) GetSourceCountryCode() string { + if x != nil { + return x.SourceCountryCode + } + return "" +} + +func (x *TransferPointToCoinSellerRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +type TransferPointToCoinSellerResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TransactionId string `protobuf:"bytes,1,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` + SourcePointBalanceAfter int64 `protobuf:"varint,2,opt,name=source_point_balance_after,json=sourcePointBalanceAfter,proto3" json:"source_point_balance_after,omitempty"` + SellerBalanceAfter int64 `protobuf:"varint,3,opt,name=seller_balance_after,json=sellerBalanceAfter,proto3" json:"seller_balance_after,omitempty"` + PointAmount int64 `protobuf:"varint,4,opt,name=point_amount,json=pointAmount,proto3" json:"point_amount,omitempty"` + SellerCoinAmount int64 `protobuf:"varint,5,opt,name=seller_coin_amount,json=sellerCoinAmount,proto3" json:"seller_coin_amount,omitempty"` + RatioPointAmount int64 `protobuf:"varint,6,opt,name=ratio_point_amount,json=ratioPointAmount,proto3" json:"ratio_point_amount,omitempty"` + RatioSellerCoinAmount int64 `protobuf:"varint,7,opt,name=ratio_seller_coin_amount,json=ratioSellerCoinAmount,proto3" json:"ratio_seller_coin_amount,omitempty"` +} + +func (x *TransferPointToCoinSellerResponse) Reset() { + *x = TransferPointToCoinSellerResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[288] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransferPointToCoinSellerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransferPointToCoinSellerResponse) ProtoMessage() {} + +func (x *TransferPointToCoinSellerResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[288] + 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 TransferPointToCoinSellerResponse.ProtoReflect.Descriptor instead. +func (*TransferPointToCoinSellerResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{288} +} + +func (x *TransferPointToCoinSellerResponse) GetTransactionId() string { + if x != nil { + return x.TransactionId + } + return "" +} + +func (x *TransferPointToCoinSellerResponse) GetSourcePointBalanceAfter() int64 { + if x != nil { + return x.SourcePointBalanceAfter + } + return 0 +} + +func (x *TransferPointToCoinSellerResponse) GetSellerBalanceAfter() int64 { + if x != nil { + return x.SellerBalanceAfter + } + return 0 +} + +func (x *TransferPointToCoinSellerResponse) GetPointAmount() int64 { + if x != nil { + return x.PointAmount + } + return 0 +} + +func (x *TransferPointToCoinSellerResponse) GetSellerCoinAmount() int64 { + if x != nil { + return x.SellerCoinAmount + } + return 0 +} + +func (x *TransferPointToCoinSellerResponse) GetRatioPointAmount() int64 { + if x != nil { + return x.RatioPointAmount + } + return 0 +} + +func (x *TransferPointToCoinSellerResponse) GetRatioSellerCoinAmount() int64 { + if x != nil { + return x.RatioSellerCoinAmount + } + return 0 +} + +type GetPointWithdrawalConfigRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + RegionId int64 `protobuf:"varint,3,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` + NowMs int64 `protobuf:"varint,4,opt,name=now_ms,json=nowMs,proto3" json:"now_ms,omitempty"` +} + +func (x *GetPointWithdrawalConfigRequest) Reset() { + *x = GetPointWithdrawalConfigRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[289] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetPointWithdrawalConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPointWithdrawalConfigRequest) ProtoMessage() {} + +func (x *GetPointWithdrawalConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[289] + 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 GetPointWithdrawalConfigRequest.ProtoReflect.Descriptor instead. +func (*GetPointWithdrawalConfigRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{289} +} + +func (x *GetPointWithdrawalConfigRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *GetPointWithdrawalConfigRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *GetPointWithdrawalConfigRequest) GetRegionId() int64 { + if x != nil { + return x.RegionId + } + return 0 +} + +func (x *GetPointWithdrawalConfigRequest) GetNowMs() int64 { + if x != nil { + return x.NowMs + } + return 0 +} + +type GetPointWithdrawalConfigResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Found bool `protobuf:"varint,1,opt,name=found,proto3" json:"found,omitempty"` + PointsPerUsd int64 `protobuf:"varint,2,opt,name=points_per_usd,json=pointsPerUsd,proto3" json:"points_per_usd,omitempty"` + FeeBps int32 `protobuf:"varint,3,opt,name=fee_bps,json=feeBps,proto3" json:"fee_bps,omitempty"` + MinimumPoints int64 `protobuf:"varint,4,opt,name=minimum_points,json=minimumPoints,proto3" json:"minimum_points,omitempty"` + PolicyInstanceCode string `protobuf:"bytes,5,opt,name=policy_instance_code,json=policyInstanceCode,proto3" json:"policy_instance_code,omitempty"` +} + +func (x *GetPointWithdrawalConfigResponse) Reset() { + *x = GetPointWithdrawalConfigResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[290] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetPointWithdrawalConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPointWithdrawalConfigResponse) ProtoMessage() {} + +func (x *GetPointWithdrawalConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[290] + 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 GetPointWithdrawalConfigResponse.ProtoReflect.Descriptor instead. +func (*GetPointWithdrawalConfigResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{290} +} + +func (x *GetPointWithdrawalConfigResponse) GetFound() bool { + if x != nil { + return x.Found + } + return false +} + +func (x *GetPointWithdrawalConfigResponse) GetPointsPerUsd() int64 { + if x != nil { + return x.PointsPerUsd + } + return 0 +} + +func (x *GetPointWithdrawalConfigResponse) GetFeeBps() int32 { + if x != nil { + return x.FeeBps + } + return 0 +} + +func (x *GetPointWithdrawalConfigResponse) GetMinimumPoints() int64 { + if x != nil { + return x.MinimumPoints + } + return 0 +} + +func (x *GetPointWithdrawalConfigResponse) GetPolicyInstanceCode() string { + if x != nil { + return x.PolicyInstanceCode + } + return "" +} + var File_proto_wallet_v1_wallet_proto protoreflect.FileDescriptor var file_proto_wallet_v1_wallet_proto_rawDesc = []byte{ @@ -21870,7 +25717,7 @@ var file_proto_wallet_v1_wallet_proto_rawDesc = []byte{ 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x53, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x22, 0xd6, 0x09, 0x0a, 0x11, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, + 0x63, 0x65, 0x22, 0xff, 0x09, 0x0a, 0x11, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x63, @@ -21947,543 +25794,573 @@ var file_proto_wallet_v1_wallet_proto_rawDesc = []byte{ 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x68, 0x6f, 0x73, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xed, 0x01, 0x0a, 0x0f, - 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, - 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, - 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, - 0x73, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x49, 0x73, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x3c, 0x0a, - 0x1b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6f, - 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x17, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, - 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xbc, 0x03, 0x0a, 0x15, - 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x24, 0x0a, - 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x70, - 0x72, 0x69, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x69, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, - 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6e, 0x64, - 0x65, 0x72, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x49, 0x64, 0x12, 0x3a, 0x0a, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x18, 0x0a, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x54, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x12, 0x25, - 0x0a, 0x0e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, - 0x61, 0x72, 0x67, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x9a, 0x01, 0x0a, 0x15, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x63, - 0x65, 0x69, 0x70, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x07, 0x62, 0x69, 0x6c, - 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, - 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x07, - 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x22, 0x9e, 0x01, 0x0a, 0x16, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x40, 0x0a, 0x09, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x09, 0x61, 0x67, 0x67, 0x72, 0x65, - 0x67, 0x61, 0x74, 0x65, 0x12, 0x42, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, - 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x52, 0x08, - 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x73, 0x22, 0xda, 0x02, 0x0a, 0x15, 0x44, 0x65, 0x62, - 0x69, 0x74, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, - 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, - 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, - 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, - 0x0a, 0x0d, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x69, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, - 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, - 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x52, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xb2, 0x01, 0x0a, 0x0c, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, - 0x6c, 0x65, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x72, 0x6f, 0x7a, 0x65, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x66, 0x72, 0x6f, 0x7a, 0x65, 0x6e, 0x41, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, - 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x88, 0x01, 0x0a, 0x12, 0x47, - 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, - 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, - 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, - 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, - 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x50, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, - 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x08, - 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x08, 0x62, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x22, 0xaa, 0x02, 0x0a, 0x15, 0x48, 0x6f, 0x73, 0x74, - 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4c, 0x65, 0x76, 0x65, - 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x6e, 0x6f, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x07, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x4e, 0x6f, 0x12, 0x2b, 0x0a, 0x11, - 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, - 0x64, 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x31, 0x0a, 0x15, 0x68, 0x6f, 0x73, - 0x74, 0x5f, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, - 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x68, 0x6f, 0x73, 0x74, 0x53, 0x61, - 0x6c, 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x28, 0x0a, 0x10, - 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x69, 0x6e, - 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x35, 0x0a, 0x17, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, - 0x5f, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, - 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x53, - 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x22, 0xe9, 0x03, 0x0a, 0x10, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, - 0x61, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x6f, - 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x65, 0x74, 0x74, - 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x5f, 0x6d, - 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x73, 0x65, 0x74, 0x74, 0x6c, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x65, - 0x12, 0x3a, 0x0a, 0x1a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x74, 0x6f, - 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x54, 0x6f, - 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, 0x3e, 0x0a, 0x1c, - 0x72, 0x65, 0x73, 0x69, 0x64, 0x75, 0x61, 0x6c, 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, - 0x5f, 0x74, 0x6f, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x18, 0x72, 0x65, 0x73, 0x69, 0x64, 0x75, 0x61, 0x6c, 0x44, 0x69, 0x61, 0x6d, - 0x6f, 0x6e, 0x64, 0x54, 0x6f, 0x55, 0x73, 0x64, 0x52, 0x61, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x11, - 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x6d, - 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, - 0x76, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x66, 0x66, 0x65, - 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0d, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x6f, 0x4d, 0x73, - 0x12, 0x3e, 0x0a, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, - 0x22, 0xf1, 0x01, 0x0a, 0x20, 0x47, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x48, 0x6f, - 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, - 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x5f, 0x6d, 0x6f, 0x64, 0x65, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x15, 0x0a, - 0x06, 0x6e, 0x6f, 0x77, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6e, - 0x6f, 0x77, 0x4d, 0x73, 0x22, 0x74, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, - 0x65, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x75, - 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x12, - 0x39, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x9a, 0x02, 0x0a, 0x12, 0x48, - 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, - 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, 0x6b, 0x65, 0x79, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x4b, 0x65, 0x79, - 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2f, 0x0a, - 0x14, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x61, 0x67, 0x65, - 0x6e, 0x63, 0x79, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, - 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x73, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x44, 0x69, 0x61, - 0x6d, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x64, 0x69, - 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x10, 0x67, 0x69, 0x66, 0x74, 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x54, 0x6f, - 0x74, 0x61, 0x6c, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, - 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xae, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x48, - 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, - 0x64, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, 0x6b, 0x65, - 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x4b, 0x65, - 0x79, 0x12, 0x15, 0x0a, 0x06, 0x6e, 0x6f, 0x77, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x05, 0x6e, 0x6f, 0x77, 0x4d, 0x73, 0x22, 0x60, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x48, - 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x08, 0x70, 0x72, 0x6f, - 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, - 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, - 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x22, 0x91, 0x02, 0x0a, 0x17, 0x54, - 0x65, 0x61, 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x43, 0x79, 0x63, - 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x79, 0x63, 0x6c, 0x65, - 0x4b, 0x65, 0x79, 0x12, 0x44, 0x0a, 0x1f, 0x65, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x64, - 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1b, 0x65, 0x73, - 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, - 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x40, 0x0a, 0x1d, 0x73, 0x65, 0x74, - 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, - 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x19, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, - 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x61, - 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x48, 0x6f, - 0x73, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x73, 0x22, 0xc2, - 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x54, 0x65, 0x61, 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, - 0x6c, 0x61, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, - 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x31, 0x0a, 0x15, 0x61, 0x67, - 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x12, 0x61, 0x67, 0x65, 0x6e, 0x63, - 0x79, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x1d, 0x0a, - 0x0a, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x09, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x15, 0x0a, 0x06, - 0x6e, 0x6f, 0x77, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6e, 0x6f, - 0x77, 0x4d, 0x73, 0x22, 0x86, 0x01, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x61, 0x6d, 0x48, - 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x61, 0x6d, 0x48, 0x6f, 0x73, 0x74, - 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x52, - 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xad, 0x02, 0x0a, - 0x17, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x41, 0x73, 0x73, 0x65, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, + 0x6c, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x62, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x1c, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xed, 0x01, 0x0a, 0x0f, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, + 0x66, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, - 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, - 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, - 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, - 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x76, 0x69, 0x64, 0x65, 0x6e, - 0x63, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x76, - 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x66, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x22, 0x7a, 0x0a, 0x18, - 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, - 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, - 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x22, 0xf9, 0x03, 0x0a, 0x21, 0x41, 0x64, 0x6d, - 0x69, 0x6e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, - 0x65, 0x72, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, + 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, + 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x73, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x73, 0x48, + 0x6f, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x68, 0x6f, + 0x73, 0x74, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x12, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x17, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x22, 0xbc, 0x03, 0x0a, 0x15, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, + 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, - 0x0e, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x6f, 0x63, 0x6b, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x63, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x10, 0x70, 0x61, 0x69, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, - 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x70, 0x61, - 0x69, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x12, 0x1f, 0x0a, - 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x66, 0x12, 0x21, - 0x0a, 0x0c, 0x65, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, - 0x66, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, - 0x0a, 0x11, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, - 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x73, 0x65, 0x6c, 0x6c, 0x65, - 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, - 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0d, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x8f, 0x03, 0x0a, 0x22, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, - 0x65, 0x64, 0x69, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, - 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6c, 0x6c, - 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x6f, 0x63, - 0x6b, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, - 0x6f, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, - 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, - 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x61, 0x69, 0x64, - 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x61, 0x69, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x61, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0f, 0x70, 0x61, 0x69, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x69, 0x63, - 0x72, 0x6f, 0x12, 0x39, 0x0a, 0x19, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x5f, 0x61, 0x73, 0x5f, - 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x41, 0x73, 0x53, - 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x12, 0x23, 0x0a, - 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, - 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xd5, 0x02, 0x0a, 0x1d, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x66, 0x65, 0x72, 0x43, 0x6f, 0x69, 0x6e, 0x46, 0x72, 0x6f, 0x6d, 0x53, 0x65, 0x6c, 0x6c, 0x65, - 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6c, 0x6c, 0x65, - 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0c, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, + 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, + 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, + 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, + 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x69, + 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, + 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, + 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x3a, 0x0a, 0x07, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x07, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, + 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x22, 0x9a, 0x01, 0x0a, 0x15, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x62, + 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, - 0x0a, 0x10, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, - 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x22, 0x94, - 0x04, 0x0a, 0x1e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x6f, 0x69, 0x6e, 0x46, - 0x72, 0x6f, 0x6d, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x07, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x07, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, + 0x22, 0x9e, 0x01, 0x0a, 0x16, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, + 0x69, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x09, 0x61, + 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x52, 0x09, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x12, 0x42, 0x0a, + 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, + 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x52, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, + 0x73, 0x22, 0xda, 0x02, 0x0a, 0x15, 0x44, 0x65, 0x62, 0x69, 0x74, 0x52, 0x6f, 0x62, 0x6f, 0x74, + 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, + 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, + 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, + 0x6d, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, + 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x69, 0x63, 0x65, + 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x70, 0x72, 0x69, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, + 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x72, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, + 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xb2, + 0x01, 0x0a, 0x0c, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, + 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, + 0x0a, 0x10, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, + 0x62, 0x6c, 0x65, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x72, 0x6f, + 0x7a, 0x65, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0c, 0x66, 0x72, 0x6f, 0x7a, 0x65, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, + 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, + 0x6f, 0x64, 0x65, 0x22, 0x88, 0x01, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x50, + 0x0a, 0x13, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x08, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x08, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, + 0x22, 0xaa, 0x02, 0x0a, 0x15, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x65, + 0x76, 0x65, 0x6c, 0x5f, 0x6e, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x6c, 0x65, + 0x76, 0x65, 0x6c, 0x4e, 0x6f, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, + 0x64, 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x10, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, + 0x64, 0x73, 0x12, 0x31, 0x0a, 0x15, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x61, 0x6c, 0x61, 0x72, + 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x12, 0x68, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, + 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x6f, + 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0e, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, + 0x35, 0x0a, 0x17, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, + 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x14, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, 0x73, + 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, + 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22, 0xe9, 0x03, + 0x0a, 0x10, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x64, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x74, 0x74, + 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x64, + 0x65, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, + 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x15, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x72, + 0x69, 0x67, 0x67, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x3a, 0x0a, 0x1a, 0x67, 0x69, 0x66, + 0x74, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x74, 0x6f, 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, + 0x64, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x67, + 0x69, 0x66, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x54, 0x6f, 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, + 0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, 0x3e, 0x0a, 0x1c, 0x72, 0x65, 0x73, 0x69, 0x64, 0x75, 0x61, + 0x6c, 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x75, 0x73, 0x64, + 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x72, 0x65, 0x73, + 0x69, 0x64, 0x75, 0x61, 0x6c, 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x54, 0x6f, 0x55, 0x73, + 0x64, 0x52, 0x61, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x4d, + 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x74, + 0x6f, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x65, 0x66, 0x66, 0x65, + 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x6f, 0x4d, 0x73, 0x12, 0x3e, 0x0a, 0x06, 0x6c, 0x65, 0x76, + 0x65, 0x6c, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, + 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x52, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x22, 0xf1, 0x01, 0x0a, 0x20, 0x47, 0x65, + 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, + 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, + 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x36, + 0x0a, 0x17, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x72, 0x69, + 0x67, 0x67, 0x65, 0x72, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x15, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x72, 0x69, 0x67, 0x67, + 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6e, 0x6f, 0x77, 0x5f, 0x6d, 0x73, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6e, 0x6f, 0x77, 0x4d, 0x73, 0x22, 0x74, 0x0a, + 0x21, 0x47, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, + 0x6c, 0x61, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x05, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x39, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x53, + 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x06, 0x70, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x22, 0x9a, 0x02, 0x0a, 0x12, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, + 0x72, 0x79, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x6f, + 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, + 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, + 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x77, 0x6e, 0x65, + 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x2c, + 0x0a, 0x12, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x5f, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, 0x69, 0x66, 0x74, + 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x22, 0x0a, 0x0d, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, + 0x22, 0xae, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, + 0x72, 0x79, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x68, + 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, + 0x09, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x15, 0x0a, 0x06, 0x6e, 0x6f, + 0x77, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6e, 0x6f, 0x77, 0x4d, + 0x73, 0x22, 0x60, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, + 0x72, 0x79, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, + 0x79, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, + 0x65, 0x73, 0x73, 0x22, 0x91, 0x02, 0x0a, 0x17, 0x54, 0x65, 0x61, 0x6d, 0x48, 0x6f, 0x73, 0x74, + 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x12, + 0x1b, 0x0a, 0x09, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x44, 0x0a, 0x1f, + 0x65, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, + 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1b, 0x65, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, + 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, + 0x6f, 0x72, 0x12, 0x40, 0x0a, 0x1d, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x68, 0x6f, + 0x73, 0x74, 0x5f, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, + 0x6e, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x19, 0x73, 0x65, 0x74, 0x74, 0x6c, + 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, 0x4d, + 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x68, + 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, + 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x44, + 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x73, 0x22, 0xc2, 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x54, + 0x65, 0x61, 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x53, 0x74, 0x61, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, + 0x6f, 0x64, 0x65, 0x12, 0x31, 0x0a, 0x15, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x03, 0x52, 0x12, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, + 0x6b, 0x65, 0x79, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x63, 0x79, 0x63, 0x6c, + 0x65, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x6e, 0x6f, 0x77, 0x5f, 0x6d, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6e, 0x6f, 0x77, 0x4d, 0x73, 0x22, 0x86, 0x01, 0x0a, + 0x1e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x61, 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, + 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3e, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x54, 0x65, 0x61, 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x43, + 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x12, + 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, + 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xad, 0x02, 0x0a, 0x17, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, + 0x72, 0x65, 0x64, 0x69, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, + 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, + 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, + 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, + 0x21, 0x0a, 0x0c, 0x65, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x52, + 0x65, 0x66, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, + 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x22, 0x7a, 0x0a, 0x18, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, + 0x65, 0x64, 0x69, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x65, 0x6c, 0x6c, - 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x42, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x14, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, - 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x22, 0xf9, 0x03, 0x0a, 0x21, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x64, 0x69, + 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x6f, 0x63, 0x6b, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x73, 0x74, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x73, 0x74, 0x6f, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, + 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, + 0x70, 0x61, 0x69, 0x64, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x61, 0x69, 0x64, 0x43, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x61, + 0x69, 0x64, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x70, 0x61, 0x69, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x66, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x76, 0x69, 0x64, 0x65, + 0x6e, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, + 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x66, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, + 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x65, 0x6c, 0x6c, 0x65, + 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, + 0x79, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x72, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, + 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x8f, 0x03, + 0x0a, 0x22, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x6f, 0x69, + 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, + 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x6f, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, + 0x61, 0x69, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x2a, 0x0a, 0x11, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, + 0x69, 0x63, 0x72, 0x6f, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x70, 0x61, 0x69, 0x64, + 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x12, 0x39, 0x0a, 0x19, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x5f, 0x61, 0x73, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, + 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x41, 0x73, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, + 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x62, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, + 0xd5, 0x02, 0x0a, 0x1d, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x6f, 0x69, 0x6e, + 0x46, 0x72, 0x6f, 0x6d, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, + 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, - 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x10, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, - 0x6f, 0x72, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x63, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x43, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x72, 0x65, 0x63, 0x68, - 0x61, 0x72, 0x67, 0x65, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, - 0x67, 0x65, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, - 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3d, - 0x0a, 0x1b, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x18, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x46, 0x0a, - 0x20, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1c, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, - 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x41, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xf8, 0x01, 0x0a, 0x25, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, - 0x65, 0x72, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x6f, 0x63, - 0x6b, 0x54, 0x6f, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, - 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x68, 0x69, - 0x6c, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, + 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6c, 0x6c, 0x65, + 0x72, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0e, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, + 0x64, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x22, 0x94, 0x04, 0x0a, 0x1e, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x66, 0x65, 0x72, 0x43, 0x6f, 0x69, 0x6e, 0x46, 0x72, 0x6f, 0x6d, 0x53, 0x65, 0x6c, 0x6c, + 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, + 0x64, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x12, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, + 0x74, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x14, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x62, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x12, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, + 0x12, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, + 0x6e, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x72, 0x65, 0x63, 0x68, 0x61, + 0x72, 0x67, 0x65, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x34, 0x0a, 0x16, 0x72, + 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, + 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x63, + 0x68, 0x61, 0x72, 0x67, 0x65, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x70, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x72, + 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x64, 0x12, + 0x36, 0x0a, 0x17, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x15, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x1b, 0x72, 0x65, 0x63, 0x68, 0x61, + 0x72, 0x67, 0x65, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x72, 0x65, + 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x43, 0x6f, 0x69, 0x6e, + 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x46, 0x0a, 0x20, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, + 0x6e, 0x6f, 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x1c, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xf8, + 0x01, 0x0a, 0x25, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x6f, 0x69, 0x6e, 0x53, + 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x54, 0x6f, 0x43, 0x68, 0x69, 0x6c, + 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, + 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0c, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, + 0x0d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, + 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xed, 0x01, 0x0a, 0x26, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, + 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x54, 0x6f, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x70, + 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, + 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x70, 0x61, 0x72, 0x65, 0x6e, + 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x2e, 0x0a, + 0x13, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, + 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x63, 0x68, 0x69, 0x6c, + 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x16, 0x0a, + 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x84, 0x02, 0x0a, 0x20, 0x43, 0x6f, + 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x45, 0x78, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x54, 0x69, 0x65, 0x72, 0x12, 0x1b, + 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6d, + 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, + 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x55, 0x73, 0x64, 0x4d, 0x69, + 0x6e, 0x6f, 0x72, 0x12, 0x20, 0x0a, 0x0c, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x70, 0x65, 0x72, 0x5f, + 0x75, 0x73, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x50, + 0x65, 0x72, 0x55, 0x73, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, + 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, + 0x22, 0xb0, 0x01, 0x0a, 0x2c, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, + 0x6c, 0x65, 0x72, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x52, 0x61, 0x74, 0x65, 0x54, 0x69, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, + 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, + 0x75, 0x64, 0x65, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x44, 0x69, 0x73, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x22, 0x78, 0x0a, 0x2d, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, + 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x54, 0x69, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x05, 0x74, 0x69, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, + 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, + 0x74, 0x65, 0x54, 0x69, 0x65, 0x72, 0x52, 0x05, 0x74, 0x69, 0x65, 0x72, 0x73, 0x22, 0xde, 0x01, + 0x0a, 0x1b, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, + 0x54, 0x6f, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, + 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0f, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, + 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x61, 0x6c, + 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x92, + 0x02, 0x0a, 0x1c, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, + 0x79, 0x54, 0x6f, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, + 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x42, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x6f, 0x69, 0x6e, + 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x69, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, + 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0e, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, + 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x75, 0x73, + 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x65, 0x72, + 0x55, 0x73, 0x64, 0x22, 0xb4, 0x02, 0x0a, 0x21, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, + 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x54, 0x6f, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, + 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, + 0x0a, 0x0e, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x61, + 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0f, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x28, 0x0a, 0x10, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, + 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x61, 0x6c, 0x61, + 0x72, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, + 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x83, 0x03, 0x0a, 0x22, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x54, 0x6f, 0x43, + 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x3d, 0x0a, 0x1b, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x5f, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x42, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x65, 0x6c, 0x6c, 0x65, + 0x72, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x42, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x61, 0x6c, + 0x61, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, + 0x6e, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x70, 0x65, 0x72, + 0x5f, 0x75, 0x73, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, + 0x50, 0x65, 0x72, 0x55, 0x73, 0x64, 0x12, 0x2b, 0x0a, 0x12, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x6d, + 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0f, 0x72, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x6e, 0x55, 0x73, 0x64, 0x4d, 0x69, + 0x6e, 0x6f, 0x72, 0x12, 0x2b, 0x0a, 0x12, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, + 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0f, 0x72, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x78, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, + 0x22, 0x87, 0x02, 0x0a, 0x1d, 0x46, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, + 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, + 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x61, + 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, + 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0e, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, - 0x22, 0xed, 0x01, 0x0a, 0x26, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x6f, 0x69, - 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x54, 0x6f, 0x43, 0x68, - 0x69, 0x6c, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x12, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, - 0x66, 0x74, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x62, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x11, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, - 0x66, 0x74, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, - 0x22, 0x84, 0x02, 0x0a, 0x20, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, - 0x61, 0x6c, 0x61, 0x72, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, - 0x65, 0x54, 0x69, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, - 0x6e, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x55, 0x73, - 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x73, - 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6d, - 0x61, 0x78, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x20, 0x0a, 0x0c, 0x63, 0x6f, - 0x69, 0x6e, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x65, 0x72, 0x55, 0x73, 0x64, 0x12, 0x16, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, - 0x64, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, - 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xb0, 0x01, 0x0a, 0x2c, 0x4c, 0x69, 0x73, 0x74, - 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, - 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x54, 0x69, 0x65, 0x72, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, - 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, - 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, - 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, - 0x64, 0x65, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x78, 0x0a, 0x2d, 0x4c, 0x69, - 0x73, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x61, 0x6c, 0x61, - 0x72, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x54, 0x69, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x05, 0x74, - 0x69, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x69, - 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x45, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x54, 0x69, 0x65, 0x72, 0x52, 0x05, 0x74, - 0x69, 0x65, 0x72, 0x73, 0x22, 0xde, 0x01, 0x0a, 0x1b, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x54, 0x6f, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, - 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x41, - 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x61, 0x6c, 0x61, - 0x72, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0e, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, - 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, - 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, - 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x92, 0x02, 0x0a, 0x1c, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x54, 0x6f, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, - 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x30, 0x0a, - 0x14, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, - 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x73, 0x61, 0x6c, - 0x61, 0x72, 0x79, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, - 0x2c, 0x0a, 0x12, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, - 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x69, - 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x28, 0x0a, + 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, + 0x6c, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x69, 0x74, + 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x66, 0x22, 0xaa, 0x01, 0x0a, 0x1e, 0x46, + 0x72, 0x65, 0x65, 0x7a, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, + 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, + 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, - 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, - 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, - 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, - 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x63, 0x6f, 0x69, 0x6e, - 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, - 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x65, 0x72, 0x55, 0x73, 0x64, 0x22, 0xb4, 0x02, 0x0a, 0x21, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x54, 0x6f, 0x43, - 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, - 0x24, 0x0a, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, - 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x73, - 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x41, 0x73, - 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x61, 0x6c, 0x61, 0x72, - 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0e, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, - 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x16, - 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, - 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, - 0x65, 0x22, 0x83, 0x03, 0x0a, 0x22, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x53, 0x61, - 0x6c, 0x61, 0x72, 0x79, 0x54, 0x6f, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, - 0x3d, 0x0a, 0x1b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, - 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x61, 0x6c, 0x61, - 0x72, 0x79, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x30, - 0x0a, 0x14, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, - 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x73, 0x65, - 0x6c, 0x6c, 0x65, 0x72, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, - 0x12, 0x28, 0x0a, 0x10, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, - 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x61, 0x6c, 0x61, - 0x72, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, - 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x63, - 0x6f, 0x69, 0x6e, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x65, 0x72, 0x55, 0x73, 0x64, 0x12, 0x2b, 0x0a, - 0x12, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, - 0x6e, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x61, 0x74, 0x65, 0x4d, - 0x69, 0x6e, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x2b, 0x0a, 0x12, 0x72, 0x61, - 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x78, 0x55, - 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x22, 0x87, 0x02, 0x0a, 0x1d, 0x46, 0x72, 0x65, 0x65, - 0x7a, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, + 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, + 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x22, 0xc6, 0x02, 0x0a, 0x1d, 0x53, 0x65, 0x74, 0x74, + 0x6c, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, @@ -22493,24 +26370,28 @@ var file_proto_wallet_v1_wallet_proto_rawDesc = []byte{ 0x6c, 0x61, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, - 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, - 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x69, - 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, - 0x66, 0x22, 0xaa, 0x01, 0x0a, 0x1e, 0x46, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x53, 0x61, 0x6c, 0x61, - 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, 0x62, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, - 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x75, - 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, - 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x22, 0xc6, - 0x02, 0x0a, 0x1d, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, + 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, + 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x3a, 0x0a, 0x19, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, + 0x61, 0x6c, 0x5f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, + 0x77, 0x61, 0x6c, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x22, 0xaa, 0x01, 0x0a, 0x1e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, + 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x73, + 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, + 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x22, 0xc7, 0x02, + 0x0a, 0x1e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, @@ -22530,124 +26411,142 @@ var file_proto_wallet_v1_wallet_proto_rawDesc = []byte{ 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x5f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xaa, 0x01, 0x0a, 0x1e, 0x53, 0x65, 0x74, 0x74, - 0x6c, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xab, 0x01, 0x0a, 0x1f, 0x52, 0x65, 0x6c, 0x65, + 0x61, 0x73, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, + 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x73, + 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, + 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x22, 0x90, 0x03, 0x0a, 0x1c, 0x46, 0x72, 0x65, 0x65, 0x7a, 0x65, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, + 0x12, 0x67, 0x72, 0x6f, 0x73, 0x73, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, 0x72, 0x6f, 0x73, 0x73, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, + 0x65, 0x65, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x65, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x65, 0x74, 0x5f, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0e, 0x6e, 0x65, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x24, 0x0a, 0x0e, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x75, 0x73, + 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x50, + 0x65, 0x72, 0x55, 0x73, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65, 0x5f, 0x62, 0x70, 0x73, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x66, 0x65, 0x65, 0x42, 0x70, 0x73, 0x12, 0x16, + 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x5f, + 0x72, 0x65, 0x66, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x69, 0x74, 0x68, 0x64, + 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x66, 0x22, 0xc0, 0x02, 0x0a, 0x1d, 0x46, 0x72, 0x65, + 0x65, 0x7a, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x61, - 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, 0x4d, - 0x69, 0x6e, 0x6f, 0x72, 0x22, 0xc7, 0x02, 0x0a, 0x1e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, - 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x2a, 0x0a, 0x11, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x61, 0x6c, 0x61, - 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x73, - 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, - 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, - 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, - 0x64, 0x65, 0x12, 0x3a, 0x0a, 0x19, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, - 0x5f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, - 0x6c, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xab, - 0x01, 0x0a, 0x1f, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, - 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, - 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x64, - 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x61, - 0x6c, 0x61, 0x72, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x22, 0x90, 0x03, 0x0a, - 0x1c, 0x46, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, - 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, - 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x72, 0x6f, 0x73, 0x73, 0x5f, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x10, 0x67, 0x72, 0x6f, 0x73, 0x73, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, - 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x65, - 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, - 0x6e, 0x65, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6e, 0x65, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, - 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, - 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x50, 0x65, 0x72, 0x55, 0x73, 0x64, 0x12, 0x17, 0x0a, 0x07, - 0x66, 0x65, 0x65, 0x5f, 0x62, 0x70, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x66, - 0x65, 0x65, 0x42, 0x70, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, - 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x69, 0x74, 0x68, - 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0d, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x66, 0x22, - 0xc0, 0x02, 0x0a, 0x1d, 0x46, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, - 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, - 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, - 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x72, 0x6f, 0x73, 0x73, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, - 0x72, 0x6f, 0x73, 0x73, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x28, 0x0a, 0x10, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x65, 0x65, 0x50, 0x6f, - 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x65, 0x74, - 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6e, 0x65, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x5f, 0x70, 0x65, - 0x72, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x73, 0x50, 0x65, 0x72, 0x55, 0x73, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65, - 0x5f, 0x62, 0x70, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x66, 0x65, 0x65, 0x42, - 0x70, 0x73, 0x22, 0xcf, 0x03, 0x0a, 0x1c, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x50, 0x6f, 0x69, - 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, - 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x72, + 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x72, 0x6f, 0x73, 0x73, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, 0x72, 0x6f, 0x73, 0x73, 0x50, 0x6f, 0x69, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, 0x72, 0x6f, 0x73, 0x73, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x65, 0x65, 0x5f, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x65, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x65, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, - 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6e, 0x65, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6e, 0x65, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x07, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x50, 0x65, 0x72, 0x55, - 0x73, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65, 0x5f, 0x62, 0x70, 0x73, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x06, 0x66, 0x65, 0x65, 0x42, 0x70, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, - 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x3a, 0x0a, 0x19, 0x77, 0x69, 0x74, 0x68, - 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x5f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x69, 0x74, - 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xc0, 0x02, 0x0a, 0x1d, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x50, + 0x73, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65, 0x5f, 0x62, 0x70, 0x73, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x06, 0x66, 0x65, 0x65, 0x42, 0x70, 0x73, 0x22, 0xcf, 0x03, 0x0a, 0x1c, + 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, + 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x72, 0x6f, 0x73, 0x73, 0x5f, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x10, 0x67, 0x72, 0x6f, 0x73, 0x73, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x65, 0x65, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6e, + 0x65, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6e, 0x65, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x5f, + 0x70, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x50, 0x65, 0x72, 0x55, 0x73, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x66, + 0x65, 0x65, 0x5f, 0x62, 0x70, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x66, 0x65, + 0x65, 0x42, 0x70, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x3a, 0x0a, 0x19, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x5f, + 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, + 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xc0, 0x02, + 0x0a, 0x1d, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, + 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, + 0x2c, 0x0a, 0x12, 0x67, 0x72, 0x6f, 0x73, 0x73, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, 0x72, 0x6f, + 0x73, 0x73, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, + 0x10, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x65, 0x65, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x65, 0x74, 0x5f, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0e, 0x6e, 0x65, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, + 0x75, 0x73, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x73, 0x50, 0x65, 0x72, 0x55, 0x73, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65, 0x5f, 0x62, + 0x70, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x66, 0x65, 0x65, 0x42, 0x70, 0x73, + 0x22, 0xd0, 0x03, 0x0a, 0x1d, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, + 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x72, 0x6f, + 0x73, 0x73, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, 0x72, 0x6f, 0x73, 0x73, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x65, 0x65, 0x5f, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0e, 0x66, 0x65, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x65, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6e, 0x65, 0x74, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x50, 0x65, 0x72, 0x55, 0x73, + 0x64, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65, 0x5f, 0x62, 0x70, 0x73, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x06, 0x66, 0x65, 0x65, 0x42, 0x70, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, + 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x3a, 0x0a, 0x19, 0x77, 0x69, 0x74, 0x68, 0x64, + 0x72, 0x61, 0x77, 0x61, 0x6c, 0x5f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x69, 0x74, 0x68, + 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x22, 0xc1, 0x02, 0x0a, 0x1e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, @@ -22667,2314 +26566,2363 @@ var file_proto_wallet_v1_wallet_proto_rawDesc = []byte{ 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x50, 0x65, 0x72, 0x55, 0x73, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65, 0x5f, 0x62, 0x70, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x06, 0x66, 0x65, 0x65, 0x42, 0x70, 0x73, 0x22, 0xd0, 0x03, 0x0a, 0x1d, 0x52, 0x65, 0x6c, 0x65, - 0x61, 0x73, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, - 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x72, 0x6f, 0x73, 0x73, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, - 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, 0x72, - 0x6f, 0x73, 0x73, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, - 0x0a, 0x10, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x65, 0x65, 0x50, 0x6f, 0x69, - 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x65, 0x74, 0x5f, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0e, 0x6e, 0x65, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, - 0x5f, 0x75, 0x73, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x73, 0x50, 0x65, 0x72, 0x55, 0x73, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65, 0x5f, - 0x62, 0x70, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x66, 0x65, 0x65, 0x42, 0x70, - 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x3a, - 0x0a, 0x19, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x5f, 0x61, 0x70, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x17, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x41, 0x70, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xc1, 0x02, 0x0a, 0x1e, 0x52, - 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, - 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, - 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2c, 0x0a, - 0x12, 0x67, 0x72, 0x6f, 0x73, 0x73, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, 0x72, 0x6f, 0x73, 0x73, - 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, - 0x65, 0x65, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x66, 0x65, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x65, 0x74, 0x5f, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0e, 0x6e, 0x65, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x24, 0x0a, 0x0e, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x75, 0x73, - 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x50, - 0x65, 0x72, 0x55, 0x73, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65, 0x5f, 0x62, 0x70, 0x73, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x66, 0x65, 0x65, 0x42, 0x70, 0x73, 0x22, 0xe7, - 0x06, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x61, - 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, - 0x09, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x09, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x67, - 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, - 0x67, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2e, - 0x0a, 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, - 0x0a, 0x0c, 0x75, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x18, 0x0b, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x75, 0x73, 0x61, 0x67, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, - 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0c, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1f, - 0x0a, 0x0b, 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0d, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x55, 0x72, 0x6c, 0x12, - 0x23, 0x0a, 0x0d, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, - 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, - 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x11, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x12, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x5f, 0x6d, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x65, 0x6e, 0x61, 0x62, - 0x6c, 0x65, 0x64, 0x18, 0x15, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x72, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1d, - 0x0a, 0x0a, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x16, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, - 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x11, - 0x67, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x18, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x69, - 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc7, 0x03, 0x0a, 0x11, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x22, - 0x0a, 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, - 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x1f, 0x0a, - 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x35, - 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, - 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x74, 0x65, - 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x74, - 0x65, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x11, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x22, 0xad, 0x03, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, - 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x06, 0x66, 0x65, 0x65, 0x42, 0x70, 0x73, 0x22, 0xe7, 0x06, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, - 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, - 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, - 0x12, 0x2b, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, - 0x12, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, - 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x4d, 0x73, 0x22, 0xd3, 0x06, 0x0a, 0x0a, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, - 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, - 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, - 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, - 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x72, 0x65, 0x73, - 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, - 0x69, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, - 0x69, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, - 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x69, 0x66, - 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x68, 0x65, 0x61, 0x74, 0x5f, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x68, 0x65, 0x61, 0x74, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2b, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x2b, 0x0a, 0x12, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, - 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x5f, 0x6d, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x64, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x67, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x67, - 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x63, - 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x41, 0x73, - 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, - 0x74, 0x69, 0x76, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x6d, 0x73, 0x18, 0x14, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x72, 0x6f, - 0x6d, 0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, - 0x5f, 0x74, 0x6f, 0x5f, 0x6d, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x65, 0x66, - 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x6f, 0x4d, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x65, - 0x66, 0x66, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x16, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x0b, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x28, - 0x0a, 0x10, 0x63, 0x70, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x70, 0x52, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0xce, 0x02, 0x0a, 0x0e, 0x47, 0x69, 0x66, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, - 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x79, 0x70, 0x65, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x62, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x62, 0x4b, 0x65, 0x79, - 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, - 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, - 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x12, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, - 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xb7, 0x04, 0x0a, 0x10, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x19, - 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x73, 0x68, 0x6f, - 0x70, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0a, 0x73, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x08, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x64, - 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x79, 0x73, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x0c, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x79, 0x73, - 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x2a, - 0x0a, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, - 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, - 0x74, 0x69, 0x76, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x66, - 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0d, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x6f, - 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, + 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, + 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x61, + 0x62, 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x67, 0x72, 0x61, 0x6e, 0x74, + 0x61, 0x62, 0x6c, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x74, + 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x67, 0x72, + 0x61, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x75, 0x73, 0x61, 0x67, 0x65, + 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x75, + 0x73, 0x61, 0x67, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, + 0x73, 0x73, 0x65, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x65, 0x76, 0x69, + 0x65, 0x77, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, + 0x65, 0x76, 0x69, 0x65, 0x77, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6e, 0x69, 0x6d, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, + 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x0f, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4a, 0x73, + 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x12, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x75, 0x70, 0x64, 0x61, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, - 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, - 0x74, 0x4d, 0x73, 0x22, 0x87, 0x04, 0x0a, 0x17, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, - 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, - 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, - 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x08, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, - 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x71, 0x75, - 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x2d, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, - 0x69, 0x6e, 0x67, 0x5f, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x11, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x51, 0x75, 0x61, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, - 0x76, 0x65, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, - 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, - 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, - 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x72, 0x61, 0x6e, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, - 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, - 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x18, 0x0e, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x22, 0x86, 0x03, - 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x49, - 0x74, 0x65, 0x6d, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x74, 0x65, - 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, - 0x74, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x61, 0x6e, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x61, 0x6e, 0x74, - 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x49, 0x64, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, - 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x6e, 0x61, - 0x70, 0x73, 0x68, 0x6f, 0x74, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x71, 0x75, 0x61, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xd7, 0x04, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1d, - 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, - 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, - 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, - 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x10, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, - 0x67, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x28, - 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, - 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x4d, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x67, + 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x15, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x13, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x47, 0x72, 0x61, 0x6e, 0x74, + 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x69, 0x63, 0x65, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x69, + 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x70, + 0x72, 0x69, 0x63, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, + 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x18, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x22, 0xc7, 0x03, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, + 0x6f, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, + 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0a, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, + 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, + 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x4d, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x74, 0x65, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x2a, 0x0a, 0x11, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xad, 0x03, 0x0a, 0x0d, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, + 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x43, 0x6f, + 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x20, + 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, + 0x38, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x74, + 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x12, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xd3, 0x06, 0x0a, 0x0a, + 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, + 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, + 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1f, + 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, + 0x35, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x72, + 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x23, + 0x0a, 0x0d, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x69, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x63, + 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, + 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x67, + 0x69, 0x66, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x68, 0x65, 0x61, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x09, 0x68, 0x65, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2b, 0x0a, + 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x12, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, + 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x10, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, + 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x11, 0x20, + 0x03, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x12, 0x24, + 0x0a, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x67, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x61, + 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0f, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x66, 0x72, + 0x6f, 0x6d, 0x5f, 0x6d, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x65, 0x66, 0x66, + 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f, + 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x6d, 0x73, 0x18, + 0x15, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, + 0x54, 0x6f, 0x4d, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x18, 0x16, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x66, 0x66, 0x65, + 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x70, 0x5f, 0x72, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x63, 0x70, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, + 0x65, 0x22, 0xce, 0x02, 0x0a, 0x0e, 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x74, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x74, 0x61, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x12, 0x2b, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, + 0x12, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, + 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x4d, 0x73, 0x22, 0xb7, 0x04, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, + 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, + 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, + 0x64, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x73, 0x68, 0x6f, 0x70, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x68, 0x6f, 0x70, 0x49, 0x74, + 0x65, 0x6d, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, - 0x6d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x64, 0x61, 0x79, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x64, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x79, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x69, + 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, + 0x72, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, + 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, + 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, + 0x74, 0x69, 0x76, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x72, 0x6f, + 0x6d, 0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, + 0x5f, 0x74, 0x6f, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x65, 0x66, + 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x6f, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, + 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x12, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, + 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x12, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x87, 0x04, 0x0a, + 0x17, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, + 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x74, + 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, + 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, + 0x2d, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x71, 0x75, 0x61, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x72, 0x65, 0x6d, + 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x26, + 0x0a, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x61, 0x74, 0x5f, 0x6d, + 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, + 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, + 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x65, - 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2b, - 0x0a, 0x12, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x76, 0x6f, - 0x6b, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, - 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x22, 0x8e, 0x02, 0x0a, 0x16, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, - 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, - 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, - 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, - 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, - 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, - 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x74, 0x65, 0x6d, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x74, 0x65, - 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, - 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, - 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x22, 0xa7, 0x02, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, - 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, - 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, - 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x2c, 0x0a, - 0x12, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x6f, - 0x6e, 0x6c, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x72, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x66, 0x0a, 0x15, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x14, 0x0a, - 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x22, 0x6f, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x49, 0x64, 0x22, 0x4c, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x22, 0x99, 0x06, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, - 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, - 0x67, 0x72, 0x61, 0x6e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x09, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x67, 0x72, - 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, - 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, - 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, - 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, - 0x0c, 0x75, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x18, 0x0b, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x0b, 0x75, 0x73, 0x61, 0x67, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, - 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0c, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, - 0x0b, 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0d, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x55, 0x72, 0x6c, 0x12, 0x23, - 0x0a, 0x0d, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, - 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, - 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, - 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, - 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x71, + 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x71, + 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x22, 0x86, 0x03, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x22, 0x0a, 0x0d, + 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, + 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x34, 0x0a, 0x16, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, + 0x74, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x4a, 0x73, + 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1f, + 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, + 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x32, 0x0a, 0x15, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, + 0xd7, 0x04, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, + 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, + 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x67, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, + 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0x2c, 0x0a, 0x12, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x67, 0x72, 0x61, + 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, + 0x10, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x37, 0x0a, 0x15, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x61, - 0x6e, 0x74, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, - 0x48, 0x00, 0x52, 0x13, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x47, 0x72, 0x61, 0x6e, 0x74, - 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, - 0x69, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x70, 0x72, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, - 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, - 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x69, 0x66, 0x74, - 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x15, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x18, 0x0a, 0x16, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, - 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0xba, - 0x06, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x64, 0x12, 0x38, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, + 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, + 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x65, 0x76, 0x6f, + 0x6b, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x72, 0x65, 0x76, 0x6f, 0x6b, + 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x5f, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x76, + 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x8e, 0x02, 0x0a, 0x16, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x49, + 0x6e, 0x70, 0x75, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x74, 0x65, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2a, + 0x0a, 0x11, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xa7, 0x02, 0x0a, 0x14, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, + 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, + 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, + 0x77, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, + 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, + 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x72, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x10, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x47, 0x72, 0x61, 0x6e, 0x74, + 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x66, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, + 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x6f, 0x0a, 0x12, + 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, + 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x22, 0x4c, 0x0a, + 0x13, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x99, 0x06, 0x0a, 0x15, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x61, + 0x62, 0x6c, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x67, 0x72, 0x61, + 0x6e, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x11, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x75, 0x73, 0x61, 0x67, 0x65, 0x5f, + 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x75, 0x73, + 0x61, 0x67, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, + 0x77, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x65, + 0x76, 0x69, 0x65, 0x77, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6e, 0x69, 0x6d, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x0f, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4a, 0x73, 0x6f, + 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, + 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x15, 0x6d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x65, 0x6e, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x13, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x72, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x69, 0x63, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, + 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, + 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x15, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x67, 0x69, + 0x66, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x18, 0x0a, + 0x16, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, + 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0xba, 0x06, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x12, 0x25, 0x0a, 0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, + 0x67, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x53, + 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x11, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x75, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x63, 0x6f, + 0x70, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x75, 0x73, 0x61, 0x67, 0x65, + 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, + 0x75, 0x72, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, + 0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x75, + 0x72, 0x6c, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, + 0x77, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x6e, 0x69, + 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x1d, + 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x11, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x28, 0x0a, + 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, + 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x15, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x72, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x13, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x72, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x88, 0x01, 0x01, + 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x14, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x15, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x2a, + 0x0a, 0x11, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x16, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x50, + 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x18, 0x0a, 0x16, 0x5f, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x65, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x22, 0xb7, 0x01, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x9c, + 0x01, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x67, 0x72, 0x61, - 0x6e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x67, 0x72, - 0x61, 0x6e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74, - 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x2a, - 0x0a, 0x11, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, - 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x75, 0x73, - 0x61, 0x67, 0x65, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x0b, 0x75, 0x73, 0x61, 0x67, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x1b, 0x0a, - 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, - 0x65, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x61, - 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0f, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, 0x6c, - 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6a, 0x73, 0x6f, - 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, - 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x37, - 0x0a, 0x15, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, - 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, - 0x13, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x45, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x69, 0x63, 0x65, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x69, - 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x70, - 0x72, 0x69, 0x63, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, - 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x16, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x42, 0x18, 0x0a, 0x16, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x67, 0x72, - 0x61, 0x6e, 0x74, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0xb7, 0x01, 0x0a, 0x18, - 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, - 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x9c, 0x01, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, - 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x22, 0xa4, 0x01, 0x0a, 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x21, - 0x0a, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, - 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x57, 0x0a, 0x1c, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x09, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x22, 0x49, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, - 0xd9, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, + 0x65, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xa4, 0x01, + 0x0a, 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, - 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x0a, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x6a, 0x0a, 0x1a, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x06, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, - 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x6e, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, - 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x50, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, - 0x75, 0x70, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x22, 0xcb, 0x02, 0x0a, 0x1a, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, - 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, - 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x43, 0x6f, 0x64, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x20, 0x0a, - 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x3d, - 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x74, 0x65, - 0x6d, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x28, 0x0a, - 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, - 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xe6, 0x02, 0x0a, 0x1a, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, + 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0b, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x22, 0x57, 0x0a, 0x1c, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x22, 0x49, 0x0a, + 0x10, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x35, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0xd9, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, - 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, - 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, + 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, + 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, + 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, + 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, + 0x6c, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, + 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x6a, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x36, 0x0a, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x52, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x22, 0x6e, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, + 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, + 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, + 0x22, 0x50, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x05, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x05, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x22, 0xcb, 0x02, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, - 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, + 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, - 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, + 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x22, 0xb6, 0x01, 0x0a, 0x1d, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, - 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x22, 0xe6, 0x02, 0x0a, 0x1a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, + 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x43, + 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x12, 0x3d, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, + 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x4d, 0x0a, 0x15, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x34, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, - 0x70, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x22, 0xbe, 0x02, 0x0a, 0x16, 0x4c, 0x69, 0x73, - 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, - 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, - 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, - 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, - 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23, - 0x0a, 0x0d, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x67, 0x69, 0x66, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x62, 0x0a, 0x17, 0x4c, 0x69, 0x73, - 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x05, 0x67, 0x69, 0x66, 0x74, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x05, 0x67, 0x69, 0x66, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x8f, 0x01, - 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, + 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xb6, 0x01, 0x0a, 0x1d, 0x53, 0x65, + 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, + 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, + 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x22, 0x4d, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, + 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x05, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x22, 0xbe, 0x02, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, - 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x22, - 0x5d, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, - 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x22, 0x82, - 0x02, 0x0a, 0x1b, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, - 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, - 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x79, 0x70, - 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x62, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x62, 0x4b, - 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, - 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, - 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x22, 0x56, 0x0a, 0x16, 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, - 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xf8, 0x05, 0x0a, 0x17, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, - 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, - 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, - 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, - 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x10, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, - 0x73, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x69, 0x63, - 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, - 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, - 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x69, 0x66, 0x74, 0x5f, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x68, 0x65, 0x61, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x68, 0x65, 0x61, 0x74, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, - 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x65, 0x66, 0x66, - 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0e, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, - 0x64, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x49, 0x64, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x67, 0x69, 0x66, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x68, 0x61, - 0x72, 0x67, 0x65, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x11, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x41, 0x73, 0x73, 0x65, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, - 0x76, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x6d, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x4d, - 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x74, - 0x6f, 0x5f, 0x6d, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x65, 0x66, 0x66, 0x65, - 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x6f, 0x4d, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x66, 0x66, - 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0b, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, - 0x63, 0x70, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x70, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0xc3, 0x01, 0x0a, 0x1d, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, - 0x64, 0x65, 0x12, 0x3e, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x69, 0x74, 0x65, - 0x6d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x53, 0x0a, 0x1e, - 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, - 0x0a, 0x05, 0x67, 0x69, 0x66, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x67, 0x69, 0x66, 0x74, - 0x73, 0x22, 0xf8, 0x05, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, - 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, - 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, - 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, - 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, - 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x11, - 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6a, 0x73, 0x6f, - 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x69, - 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x70, 0x72, 0x69, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, - 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x0a, - 0x11, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x50, 0x6f, - 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x68, 0x65, 0x61, - 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x68, - 0x65, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x66, 0x66, 0x65, - 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0d, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x41, 0x74, 0x4d, 0x73, - 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, - 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x03, 0x52, 0x09, - 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x67, 0x69, 0x66, - 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x67, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x2a, 0x0a, 0x11, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x72, - 0x67, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x65, - 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x6d, 0x73, - 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, - 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, - 0x74, 0x69, 0x76, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x6d, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0d, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x6f, 0x4d, 0x73, 0x12, - 0x21, 0x0a, 0x0c, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, - 0x14, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x70, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x70, - 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0xb1, 0x01, 0x0a, - 0x1a, 0x53, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, - 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, - 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x22, 0x96, 0x01, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, - 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, - 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x45, 0x0a, 0x12, 0x47, 0x69, 0x66, - 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x2f, 0x0a, 0x04, 0x67, 0x69, 0x66, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x04, 0x67, 0x69, 0x66, 0x74, - 0x22, 0xb9, 0x02, 0x0a, 0x14, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, - 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x71, 0x75, - 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, - 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x67, 0x72, 0x61, - 0x6e, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0xfb, 0x01, 0x0a, - 0x19, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, - 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x28, 0x0a, - 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, - 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x67, 0x72, 0x61, 0x6e, 0x74, - 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x67, - 0x72, 0x61, 0x6e, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0xb3, 0x01, 0x0a, 0x1a, 0x52, - 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, - 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x22, 0x4d, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x05, 0x67, 0x72, 0x61, - 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x05, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x22, - 0xb3, 0x01, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, - 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6f, - 0x6e, 0x6c, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x63, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x46, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, - 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x22, 0xb5, 0x01, 0x0a, 0x18, 0x45, - 0x71, 0x75, 0x69, 0x70, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, - 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, - 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x49, 0x64, 0x22, 0x61, 0x0a, 0x19, 0x45, 0x71, 0x75, 0x69, 0x70, 0x55, 0x73, 0x65, 0x72, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x94, 0x01, 0x0a, 0x1a, 0x55, 0x6e, 0x65, 0x71, 0x75, 0x69, - 0x70, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, - 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x86, 0x01, 0x0a, - 0x1b, 0x55, 0x6e, 0x65, 0x71, 0x75, 0x69, 0x70, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x75, 0x6e, 0x65, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x6e, 0x65, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, - 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, - 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xa2, 0x01, 0x0a, 0x24, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, - 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x45, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, - 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, - 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x22, 0x78, 0x0a, 0x15, 0x55, 0x73, - 0x65, 0x72, 0x45, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x46, 0x0a, 0x09, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x6e, - 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x22, 0x65, 0x0a, 0x25, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x45, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, - 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x73, 0x65, 0x72, 0x45, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x22, 0xc4, 0x01, 0x0a, 0x19, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, - 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, - 0x7a, 0x65, 0x22, 0x6a, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x36, 0x0a, 0x06, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, - 0x52, 0x06, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x8a, - 0x02, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, - 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x73, 0x68, 0x6f, 0x70, - 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, - 0x73, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x64, 0x61, 0x79, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x64, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x79, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x66, 0x66, 0x65, - 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x72, - 0x6f, 0x6d, 0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, - 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x65, - 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x6f, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, - 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x81, 0x02, 0x0a, 0x1c, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, - 0x49, 0x74, 0x65, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, - 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, - 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, - 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1f, - 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x22, - 0x6e, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, - 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x37, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, - 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, - 0xc2, 0x01, 0x0a, 0x1e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, - 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x3c, 0x0a, 0x05, - 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, - 0x70, 0x75, 0x74, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x22, 0x5a, 0x0a, 0x1f, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, - 0x22, 0xc0, 0x01, 0x0a, 0x20, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x20, 0x0a, 0x0c, 0x73, 0x68, 0x6f, 0x70, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x49, - 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x22, 0x51, 0x0a, 0x18, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, - 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x35, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, - 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x96, 0x01, 0x0a, 0x1f, 0x50, 0x75, 0x72, 0x63, 0x68, - 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, - 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x20, 0x0a, - 0x0c, 0x73, 0x68, 0x6f, 0x70, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x22, - 0xee, 0x02, 0x0a, 0x20, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, - 0x49, 0x64, 0x12, 0x3e, 0x0a, 0x09, 0x73, 0x68, 0x6f, 0x70, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x08, 0x73, 0x68, 0x6f, 0x70, 0x49, 0x74, - 0x65, 0x6d, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, - 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, - 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x70, 0x65, 0x6e, 0x74, - 0x22, 0xa1, 0x08, 0x0a, 0x0c, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, - 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, 0x0e, - 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x63, 0x68, 0x61, - 0x72, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x21, 0x0a, 0x0c, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x66, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x52, - 0x65, 0x66, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, - 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x67, 0x69, - 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x65, 0x6c, - 0x6c, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, - 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, - 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x28, 0x0a, 0x10, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x61, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x75, 0x73, 0x64, 0x4d, 0x69, - 0x6e, 0x6f, 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x19, 0x65, - 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, - 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, 0x16, - 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, - 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, - 0x6e, 0x6f, 0x72, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x23, - 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x69, 0x64, - 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x15, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x75, 0x73, 0x65, 0x72, 0x50, 0x61, 0x69, 0x64, 0x43, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x33, 0x0a, 0x16, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, - 0x69, 0x63, 0x72, 0x6f, 0x18, 0x16, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x75, 0x73, 0x65, 0x72, - 0x50, 0x61, 0x69, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x12, - 0x2c, 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x5f, - 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x18, 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x64, 0x65, 0x72, 0x46, 0x65, 0x65, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x12, 0x2c, 0x0a, - 0x12, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x74, 0x61, 0x78, 0x5f, 0x6d, 0x69, - 0x63, 0x72, 0x6f, 0x18, 0x18, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x54, 0x61, 0x78, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x12, 0x2c, 0x0a, 0x12, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x65, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x72, - 0x6f, 0x18, 0x19, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x4e, 0x65, 0x74, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x12, 0x29, 0x0a, 0x11, 0x70, 0x61, 0x69, - 0x64, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x1a, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x70, 0x61, 0x69, 0x64, 0x53, 0x79, 0x6e, 0x63, 0x65, 0x64, - 0x41, 0x74, 0x4d, 0x73, 0x22, 0x93, 0x03, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, - 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, - 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, - 0x6c, 0x6c, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x63, 0x68, 0x61, - 0x72, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1e, - 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1a, - 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, - 0x67, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, - 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, - 0x61, 0x69, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x70, 0x61, 0x69, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, 0x66, 0x0a, 0x19, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x05, 0x62, 0x69, 0x6c, 0x6c, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, - 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x52, 0x05, 0x62, 0x69, 0x6c, 0x6c, 0x73, 0x12, 0x14, 0x0a, 0x05, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x22, 0xc8, 0x02, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, - 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, - 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6c, 0x6c, 0x65, - 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0c, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, - 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, - 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, - 0x72, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, - 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x41, 0x74, 0x4d, - 0x73, 0x12, 0x1a, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x85, 0x01, - 0x0a, 0x19, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x75, - 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, - 0x69, 0x6c, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x09, 0x62, 0x69, 0x6c, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, - 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x75, - 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x75, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x41, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc9, 0x02, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, - 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, - 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x42, 0x75, 0x63, - 0x6b, 0x65, 0x74, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x4b, 0x0a, 0x0b, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x5f, 0x70, 0x6c, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x75, - 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x0a, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x50, 0x6c, 0x61, 0x79, 0x12, 0x4b, 0x0a, 0x0b, 0x74, 0x68, 0x69, 0x72, 0x64, - 0x5f, 0x70, 0x61, 0x72, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, - 0x72, 0x79, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x0a, 0x74, 0x68, 0x69, 0x72, 0x64, 0x50, - 0x61, 0x72, 0x74, 0x79, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, - 0x6c, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, - 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x42, - 0x75, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, - 0x72, 0x22, 0x9c, 0x02, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, - 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, - 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, - 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1a, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x5f, - 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, 0x6e, 0x64, - 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x7a, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, - 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x75, 0x74, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x0f, 0x74, 0x7a, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x6e, 0x75, 0x74, 0x65, 0x73, - 0x22, 0xd9, 0x02, 0x0a, 0x17, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, - 0x6c, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, - 0x64, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x65, - 0x12, 0x28, 0x0a, 0x10, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, - 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x31, 0x0a, 0x15, 0x74, 0x68, - 0x69, 0x72, 0x64, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, - 0x6e, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x74, 0x68, 0x69, 0x72, 0x64, - 0x50, 0x61, 0x72, 0x74, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x31, 0x0a, - 0x15, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x64, - 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x63, 0x6f, - 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, - 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, - 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x35, - 0x0a, 0x17, 0x74, 0x68, 0x69, 0x72, 0x64, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x79, 0x5f, 0x63, 0x6f, - 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x14, 0x74, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x41, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, - 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, - 0x65, 0x72, 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x80, 0x01, 0x0a, - 0x18, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x52, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x69, 0x6c, 0x6c, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x62, 0x69, 0x6c, 0x6c, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, - 0x6f, 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0e, 0x75, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, - 0xc0, 0x02, 0x0a, 0x1b, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, - 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x69, 0x64, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, - 0x2a, 0x0a, 0x11, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x5f, 0x62, 0x69, 0x6c, 0x6c, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, - 0x79, 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, - 0x0a, 0x0e, 0x75, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x75, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, - 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0f, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, - 0x72, 0x12, 0x29, 0x0a, 0x11, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x75, 0x73, 0x64, - 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x65, 0x73, - 0x74, 0x46, 0x65, 0x65, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x11, - 0x65, 0x73, 0x74, 0x5f, 0x74, 0x61, 0x78, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, - 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x65, 0x73, 0x74, 0x54, 0x61, 0x78, 0x55, - 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x11, 0x65, 0x73, 0x74, 0x5f, 0x6e, - 0x65, 0x74, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0e, 0x65, 0x73, 0x74, 0x4e, 0x65, 0x74, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, - 0x6f, 0x72, 0x22, 0x76, 0x0a, 0x1f, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, - 0x6c, 0x6c, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, - 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, - 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, - 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, - 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, - 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, - 0x65, 0x72, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x22, 0xd0, 0x02, 0x0a, 0x1f, 0x47, - 0x65, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x4f, 0x76, - 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, - 0x0a, 0x05, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x44, 0x61, 0x69, 0x6c, - 0x79, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x05, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x12, 0x43, - 0x0a, 0x07, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x52, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x07, 0x72, 0x65, 0x67, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x4d, 0x0a, 0x0b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x5f, 0x70, 0x61, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, - 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x69, - 0x64, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x0a, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, - 0x69, 0x64, 0x12, 0x59, 0x0a, 0x0f, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x74, 0x72, 0x61, - 0x6e, 0x73, 0x66, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x0e, 0x73, - 0x61, 0x6c, 0x61, 0x72, 0x79, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x22, 0x86, 0x01, - 0x0a, 0x21, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x27, 0x0a, - 0x0f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x22, 0xfe, 0x01, 0x0a, 0x12, 0x47, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x25, 0x0a, - 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x61, 0x69, - 0x64, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x70, 0x61, 0x69, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x78, 0x5f, 0x6d, 0x69, 0x63, - 0x72, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x74, 0x61, 0x78, 0x4d, 0x69, 0x63, - 0x72, 0x6f, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x65, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6e, 0x65, 0x74, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x12, - 0x20, 0x0a, 0x0c, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x41, 0x74, 0x4d, - 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x61, 0x0a, 0x22, 0x52, 0x65, 0x66, 0x72, 0x65, - 0x73, 0x68, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, - 0x72, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, - 0x06, 0x70, 0x72, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, - 0x63, 0x65, 0x52, 0x06, 0x70, 0x72, 0x69, 0x63, 0x65, 0x73, 0x22, 0x79, 0x0a, 0x12, 0x57, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, - 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x65, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, 0x65, 0x63, 0x68, - 0x61, 0x72, 0x67, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x18, 0x64, - 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x5f, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, - 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x64, - 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x45, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x6d, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x57, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, - 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x22, 0xa0, 0x01, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x57, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x39, 0x0a, 0x08, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, - 0x6e, 0x63, 0x65, 0x52, 0x08, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x48, 0x0a, - 0x0d, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x46, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x52, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x22, 0x59, 0x0a, 0x12, 0x57, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x1f, 0x0a, - 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, - 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x4d, 0x73, 0x22, 0x71, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, - 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x5e, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x57, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x07, 0x73, 0x75, - 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0xec, 0x05, 0x0a, 0x0c, 0x47, 0x69, 0x66, 0x74, 0x57, 0x61, - 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, - 0x1b, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x35, 0x0a, - 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x5f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x6e, - 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x67, 0x69, - 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x67, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, - 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x72, 0x65, - 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, - 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x28, 0x0a, - 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, - 0x69, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x44, 0x69, 0x61, 0x6d, 0x6f, - 0x6e, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x69, 0x6e, - 0x74, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x68, 0x65, 0x61, 0x74, 0x5f, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x48, 0x65, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x63, - 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x41, 0x73, - 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, - 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0f, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x56, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2f, 0x0a, 0x14, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x72, - 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x10, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x11, 0x66, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, - 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, - 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x11, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, - 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x18, 0x12, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x22, 0x6b, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x47, - 0x69, 0x66, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, - 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, - 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x22, 0xef, 0x02, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x47, 0x69, 0x66, - 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, - 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, + 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, + 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x74, + 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, + 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, + 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x67, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, + 0x64, 0x65, 0x22, 0x62, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, + 0x05, 0x67, 0x69, 0x66, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x69, 0x66, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, - 0x6d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x5f, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x67, 0x69, 0x66, - 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x67, 0x69, - 0x66, 0x74, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, - 0x6f, 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x69, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, - 0x2e, 0x0a, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, - 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, - 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x5f, 0x68, 0x65, 0x61, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x48, 0x65, 0x61, 0x74, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x22, 0xa4, 0x06, 0x0a, 0x0f, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, - 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, - 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x70, 0x72, 0x6f, - 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, - 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, - 0x6f, 0x64, 0x75, 0x63, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, - 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, - 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x25, 0x0a, 0x0e, - 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1d, 0x0a, - 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, - 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, - 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x0c, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1d, 0x0a, - 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, - 0x03, 0x52, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x12, 0x2e, 0x0a, 0x13, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, - 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x18, 0x0f, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x12, - 0x2b, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x12, - 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, - 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x13, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, - 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x14, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x64, - 0x69, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x70, - 0x5f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, - 0x61, 0x70, 0x70, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x22, 0xce, 0x01, 0x0a, 0x1b, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, - 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, - 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, - 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, - 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, - 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, - 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x78, 0x0a, 0x1c, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, - 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x08, 0x70, - 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, - 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x63, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0x9c, 0x03, 0x0a, 0x1b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, - 0x6d, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x67, 0x69, 0x66, 0x74, 0x73, + 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x8f, 0x01, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x47, + 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, - 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, - 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, - 0x63, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, - 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, - 0x63, 0x6b, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x75, 0x72, - 0x63, 0x68, 0x61, 0x73, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x70, - 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x54, - 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, - 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x49, 0x64, 0x22, 0xf5, 0x02, 0x0a, 0x1c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, - 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, - 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, - 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x43, 0x6f, 0x64, 0x65, - 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x64, - 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x73, 0x75, - 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, 0x9d, 0x02, 0x0a, - 0x20, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, - 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x5d, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, + 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x69, + 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x67, 0x69, + 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x22, 0x82, 0x02, 0x0a, 0x1b, 0x55, 0x70, 0x73, 0x65, + 0x72, 0x74, 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, + 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x56, 0x0a, 0x16, + 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x69, 0x66, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x22, 0xf8, 0x05, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, + 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, + 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, + 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, + 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, + 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, + 0x2b, 0x0a, 0x11, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x72, 0x65, 0x73, + 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, + 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x69, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, + 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x67, 0x69, 0x66, + 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x68, 0x65, 0x61, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x09, 0x68, 0x65, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x65, + 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x41, + 0x74, 0x4d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, + 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, + 0x03, 0x52, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x12, 0x24, 0x0a, 0x0e, + 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x10, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x67, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, + 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, + 0x68, 0x61, 0x72, 0x67, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2a, + 0x0a, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, + 0x5f, 0x6d, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, + 0x74, 0x69, 0x76, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x66, + 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x6d, 0x73, 0x18, 0x13, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0d, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x6f, + 0x4d, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x70, 0x5f, 0x72, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x63, 0x70, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, + 0xc3, 0x01, 0x0a, 0x1d, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, + 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, - 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, - 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, - 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, - 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, - 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x75, 0x64, 0x69, 0x65, - 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x77, 0x0a, 0x21, - 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, - 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, - 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, - 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0xbb, 0x03, 0x0a, 0x1c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x3e, 0x0a, 0x05, 0x69, + 0x74, 0x65, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x53, 0x0a, 0x1e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x05, 0x67, 0x69, 0x66, 0x74, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x05, 0x67, 0x69, 0x66, 0x74, 0x73, 0x22, 0xf8, 0x05, 0x0a, 0x17, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, - 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x69, - 0x63, 0x72, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, - 0x75, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, - 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, - 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, - 0x69, 0x64, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x28, - 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x75, 0x64, 0x69, - 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, - 0x0b, 0x61, 0x70, 0x70, 0x5f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x0c, 0x20, 0x01, - 0x28, 0x08, 0x48, 0x00, 0x52, 0x0a, 0x61, 0x70, 0x70, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, - 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x61, 0x70, 0x70, 0x5f, 0x76, 0x69, 0x73, 0x69, - 0x62, 0x6c, 0x65, 0x22, 0xda, 0x03, 0x0a, 0x1c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, + 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, + 0x72, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x10, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x73, + 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x69, 0x63, 0x65, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, + 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, + 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x68, 0x65, 0x61, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x68, 0x65, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x65, 0x66, 0x66, 0x65, + 0x63, 0x74, 0x69, 0x76, 0x65, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, + 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, + 0x64, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x67, 0x69, 0x66, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x11, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, + 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x6d, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x4d, 0x73, + 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x74, 0x6f, + 0x5f, 0x6d, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x65, 0x66, 0x66, 0x65, 0x63, + 0x74, 0x69, 0x76, 0x65, 0x54, 0x6f, 0x4d, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x66, 0x66, 0x65, + 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, + 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x63, + 0x70, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x70, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0xb1, 0x01, 0x0a, 0x1a, 0x53, 0x65, 0x74, 0x47, 0x69, 0x66, + 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, - 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, - 0x0c, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x69, 0x63, 0x72, 0x6f, - 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, - 0x72, 0x6d, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, - 0x72, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, - 0x18, 0x09, 0x20, 0x03, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, - 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, - 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, - 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0b, 0x61, 0x70, - 0x70, 0x5f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x48, - 0x00, 0x52, 0x0a, 0x61, 0x70, 0x70, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x88, 0x01, 0x01, - 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x61, 0x70, 0x70, 0x5f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, - 0x22, 0xa1, 0x01, 0x0a, 0x1c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, - 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, - 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, - 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x22, 0x55, 0x0a, 0x17, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, - 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x3a, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, - 0x63, 0x74, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0x39, 0x0a, 0x1d, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, - 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, - 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0xa6, 0x04, 0x0a, 0x17, 0x54, 0x68, 0x69, 0x72, 0x64, - 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, - 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x17, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x5f, 0x77, 0x61, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x70, 0x61, 0x79, 0x57, 0x61, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x61, 0x79, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x6f, 0x5f, 0x75, 0x72, 0x6c, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x6c, 0x12, - 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2f, 0x0a, 0x14, 0x75, 0x73, 0x64, 0x5f, 0x74, - 0x6f, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, - 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x73, 0x64, 0x54, 0x6f, 0x43, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x52, 0x61, 0x74, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, - 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, - 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x10, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, - 0xfa, 0x01, 0x0a, 0x18, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x19, 0x0a, 0x08, - 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, - 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, - 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x68, 0x69, 0x72, - 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, - 0x68, 0x6f, 0x64, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x22, 0xd7, 0x01, 0x0a, - 0x24, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, + 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x96, 0x01, 0x0a, 0x17, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x38, 0x0a, 0x18, - 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, - 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x4d, - 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x22, 0x6e, 0x0a, 0x25, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x68, - 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x45, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x08, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0xc4, 0x01, 0x0a, 0x27, 0x53, 0x65, 0x74, 0x54, 0x68, - 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, - 0x65, 0x74, 0x68, 0x6f, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x22, 0x45, 0x0a, 0x12, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x67, 0x69, 0x66, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x04, 0x67, 0x69, 0x66, 0x74, 0x22, 0xb9, 0x02, 0x0a, 0x14, 0x47, 0x72, + 0x61, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, + 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, + 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, + 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x53, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0xfb, 0x01, 0x0a, 0x19, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, + 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x21, 0x0a, 0x0c, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x22, 0xb3, 0x01, 0x0a, 0x1a, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, - 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x08, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, - 0x6c, 0x65, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x63, 0x0a, - 0x1f, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x40, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x22, 0xd6, 0x01, 0x0a, 0x22, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x68, 0x69, - 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x61, - 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, - 0x12, 0x2f, 0x0a, 0x14, 0x75, 0x73, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, - 0x75, 0x73, 0x64, 0x54, 0x6f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x61, 0x74, - 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x98, 0x01, 0x0a, 0x20, - 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x73, 0x73, 0x75, 0x65, - 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xae, 0x01, 0x0a, 0x23, 0x53, 0x79, 0x6e, 0x63, 0x54, - 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, - 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, - 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, - 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, - 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xed, 0x02, 0x0a, 0x24, 0x53, 0x79, 0x6e, 0x63, - 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x66, 0x65, 0x74, - 0x63, 0x68, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x66, 0x65, 0x74, 0x63, 0x68, 0x65, - 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, - 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x70, - 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, - 0x73, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x49, 0x0a, 0x06, - 0x69, 0x73, 0x73, 0x75, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, - 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, - 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x73, 0x22, 0xf9, 0x01, 0x0a, 0x18, 0x48, 0x35, 0x52, 0x65, - 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, - 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x72, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2e, - 0x0a, 0x13, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, - 0x0a, 0x0d, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x54, - 0x79, 0x70, 0x65, 0x22, 0x88, 0x02, 0x0a, 0x19, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, - 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, - 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, - 0x51, 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x68, 0x69, 0x72, 0x64, - 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x75, 0x73, 0x64, 0x74, 0x5f, 0x74, 0x72, 0x63, 0x32, 0x30, - 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, - 0x75, 0x73, 0x64, 0x74, 0x54, 0x72, 0x63, 0x32, 0x30, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x12, 0x2c, 0x0a, 0x12, 0x75, 0x73, 0x64, 0x74, 0x5f, 0x74, 0x72, 0x63, 0x32, 0x30, 0x5f, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x75, 0x73, - 0x64, 0x74, 0x54, 0x72, 0x63, 0x32, 0x30, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x8e, - 0x08, 0x0a, 0x15, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x68, 0x61, - 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, - 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x72, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2e, - 0x0a, 0x13, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, - 0x0a, 0x0d, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, - 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, - 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, - 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, - 0x6e, 0x6f, 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0e, 0x75, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, - 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, - 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, - 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, - 0x6f, 0x72, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x17, 0x0a, - 0x07, 0x70, 0x61, 0x79, 0x5f, 0x77, 0x61, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x70, 0x61, 0x79, 0x57, 0x61, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x61, 0x79, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x12, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x79, 0x55, 0x72, 0x6c, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, - 0x68, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, - 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, - 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, - 0x6f, 0x6e, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, - 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x22, - 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x19, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x5f, 0x6d, 0x73, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, - 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x18, 0x1b, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x10, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, - 0x6c, 0x61, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x22, - 0x94, 0x05, 0x0a, 0x1c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, - 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, + 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x67, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, + 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x4d, 0x0a, 0x15, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x34, 0x0a, 0x05, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, + 0x74, 0x52, 0x05, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x22, 0xb3, 0x01, 0x0a, 0x18, 0x4c, 0x69, 0x73, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, + 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x63, + 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x09, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x22, 0xb5, 0x01, 0x0a, 0x18, 0x45, 0x71, 0x75, 0x69, 0x70, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x28, 0x0a, 0x10, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x75, 0x64, - 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, - 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, - 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, - 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x65, - 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x1d, - 0x0a, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x1d, 0x0a, - 0x0a, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x55, 0x72, 0x6c, 0x12, 0x1b, 0x0a, 0x09, - 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x6e, - 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, - 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x79, 0x65, 0x72, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x79, 0x65, 0x72, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x61, 0x79, 0x65, 0x72, 0x5f, 0x61, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x61, 0x79, - 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x79, - 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x70, 0x61, 0x79, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, - 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x22, 0xf6, 0x03, 0x0a, 0x23, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, - 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, + 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, + 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x61, 0x0a, 0x19, 0x45, + 0x71, 0x75, 0x69, 0x70, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x94, + 0x01, 0x0a, 0x1a, 0x55, 0x6e, 0x65, 0x71, 0x75, 0x69, 0x70, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, + 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, + 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x86, 0x01, 0x0a, 0x1b, 0x55, 0x6e, 0x65, 0x71, 0x75, 0x69, + 0x70, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x75, 0x6e, + 0x65, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, + 0x75, 0x6e, 0x65, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xa2, + 0x01, 0x0a, 0x24, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x45, + 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x25, 0x0a, 0x0e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x73, 0x22, 0x78, 0x0a, 0x15, 0x55, 0x73, 0x65, 0x72, 0x45, 0x71, 0x75, 0x69, 0x70, + 0x70, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x17, 0x0a, 0x07, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x46, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x22, 0x65, 0x0a, + 0x25, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x45, 0x71, 0x75, + 0x69, 0x70, 0x70, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x45, 0x71, 0x75, 0x69, + 0x70, 0x70, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x05, 0x75, + 0x73, 0x65, 0x72, 0x73, 0x22, 0xc4, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, + 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, + 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, + 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x6a, 0x0a, 0x1a, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x06, 0x67, 0x72, 0x61, + 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x06, 0x67, 0x72, 0x61, 0x6e, 0x74, + 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x8a, 0x02, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x70, 0x75, + 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x73, 0x68, 0x6f, 0x70, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, + 0x6d, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, + 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x79, 0x73, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0c, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x79, + 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x66, + 0x72, 0x6f, 0x6d, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x65, 0x66, + 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x4d, 0x73, 0x12, 0x26, 0x0a, + 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x6d, 0x73, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, + 0x65, 0x54, 0x6f, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x22, 0x81, 0x02, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, + 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, + 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, + 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x6e, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x69, 0x74, 0x65, + 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, + 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0xc2, 0x01, 0x0a, 0x1e, 0x55, 0x70, 0x73, + 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, + 0x74, 0x65, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, + 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, + 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x3c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, + 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x05, 0x69, 0x74, + 0x65, 0x6d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x5a, 0x0a, + 0x1f, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, + 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x37, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, + 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xc0, 0x01, 0x0a, 0x20, 0x53, 0x65, + 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, + 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, - 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, - 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x75, 0x73, 0x64, 0x5f, - 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0e, 0x75, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x41, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x5f, 0x75, 0x72, - 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x55, - 0x72, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x5f, 0x75, 0x72, 0x6c, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x55, 0x72, - 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x12, 0x1a, - 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, - 0x79, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x70, 0x61, 0x79, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x61, 0x79, - 0x65, 0x72, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x70, 0x61, 0x79, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, - 0x0a, 0x0b, 0x70, 0x61, 0x79, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x0e, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x79, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x22, - 0x77, 0x0a, 0x20, 0x47, 0x65, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x52, - 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x73, 0x68, 0x6f, 0x70, + 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, + 0x73, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x51, 0x0a, 0x18, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, + 0x96, 0x01, 0x0a, 0x1f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, - 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x22, 0xe6, 0x01, 0x0a, 0x22, 0x4c, 0x69, 0x73, - 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, - 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, - 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, - 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, - 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, - 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, - 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, - 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, - 0x65, 0x22, 0x7b, 0x0a, 0x23, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, - 0x72, 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x06, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x52, 0x06, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0xaf, - 0x01, 0x0a, 0x19, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, - 0x72, 0x67, 0x65, 0x54, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, + 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x73, 0x68, 0x6f, 0x70, 0x5f, 0x69, + 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x68, + 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x22, 0xee, 0x02, 0x0a, 0x20, 0x50, 0x75, 0x72, + 0x63, 0x68, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, + 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, + 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, + 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x72, 0x61, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x3e, 0x0a, 0x09, 0x73, + 0x68, 0x6f, 0x70, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, + 0x6d, 0x52, 0x08, 0x73, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x44, 0x0a, 0x08, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, + 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, + 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x22, 0xa1, 0x08, 0x0a, 0x0c, 0x52, 0x65, + 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, + 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, + 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, + 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x66, 0x12, 0x17, 0x0a, 0x07, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, + 0x6c, 0x6c, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, + 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x72, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, + 0x0a, 0x09, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x70, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, + 0x6f, 0x64, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, + 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x75, 0x73, 0x64, 0x5f, + 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0e, 0x75, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x63, + 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x12, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x19, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, 0x16, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, + 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x4d, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x13, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x35, 0x0a, 0x17, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x75, + 0x73, 0x65, 0x72, 0x50, 0x61, 0x69, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, + 0x6f, 0x64, 0x65, 0x12, 0x33, 0x0a, 0x16, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x69, 0x64, + 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x18, 0x16, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x13, 0x75, 0x73, 0x65, 0x72, 0x50, 0x61, 0x69, 0x64, 0x41, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x18, 0x17, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x46, 0x65, + 0x65, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x5f, 0x74, 0x61, 0x78, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x18, 0x18, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x54, 0x61, 0x78, 0x4d, + 0x69, 0x63, 0x72, 0x6f, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x5f, 0x6e, 0x65, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x18, 0x19, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x10, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x4e, 0x65, 0x74, 0x4d, 0x69, 0x63, + 0x72, 0x6f, 0x12, 0x29, 0x0a, 0x11, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x70, + 0x61, 0x69, 0x64, 0x53, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x93, 0x03, + 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, + 0x6c, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, + 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, + 0x0e, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, + 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1a, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x41, + 0x74, 0x4d, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x73, 0x69, 0x7a, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, + 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x69, 0x64, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x22, 0x66, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, + 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x33, 0x0a, 0x05, 0x62, 0x69, 0x6c, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x52, 0x05, + 0x62, 0x69, 0x6c, 0x6c, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0xc8, 0x02, 0x0a, 0x1d, + 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x53, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, + 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, + 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x63, 0x68, + 0x61, 0x72, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1a, 0x0a, 0x09, 0x65, 0x6e, + 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, + 0x6e, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x85, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x63, 0x68, 0x61, + 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x42, 0x75, + 0x63, 0x6b, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x69, 0x6c, 0x6c, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x62, 0x69, 0x6c, 0x6c, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, + 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, + 0x75, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc9, + 0x02, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, + 0x6c, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x40, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x53, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x05, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x12, 0x4b, 0x0a, 0x0b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x5f, 0x70, 0x6c, + 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, + 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x42, 0x75, + 0x63, 0x6b, 0x65, 0x74, 0x52, 0x0a, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x6c, 0x61, 0x79, + 0x12, 0x4b, 0x0a, 0x0b, 0x74, 0x68, 0x69, 0x72, 0x64, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, + 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x42, 0x75, 0x63, 0x6b, 0x65, + 0x74, 0x52, 0x0a, 0x74, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x12, 0x4b, 0x0a, + 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, + 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x0a, + 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x22, 0x8b, 0x02, 0x0a, 0x11, 0x55, + 0x73, 0x65, 0x72, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, + 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x63, + 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0d, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x16, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x31, 0x0a, 0x15, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x68, 0x61, + 0x72, 0x67, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x12, 0x66, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x64, + 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x77, 0x0a, 0x20, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, + 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, - 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, - 0x22, 0x96, 0x01, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, - 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, - 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, - 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x57, 0x0a, 0x17, 0x48, 0x35, 0x52, - 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x52, 0x65, - 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x22, 0xe1, 0x03, 0x0a, 0x26, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x6f, 0x69, - 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, - 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, + 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x73, 0x22, 0x5d, 0x0a, 0x21, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x63, 0x68, + 0x61, 0x72, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x22, 0x9c, 0x02, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, + 0x42, 0x69, 0x6c, 0x6c, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, + 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1a, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x41, + 0x74, 0x4d, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x7a, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, + 0x5f, 0x6d, 0x69, 0x6e, 0x75, 0x74, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, + 0x74, 0x7a, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x6e, 0x75, 0x74, 0x65, 0x73, 0x22, + 0xd9, 0x02, 0x0a, 0x17, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, + 0x44, 0x61, 0x69, 0x6c, 0x79, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, + 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x65, 0x12, + 0x28, 0x0a, 0x10, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, + 0x6e, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x31, 0x0a, 0x15, 0x74, 0x68, 0x69, + 0x72, 0x64, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, + 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x74, 0x68, 0x69, 0x72, 0x64, 0x50, + 0x61, 0x72, 0x74, 0x79, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x31, 0x0a, 0x15, + 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x64, 0x5f, + 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x63, 0x6f, 0x69, + 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, + 0x2c, 0x0a, 0x12, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x35, 0x0a, + 0x17, 0x74, 0x68, 0x69, 0x72, 0x64, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x79, 0x5f, 0x63, 0x6f, 0x69, + 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, + 0x74, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, + 0x6c, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, + 0x72, 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x80, 0x01, 0x0a, 0x18, + 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x52, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x69, 0x6c, 0x6c, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x62, 0x69, 0x6c, 0x6c, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, + 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, + 0x75, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc0, + 0x02, 0x0a, 0x1b, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x47, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x69, 0x64, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x2a, + 0x0a, 0x11, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x5f, 0x62, 0x69, 0x6c, 0x6c, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x79, + 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, + 0x0e, 0x75, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x75, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, 0x5f, + 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0f, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, + 0x12, 0x29, 0x0a, 0x11, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x5f, + 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x65, 0x73, 0x74, + 0x46, 0x65, 0x65, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x11, 0x65, + 0x73, 0x74, 0x5f, 0x74, 0x61, 0x78, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x65, 0x73, 0x74, 0x54, 0x61, 0x78, 0x55, 0x73, + 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x11, 0x65, 0x73, 0x74, 0x5f, 0x6e, 0x65, + 0x74, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0e, 0x65, 0x73, 0x74, 0x4e, 0x65, 0x74, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, + 0x72, 0x22, 0x76, 0x0a, 0x1f, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, + 0x6c, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x53, + 0x74, 0x61, 0x74, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, + 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, + 0x72, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x22, 0xd0, 0x02, 0x0a, 0x1f, 0x47, 0x65, + 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x4f, 0x76, 0x65, + 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, + 0x05, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x44, 0x61, 0x69, 0x6c, 0x79, + 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x05, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x12, 0x43, 0x0a, + 0x07, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x52, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x07, 0x72, 0x65, 0x67, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x4d, 0x0a, 0x0b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x69, 0x64, + 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x0a, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x69, + 0x64, 0x12, 0x59, 0x0a, 0x0f, 0x73, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x5f, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x66, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, + 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x0e, 0x73, 0x61, + 0x6c, 0x61, 0x72, 0x79, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x22, 0x86, 0x01, 0x0a, + 0x21, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, + 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x27, 0x0a, 0x0f, + 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x22, 0xfe, 0x01, 0x0a, 0x12, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, + 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x61, 0x69, 0x64, + 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x70, 0x61, 0x69, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, + 0x69, 0x63, 0x72, 0x6f, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x78, 0x5f, 0x6d, 0x69, 0x63, 0x72, + 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x74, 0x61, 0x78, 0x4d, 0x69, 0x63, 0x72, + 0x6f, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x65, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6e, 0x65, 0x74, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x12, 0x20, + 0x0a, 0x0c, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, + 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x61, 0x0a, 0x22, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, + 0x68, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, + 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, + 0x70, 0x72, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x63, + 0x65, 0x52, 0x06, 0x70, 0x72, 0x69, 0x63, 0x65, 0x73, 0x22, 0x79, 0x0a, 0x12, 0x57, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, + 0x29, 0x0a, 0x10, 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x65, 0x6e, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, 0x65, 0x63, 0x68, 0x61, + 0x72, 0x67, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x69, + 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x5f, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x64, 0x69, + 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x45, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x22, 0x6d, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, + 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x22, 0xa0, 0x01, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x39, 0x0a, 0x08, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x52, 0x08, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x48, 0x0a, 0x0d, + 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x52, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x22, 0x59, 0x0a, 0x12, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x1f, 0x0a, 0x0b, + 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a, + 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, + 0x73, 0x22, 0x71, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x22, 0x5e, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x07, 0x73, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x22, 0xec, 0x05, 0x0a, 0x0c, 0x47, 0x69, 0x66, 0x74, 0x57, 0x61, 0x6c, + 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1b, + 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x08, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, + 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x67, 0x69, 0x66, + 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x67, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x2b, 0x0a, 0x11, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x72, 0x65, 0x73, + 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, + 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x28, 0x0a, 0x10, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x69, + 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, + 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, + 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, + 0x67, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, + 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x68, 0x65, 0x61, 0x74, 0x5f, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x48, 0x65, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x68, + 0x61, 0x72, 0x67, 0x65, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x70, + 0x72, 0x69, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0f, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2f, 0x0a, 0x14, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x72, 0x65, + 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x10, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x11, 0x66, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, + 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, + 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x11, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, + 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x18, 0x12, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x22, 0x6b, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x47, 0x69, + 0x66, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, + 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, + 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x22, 0xef, 0x02, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x47, 0x69, 0x66, 0x74, + 0x57, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x05, + 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x69, + 0x66, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, + 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x5f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x67, 0x69, 0x66, 0x74, + 0x4b, 0x69, 0x6e, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x67, 0x69, 0x66, + 0x74, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, + 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x69, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2e, + 0x0a, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x28, + 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x47, + 0x69, 0x66, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x5f, 0x68, 0x65, 0x61, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x48, 0x65, 0x61, 0x74, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x22, 0xa4, 0x06, 0x0a, 0x0f, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, + 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, + 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, + 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, + 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x25, 0x0a, 0x0e, 0x70, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x70, + 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, + 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, + 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x03, + 0x52, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x18, 0x0f, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x12, 0x2b, + 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x12, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, + 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x13, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x14, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x64, 0x69, + 0x65, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x5f, + 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, + 0x70, 0x70, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x22, 0xce, 0x01, 0x0a, 0x1b, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, + 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, + 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, + 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, + 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, + 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, + 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x78, 0x0a, 0x1c, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, + 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x08, 0x70, 0x72, + 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x08, + 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x73, 0x22, 0x9c, 0x03, 0x0a, 0x1b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, + 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, + 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, + 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x63, + 0x6b, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x75, 0x72, 0x63, + 0x68, 0x61, 0x73, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0d, 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, + 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x70, 0x75, + 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x0b, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x54, 0x69, + 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, + 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, + 0x79, 0x49, 0x64, 0x22, 0xf5, 0x02, 0x0a, 0x1c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x47, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, + 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, + 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, + 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x64, 0x65, + 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, + 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, + 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, 0x9d, 0x02, 0x0a, 0x20, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, + 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, + 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1b, + 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6b, + 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, + 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, + 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, + 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, + 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, + 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x77, 0x0a, 0x21, 0x4c, + 0x69, 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, + 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x14, + 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x22, 0xbb, 0x03, 0x0a, 0x1c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x21, 0x0a, 0x0c, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x69, 0x63, + 0x72, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, + 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, + 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x64, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, + 0x49, 0x64, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x28, 0x0a, + 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, + 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x75, 0x64, 0x69, 0x65, + 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0b, + 0x61, 0x70, 0x70, 0x5f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x08, 0x48, 0x00, 0x52, 0x0a, 0x61, 0x70, 0x70, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x88, + 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x61, 0x70, 0x70, 0x5f, 0x76, 0x69, 0x73, 0x69, 0x62, + 0x6c, 0x65, 0x22, 0xda, 0x03, 0x0a, 0x1c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, + 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, + 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x12, + 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, + 0x6d, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, + 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, + 0x09, 0x20, 0x03, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, + 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0b, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x64, + 0x69, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0b, 0x61, 0x70, 0x70, + 0x5f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, + 0x52, 0x0a, 0x61, 0x70, 0x70, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x88, 0x01, 0x01, 0x42, + 0x0e, 0x0a, 0x0c, 0x5f, 0x61, 0x70, 0x70, 0x5f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x22, + 0xa1, 0x01, 0x0a, 0x1c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, + 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, + 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, + 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x22, 0x55, 0x0a, 0x17, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, + 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, + 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, + 0x74, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0x39, 0x0a, 0x1d, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, + 0x75, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0xa6, 0x04, 0x0a, 0x17, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, + 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x19, + 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, + 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, + 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, + 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x72, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, + 0x0a, 0x07, 0x70, 0x61, 0x79, 0x5f, 0x77, 0x61, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x70, 0x61, 0x79, 0x57, 0x61, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x61, 0x79, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x6f, 0x5f, 0x75, 0x72, 0x6c, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x6c, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2f, 0x0a, 0x14, 0x75, 0x73, 0x64, 0x5f, 0x74, 0x6f, + 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x73, 0x64, 0x54, 0x6f, 0x43, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x63, 0x79, 0x52, 0x61, 0x74, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, + 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xfa, + 0x01, 0x0a, 0x18, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x61, + 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, + 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, + 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, + 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x68, 0x69, 0x72, 0x64, + 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, + 0x6f, 0x64, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x22, 0xd7, 0x01, 0x0a, 0x24, + 0x4c, 0x69, 0x73, 0x74, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, + 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, + 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x69, + 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x69, + 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x4d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x73, 0x22, 0x6e, 0x0a, 0x25, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x68, 0x69, + 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, + 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x08, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0xc4, 0x01, 0x0a, 0x27, 0x53, 0x65, 0x74, 0x54, 0x68, 0x69, + 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x63, 0x0a, 0x1f, + 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x40, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x22, 0xd6, 0x01, 0x0a, 0x22, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x68, 0x69, 0x72, + 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, + 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, + 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, + 0x2f, 0x0a, 0x14, 0x75, 0x73, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x79, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, + 0x73, 0x64, 0x54, 0x6f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x61, 0x74, 0x65, + 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x98, 0x01, 0x0a, 0x20, 0x54, + 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x73, 0x73, 0x75, 0x65, 0x12, + 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, + 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, + 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xae, 0x01, 0x0a, 0x23, 0x53, 0x79, 0x6e, 0x63, 0x54, 0x68, + 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, - 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x6e, - 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x4e, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x28, - 0x0a, 0x10, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x75, 0x73, 0x64, 0x4d, 0x69, 0x6e, - 0x6f, 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x44, 0x61, 0x74, 0x65, 0x4d, 0x73, 0x12, 0x32, 0x0a, 0x15, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, - 0x12, 0x34, 0x0a, 0x16, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x14, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x5f, 0x70, 0x61, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x50, 0x61, 0x79, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x61, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x22, 0xda, 0x03, 0x0a, 0x27, 0x56, 0x65, 0x72, 0x69, 0x66, - 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x63, 0x68, 0x61, - 0x72, 0x67, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x23, - 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, - 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x4e, 0x6f, 0x12, - 0x2a, 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x10, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xed, 0x02, 0x0a, 0x24, 0x53, 0x79, 0x6e, 0x63, 0x54, + 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x13, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x66, 0x65, 0x74, 0x63, + 0x68, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x66, 0x65, 0x74, 0x63, 0x68, 0x65, 0x64, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x70, 0x65, + 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x73, + 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x49, 0x0a, 0x06, 0x69, + 0x73, 0x73, 0x75, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x68, + 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x06, + 0x69, 0x73, 0x73, 0x75, 0x65, 0x73, 0x22, 0xf9, 0x01, 0x0a, 0x18, 0x48, 0x35, 0x52, 0x65, 0x63, + 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, + 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2e, 0x0a, + 0x13, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, + 0x0d, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x22, 0x88, 0x02, 0x0a, 0x19, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, + 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x51, + 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, + 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x75, 0x73, 0x64, 0x74, 0x5f, 0x74, 0x72, 0x63, 0x32, 0x30, 0x5f, + 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x75, + 0x73, 0x64, 0x74, 0x54, 0x72, 0x63, 0x32, 0x30, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, + 0x2c, 0x0a, 0x12, 0x75, 0x73, 0x64, 0x74, 0x5f, 0x74, 0x72, 0x63, 0x32, 0x30, 0x5f, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x75, 0x73, 0x64, + 0x74, 0x54, 0x72, 0x63, 0x32, 0x30, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x8e, 0x08, + 0x0a, 0x15, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, + 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2e, 0x0a, + 0x13, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, + 0x0d, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, + 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, + 0x6f, 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0e, 0x75, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, + 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, + 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, - 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x19, 0x0a, 0x08, - 0x72, 0x61, 0x77, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x72, 0x61, 0x77, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x27, 0x0a, - 0x0f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x41, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, - 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x25, 0x0a, 0x0e, - 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0c, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x22, 0x9f, 0x01, 0x0a, 0x1a, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x4d, 0x69, - 0x66, 0x61, 0x70, 0x61, 0x79, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, - 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, - 0x6d, 0x65, 0x72, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, - 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, - 0x61, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x67, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x73, 0x69, 0x67, 0x6e, 0x22, 0x79, 0x0a, 0x1b, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x4d, - 0x69, 0x66, 0x61, 0x70, 0x61, 0x79, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, - 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x65, 0x78, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x54, 0x65, 0x78, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, - 0x22, 0xf9, 0x01, 0x0a, 0x18, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x56, 0x35, 0x50, 0x61, 0x79, - 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, + 0x72, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x17, 0x0a, 0x07, + 0x70, 0x61, 0x79, 0x5f, 0x77, 0x61, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, + 0x61, 0x79, 0x57, 0x61, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x61, 0x79, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x17, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x12, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x70, 0x61, 0x79, 0x55, 0x72, 0x6c, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x13, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x27, + 0x0a, 0x0f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x25, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, + 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, + 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x19, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, + 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, + 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x10, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, + 0x61, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x22, 0x94, + 0x05, 0x0a, 0x1c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, + 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, + 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, + 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, + 0x0a, 0x10, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x75, 0x64, 0x69, + 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, + 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x1d, 0x0a, + 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, + 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x55, 0x72, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, + 0x75, 0x61, 0x67, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, + 0x75, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x79, 0x65, 0x72, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x79, 0x65, 0x72, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x61, 0x79, 0x65, 0x72, 0x5f, 0x61, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x61, 0x79, 0x65, + 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x79, 0x65, + 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, + 0x61, 0x79, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x12, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x79, 0x49, 0x64, 0x22, 0xf6, 0x03, 0x0a, 0x23, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, + 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x61, 0x77, 0x5f, 0x6a, - 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x61, 0x77, 0x4a, 0x73, - 0x6f, 0x6e, 0x12, 0x4d, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x56, 0x35, 0x50, 0x61, 0x79, - 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, - 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, - 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x77, 0x0a, 0x19, - 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x56, 0x35, 0x50, 0x61, 0x79, 0x4e, 0x6f, 0x74, 0x69, 0x66, - 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x63, - 0x65, 0x70, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x63, 0x63, - 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x54, 0x65, 0x78, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x49, 0x64, 0x22, 0xde, 0x01, 0x0a, 0x13, 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, - 0x64, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x23, 0x0a, - 0x0d, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, 0x72, 0x6f, - 0x6d, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x74, 0x6f, - 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x74, 0x6f, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, - 0x0a, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x72, 0x6f, 0x6d, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x1b, 0x0a, 0x09, 0x74, 0x6f, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x08, 0x74, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, - 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, - 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x74, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x44, 0x69, 0x61, - 0x6d, 0x6f, 0x6e, 0x64, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x5e, 0x0a, 0x20, - 0x47, 0x65, 0x74, 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x3a, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x22, 0xfa, 0x03, 0x0a, - 0x11, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x25, 0x0a, - 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x69, 0x7a, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x69, 0x7a, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x27, - 0x0a, 0x0f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, - 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, - 0x6c, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x72, 0x6f, 0x7a, 0x65, - 0x6e, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, - 0x72, 0x6f, 0x7a, 0x65, 0x6e, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x76, - 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x66, - 0x74, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x72, 0x6f, 0x7a, 0x65, 0x6e, 0x5f, 0x61, 0x66, - 0x74, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x72, 0x6f, 0x7a, 0x65, - 0x6e, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, - 0x72, 0x70, 0x61, 0x72, 0x74, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x61, 0x72, - 0x74, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, - 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, - 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, - 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, - 0x72, 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x10, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x55, 0x73, 0x64, 0x4d, 0x69, - 0x6e, 0x6f, 0x72, 0x12, 0x34, 0x0a, 0x16, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, - 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0d, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x14, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x22, 0xc2, 0x01, 0x0a, 0x1d, 0x4c, 0x69, - 0x73, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, + 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, + 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x75, 0x73, 0x64, 0x5f, 0x6d, + 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0e, 0x75, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x5f, 0x75, 0x72, 0x6c, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x55, 0x72, + 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x5f, 0x75, 0x72, 0x6c, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x55, 0x72, 0x6c, + 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x12, 0x1a, 0x0a, + 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x79, + 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, + 0x61, 0x79, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x61, 0x79, 0x65, + 0x72, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x70, 0x61, 0x79, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, + 0x0b, 0x70, 0x61, 0x79, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x0e, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x79, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x77, + 0x0a, 0x20, 0x47, 0x65, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, + 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, + 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x22, 0xe6, 0x01, 0x0a, 0x22, 0x4c, 0x69, 0x73, 0x74, + 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, + 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, + 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, + 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, + 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, + 0x22, 0x7b, 0x0a, 0x23, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, + 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x06, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, + 0x06, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0xaf, 0x01, + 0x0a, 0x19, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x54, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, - 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, + 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x22, + 0x96, 0x01, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, + 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, + 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, + 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x57, 0x0a, 0x17, 0x48, 0x35, 0x52, 0x65, + 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x63, + 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, + 0x72, 0x22, 0xe1, 0x03, 0x0a, 0x26, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x6f, 0x69, 0x6e, + 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, + 0x63, 0x65, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, + 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, + 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x6f, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x4e, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x28, 0x0a, + 0x10, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x75, 0x73, 0x64, 0x4d, 0x69, 0x6e, 0x6f, + 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x5f, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x44, 0x61, 0x74, 0x65, 0x4d, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x34, 0x0a, 0x16, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x14, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, + 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x5f, 0x70, 0x61, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x50, 0x61, 0x79, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x22, 0xda, 0x03, 0x0a, 0x27, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, + 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x23, 0x0a, + 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, + 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6f, + 0x72, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x4e, 0x6f, 0x12, 0x2a, + 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, + 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x72, + 0x61, 0x77, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, + 0x61, 0x77, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x27, 0x0a, 0x0f, + 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, + 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x66, + 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0c, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x22, 0x9f, 0x01, 0x0a, 0x1a, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x4d, 0x69, 0x66, + 0x61, 0x70, 0x61, 0x79, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, + 0x65, 0x72, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x6d, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, + 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x67, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x73, 0x69, 0x67, 0x6e, 0x22, 0x79, 0x0a, 0x1b, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x4d, 0x69, + 0x66, 0x61, 0x70, 0x61, 0x79, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, + 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x65, 0x78, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x54, 0x65, 0x78, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x22, + 0xf9, 0x01, 0x0a, 0x18, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x56, 0x35, 0x50, 0x61, 0x79, 0x4e, + 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, + 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, + 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x61, 0x77, 0x5f, 0x6a, 0x73, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x61, 0x77, 0x4a, 0x73, 0x6f, + 0x6e, 0x12, 0x4d, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x56, 0x35, 0x50, 0x61, 0x79, 0x4e, + 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, + 0x1a, 0x39, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x77, 0x0a, 0x19, 0x48, + 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x56, 0x35, 0x50, 0x61, 0x79, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x65, + 0x70, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x63, 0x63, 0x65, + 0x70, 0x74, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x5f, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x54, 0x65, 0x78, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x49, 0x64, 0x22, 0xde, 0x01, 0x0a, 0x13, 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, + 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x23, 0x0a, 0x0d, + 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, 0x72, 0x6f, 0x6d, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x74, 0x6f, 0x5f, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x74, 0x6f, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, + 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0a, 0x66, 0x72, 0x6f, 0x6d, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, + 0x0a, 0x09, 0x74, 0x6f, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x08, 0x74, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x74, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x44, 0x69, 0x61, 0x6d, + 0x6f, 0x6e, 0x64, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, + 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, + 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x5e, 0x0a, 0x20, 0x47, + 0x65, 0x74, 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3a, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x52, 0x75, 0x6c, 0x65, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x22, 0xfa, 0x03, 0x0a, 0x11, + 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, + 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x69, 0x7a, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x69, 0x7a, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, - 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x7e, - 0x0a, 0x1e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x46, 0x0a, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x6e, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0xb1, - 0x02, 0x0a, 0x0d, 0x56, 0x69, 0x70, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, - 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, - 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6f, - 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x22, 0x0a, 0x0d, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, - 0x1b, 0x0a, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, - 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, - 0x0d, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, - 0x72, 0x6c, 0x22, 0xfc, 0x04, 0x0a, 0x08, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, + 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x27, 0x0a, + 0x0f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, + 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x72, 0x6f, 0x7a, 0x65, 0x6e, + 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x72, + 0x6f, 0x7a, 0x65, 0x6e, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x76, 0x61, + 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x66, 0x74, + 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x72, 0x6f, 0x7a, 0x65, 0x6e, 0x5f, 0x61, 0x66, 0x74, + 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x72, 0x6f, 0x7a, 0x65, 0x6e, + 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, + 0x70, 0x61, 0x72, 0x74, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x12, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x61, 0x72, 0x74, + 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, + 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, + 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, + 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, + 0x5f, 0x75, 0x73, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x10, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x55, 0x73, 0x64, 0x4d, 0x69, 0x6e, + 0x6f, 0x72, 0x12, 0x34, 0x0a, 0x16, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x63, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0d, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x14, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x22, 0xc2, 0x01, 0x0a, 0x1d, 0x4c, 0x69, 0x73, + 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, + 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, + 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x70, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, + 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x7e, 0x0a, + 0x1e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x46, 0x0a, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0xb1, 0x02, + 0x0a, 0x0d, 0x56, 0x69, 0x70, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, + 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, + 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, + 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, + 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1b, + 0x0a, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, + 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, + 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, + 0x6c, 0x22, 0xdb, 0x04, 0x0a, 0x10, 0x56, 0x69, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6c, 0x65, 0x76, 0x65, 0x6c, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x37, 0x0a, 0x18, 0x73, 0x61, 0x6d, 0x65, 0x5f, 0x6c, 0x65, + 0x76, 0x65, 0x6c, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x73, 0x61, 0x6d, 0x65, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x32, + 0x0a, 0x15, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, + 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x75, + 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x12, 0x3a, 0x0a, 0x19, 0x64, 0x6f, 0x77, 0x6e, 0x67, 0x72, 0x61, 0x64, 0x65, 0x5f, + 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x64, 0x6f, 0x77, 0x6e, 0x67, 0x72, 0x61, 0x64, 0x65, + 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x3c, + 0x0a, 0x1a, 0x62, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, 0x5f, 0x69, 0x6e, 0x68, 0x65, 0x72, 0x69, + 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x18, 0x62, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, 0x49, 0x6e, 0x68, 0x65, 0x72, + 0x69, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1d, 0x0a, 0x0a, + 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x74, + 0x72, 0x69, 0x61, 0x6c, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x74, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, + 0x72, 0x64, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, 0x13, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, + 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, + 0xe0, 0x03, 0x0a, 0x0a, 0x56, 0x69, 0x70, 0x42, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, 0x12, 0x21, + 0x0a, 0x0c, 0x62, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x65, 0x6e, + 0x65, 0x66, 0x69, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x75, 0x6e, 0x6c, 0x6f, + 0x63, 0x6b, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, + 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x72, 0x69, 0x61, 0x6c, 0x5f, 0x65, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x74, 0x72, 0x69, 0x61, + 0x6c, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x27, + 0x0a, 0x0f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x63, 0x6f, 0x70, + 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x6f, 0x5f, + 0x65, 0x71, 0x75, 0x69, 0x70, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x75, 0x74, + 0x6f, 0x45, 0x71, 0x75, 0x69, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, + 0x72, 0x64, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, + 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, + 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x4d, 0x73, 0x22, 0xdc, 0x05, 0x0a, 0x08, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, @@ -25014,7 +28962,13 @@ var file_proto_wallet_v1_wallet_proto_rawDesc = []byte{ 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, - 0x73, 0x22, 0xd0, 0x01, 0x0a, 0x07, 0x55, 0x73, 0x65, 0x72, 0x56, 0x69, 0x70, 0x12, 0x17, 0x0a, + 0x73, 0x12, 0x37, 0x0a, 0x08, 0x62, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, 0x73, 0x18, 0x10, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x42, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, + 0x52, 0x08, 0x62, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x11, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x22, 0x9a, 0x02, 0x0a, 0x07, 0x55, 0x73, 0x65, 0x72, 0x56, 0x69, 0x70, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x04, @@ -25027,97 +28981,172 @@ var file_proto_wallet_v1_wallet_proto_rawDesc = []byte{ 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x4d, 0x73, 0x22, 0x6b, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x69, 0x70, 0x50, - 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, - 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, + 0x41, 0x74, 0x4d, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x67, + 0x72, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xa5, + 0x04, 0x0a, 0x0c, 0x56, 0x69, 0x70, 0x54, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, 0x72, 0x64, 0x12, + 0x22, 0x0a, 0x0d, 0x74, 0x72, 0x69, 0x61, 0x6c, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, 0x72, + 0x64, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x74, + 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x71, 0x75, 0x69, 0x70, 0x70, + 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x71, 0x75, 0x69, 0x70, 0x70, + 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, + 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x65, 0x66, + 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, + 0x32, 0x0a, 0x15, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, + 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x4d, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, + 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x22, + 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, + 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x5f, 0x6d, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xe5, 0x01, 0x0a, 0x0f, 0x56, 0x69, 0x70, 0x55, 0x73, + 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, + 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, + 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x39, + 0x0a, 0x19, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x6e, 0x6f, 0x74, + 0x69, 0x63, 0x65, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x16, 0x72, 0x6f, 0x6f, 0x6d, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x4e, 0x6f, 0x74, 0x69, + 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x3f, 0x0a, 0x1c, 0x6f, 0x6e, 0x6c, + 0x69, 0x6e, 0x65, 0x5f, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x63, + 0x65, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x19, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, + 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xfd, + 0x03, 0x0a, 0x08, 0x56, 0x69, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x33, 0x0a, 0x08, 0x70, + 0x61, 0x69, 0x64, 0x5f, 0x76, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x73, 0x65, 0x72, 0x56, 0x69, 0x70, 0x52, 0x07, 0x70, 0x61, 0x69, 0x64, 0x56, 0x69, 0x70, + 0x12, 0x4d, 0x0a, 0x13, 0x65, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x74, 0x72, 0x69, + 0x61, 0x6c, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x56, 0x69, 0x70, 0x54, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, 0x72, 0x64, 0x52, 0x11, 0x65, 0x71, + 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x54, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, 0x72, 0x64, 0x12, + 0x3d, 0x0a, 0x0d, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x76, 0x69, 0x70, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x56, 0x69, 0x70, + 0x52, 0x0c, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x56, 0x69, 0x70, 0x12, 0x29, + 0x0a, 0x10, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, + 0x69, 0x76, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x65, 0x66, 0x66, + 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x62, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, 0x73, 0x18, + 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x42, 0x65, 0x6e, 0x65, 0x66, + 0x69, 0x74, 0x52, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x42, 0x65, 0x6e, + 0x65, 0x66, 0x69, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, + 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x48, 0x0a, + 0x0e, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, + 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, + 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x45, 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x56, 0x69, 0x70, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x52, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x6b, + 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x69, 0x70, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, + 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, + 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x86, 0x02, 0x0a, 0x17, + 0x4c, 0x69, 0x73, 0x74, 0x56, 0x69, 0x70, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x74, 0x5f, 0x76, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, + 0x73, 0x65, 0x72, 0x56, 0x69, 0x70, 0x52, 0x0a, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x56, + 0x69, 0x70, 0x12, 0x35, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, + 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x48, 0x0a, 0x0e, 0x70, 0x72, + 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x22, 0x64, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x56, 0x69, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x6f, 0x0a, 0x10, 0x47, 0x65, + 0x74, 0x4d, 0x79, 0x56, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, + 0x0a, 0x03, 0x76, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, + 0x65, 0x72, 0x56, 0x69, 0x70, 0x52, 0x03, 0x76, 0x69, 0x70, 0x12, 0x2f, 0x0a, 0x05, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x7d, 0x0a, 0x12, 0x50, + 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x56, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0xc4, 0x02, 0x0a, 0x13, 0x50, + 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x56, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, + 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x03, 0x76, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x56, 0x69, 0x70, 0x52, 0x03, 0x76, 0x69, 0x70, + 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, + 0x2c, 0x0a, 0x12, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, + 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x69, + 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x41, 0x0a, + 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x06, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, + 0x74, 0x65, 0x6d, 0x52, 0x0b, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x73, + 0x12, 0x2f, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x56, 0x69, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x22, 0xd3, 0x01, 0x0a, 0x18, 0x44, 0x65, 0x62, 0x69, 0x74, 0x43, 0x50, 0x42, 0x72, 0x65, + 0x61, 0x6b, 0x75, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x22, 0x8b, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x69, 0x70, 0x50, 0x61, 0x63, - 0x6b, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, - 0x0b, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x56, 0x69, 0x70, 0x52, 0x0a, 0x63, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x74, 0x56, 0x69, 0x70, 0x12, 0x35, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, - 0x61, 0x67, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, - 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x22, - 0x64, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x56, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, - 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x3e, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x56, 0x69, - 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x03, 0x76, 0x69, 0x70, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x56, 0x69, 0x70, - 0x52, 0x03, 0x76, 0x69, 0x70, 0x22, 0x7d, 0x0a, 0x12, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, - 0x65, 0x56, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, - 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, - 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, - 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, - 0x65, 0x76, 0x65, 0x6c, 0x22, 0x93, 0x02, 0x0a, 0x13, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, - 0x65, 0x56, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2a, - 0x0a, 0x03, 0x76, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, - 0x65, 0x72, 0x56, 0x69, 0x70, 0x52, 0x03, 0x76, 0x69, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, - 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, - 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x6f, 0x69, - 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x69, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x56, 0x69, 0x70, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x0b, 0x72, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xd3, 0x01, 0x0a, 0x18, 0x44, - 0x65, 0x62, 0x69, 0x74, 0x43, 0x50, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x75, 0x70, 0x46, 0x65, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, - 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, - 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, - 0x70, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x22, 0xc8, 0x01, 0x0a, 0x19, 0x44, 0x65, 0x62, 0x69, 0x74, 0x43, 0x50, 0x42, 0x72, 0x65, 0x61, - 0x6b, 0x75, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, - 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x70, - 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x53, - 0x70, 0x65, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x62, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x10, 0x63, 0x6f, 0x69, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, - 0x65, 0x72, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x22, 0xbc, 0x01, 0x0a, 0x15, - 0x44, 0x65, 0x62, 0x69, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x77, 0x68, 0x65, 0x65, - 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x77, 0x68, 0x65, 0x65, - 0x6c, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x72, 0x61, 0x77, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc5, 0x01, 0x0a, 0x16, 0x44, - 0x65, 0x62, 0x69, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, + 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, + 0x70, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc8, 0x01, 0x0a, 0x19, 0x44, 0x65, 0x62, 0x69, + 0x74, 0x43, 0x50, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x75, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, @@ -25129,171 +29158,558 @@ var file_proto_wallet_v1_wallet_proto_rawDesc = []byte{ 0x61, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x22, 0xec, 0x01, 0x0a, 0x0f, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x56, 0x69, 0x70, 0x52, + 0x63, 0x65, 0x22, 0xbc, 0x01, 0x0a, 0x15, 0x44, 0x65, 0x62, 0x69, 0x74, 0x57, 0x68, 0x65, 0x65, + 0x6c, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, + 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, + 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x19, 0x0a, 0x08, 0x77, 0x68, 0x65, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x77, 0x68, 0x65, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x72, + 0x61, 0x77, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, + 0x64, 0x72, 0x61, 0x77, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x22, 0xc5, 0x01, 0x0a, 0x16, 0x44, 0x65, 0x62, 0x69, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, + 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, + 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x6e, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x70, 0x65, + 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, + 0x63, 0x6f, 0x69, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, + 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x22, 0xec, 0x01, 0x0a, 0x0f, 0x47, 0x72, + 0x61, 0x6e, 0x74, 0x56, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, + 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, + 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, + 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, + 0x76, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, + 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xff, 0x01, 0x0a, 0x10, 0x47, 0x72, 0x61, + 0x6e, 0x74, 0x56, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, + 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x03, 0x76, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x56, 0x69, 0x70, 0x52, 0x03, 0x76, 0x69, 0x70, + 0x12, 0x41, 0x0a, 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x52, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x0b, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, + 0x65, 0x6d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x2f, 0x0a, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x56, 0x0a, 0x1a, 0x47, 0x65, + 0x74, 0x56, 0x69, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, + 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, + 0x64, 0x65, 0x22, 0xb7, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x56, 0x69, 0x70, 0x50, 0x72, 0x6f, + 0x67, 0x72, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x37, 0x0a, + 0x08, 0x62, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x56, 0x69, 0x70, 0x42, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, 0x52, 0x08, 0x62, 0x65, + 0x6e, 0x65, 0x66, 0x69, 0x74, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xc3, 0x01, 0x0a, + 0x22, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x50, + 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x39, 0x0a, + 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x56, 0x69, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x22, 0x86, 0x01, 0x0a, 0x23, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x6d, + 0x69, 0x6e, 0x56, 0x69, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, + 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xb4, 0x02, 0x0a, 0x1a, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x79, 0x56, 0x69, 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, + 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, + 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x3e, 0x0a, + 0x19, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x6e, 0x6f, 0x74, 0x69, + 0x63, 0x65, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, + 0x48, 0x00, 0x52, 0x16, 0x72, 0x6f, 0x6f, 0x6d, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x4e, 0x6f, 0x74, + 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x88, 0x01, 0x01, 0x12, 0x44, 0x0a, + 0x1c, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x6e, + 0x6f, 0x74, 0x69, 0x63, 0x65, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x08, 0x48, 0x01, 0x52, 0x19, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x47, 0x6c, 0x6f, + 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x88, 0x01, 0x01, 0x42, 0x1c, 0x0a, 0x1a, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x65, 0x6e, 0x74, + 0x72, 0x79, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x42, 0x1f, 0x0a, 0x1d, 0x5f, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x67, 0x6c, 0x6f, + 0x62, 0x61, 0x6c, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x22, 0x81, 0x01, 0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x79, 0x56, + 0x69, 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, + 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xd2, 0x03, 0x0a, 0x15, 0x56, 0x69, 0x70, 0x44, 0x61, + 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x62, 0x61, 0x74, 0x65, 0x52, 0x75, 0x6e, + 0x12, 0x15, 0x0a, 0x06, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x72, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x61, 0x73, 0x6b, 0x5f, + 0x64, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x44, + 0x61, 0x79, 0x12, 0x20, 0x0a, 0x0c, 0x64, 0x61, 0x79, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x61, 0x79, 0x53, 0x74, 0x61, + 0x72, 0x74, 0x4d, 0x73, 0x12, 0x1c, 0x0a, 0x0a, 0x64, 0x61, 0x79, 0x5f, 0x65, 0x6e, 0x64, 0x5f, + 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x64, 0x61, 0x79, 0x45, 0x6e, 0x64, + 0x4d, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x6c, 0x65, 0x76, + 0x65, 0x6c, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x73, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x20, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, + 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, + 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f, + 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xe9, 0x03, 0x0a, 0x12, + 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x62, 0x61, + 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x62, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x62, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12, + 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x61, 0x73, 0x6b, + 0x5f, 0x64, 0x61, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x61, 0x73, 0x6b, + 0x44, 0x61, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x69, 0x70, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x76, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x12, 0x19, 0x0a, 0x08, 0x76, 0x69, 0x70, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x76, 0x69, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, + 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, + 0x65, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x61, + 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, + 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, + 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x6c, 0x61, 0x69, 0x6d, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, + 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x9b, 0x01, 0x0a, 0x25, 0x50, 0x72, 0x6f, 0x63, + 0x65, 0x73, 0x73, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, + 0x65, 0x62, 0x61, 0x74, 0x65, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x74, + 0x61, 0x73, 0x6b, 0x5f, 0x64, 0x61, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, + 0x61, 0x73, 0x6b, 0x44, 0x61, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, + 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x62, 0x61, 0x74, 0x63, + 0x68, 0x53, 0x69, 0x7a, 0x65, 0x22, 0xed, 0x01, 0x0a, 0x26, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, + 0x73, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x62, + 0x61, 0x74, 0x65, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x38, 0x0a, 0x03, 0x72, 0x75, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x62, 0x61, + 0x74, 0x65, 0x52, 0x75, 0x6e, 0x52, 0x03, 0x72, 0x75, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x63, + 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0c, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x23, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x68, 0x61, 0x73, 0x5f, 0x6d, 0x6f, 0x72, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x61, 0x73, 0x4d, 0x6f, 0x72, 0x65, 0x12, + 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, + 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, + 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x7a, 0x0a, 0x25, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x43, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x74, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x69, + 0x6e, 0x52, 0x65, 0x62, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, + 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x22, 0xa1, 0x01, 0x0a, 0x26, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x43, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x74, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, + 0x62, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x75, + 0x6e, 0x64, 0x12, 0x3b, 0x0a, 0x06, 0x72, 0x65, 0x62, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x69, + 0x6e, 0x52, 0x65, 0x62, 0x61, 0x74, 0x65, 0x52, 0x06, 0x72, 0x65, 0x62, 0x61, 0x74, 0x65, 0x12, + 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, + 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xcc, 0x01, 0x0a, 0x27, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x79, + 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x62, 0x61, + 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, + 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x62, 0x61, 0x74, 0x65, 0x5f, + 0x69, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x62, 0x61, 0x74, + 0x65, 0x49, 0x64, 0x73, 0x22, 0xa5, 0x01, 0x0a, 0x28, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x79, 0x56, + 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x62, 0x61, 0x74, + 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3d, 0x0a, 0x07, 0x72, 0x65, 0x62, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x69, + 0x6e, 0x52, 0x65, 0x62, 0x61, 0x74, 0x65, 0x52, 0x07, 0x72, 0x65, 0x62, 0x61, 0x74, 0x65, 0x73, + 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x90, 0x01, 0x0a, + 0x1e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, + 0x69, 0x6e, 0x52, 0x65, 0x62, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, + 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x62, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x62, 0x61, 0x74, 0x65, 0x49, 0x64, 0x22, + 0xed, 0x01, 0x0a, 0x1f, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, + 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x62, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x72, 0x65, 0x62, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, + 0x69, 0x6e, 0x52, 0x65, 0x62, 0x61, 0x74, 0x65, 0x52, 0x06, 0x72, 0x65, 0x62, 0x61, 0x74, 0x65, + 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x0c, 0x63, 0x6f, 0x69, 0x6e, 0x5f, + 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x0b, 0x63, 0x6f, + 0x69, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, + 0xcc, 0x02, 0x0a, 0x18, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x56, 0x69, 0x70, 0x54, 0x72, 0x69, 0x61, + 0x6c, 0x43, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, + 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, + 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, + 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, + 0x65, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, + 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, + 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x0c, 0x67, 0x72, 0x61, + 0x6e, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x28, 0x0a, 0x10, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x42, 0x0e, + 0x0a, 0x0c, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x22, 0xb0, + 0x01, 0x0a, 0x19, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x56, 0x69, 0x70, 0x54, 0x72, 0x69, 0x61, 0x6c, + 0x43, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x0a, + 0x74, 0x72, 0x69, 0x61, 0x6c, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x54, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, 0x72, 0x64, 0x52, + 0x09, 0x74, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, 0x72, 0x64, 0x12, 0x2f, 0x0a, 0x05, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, + 0x73, 0x22, 0x94, 0x01, 0x0a, 0x18, 0x45, 0x71, 0x75, 0x69, 0x70, 0x56, 0x69, 0x70, 0x54, 0x72, + 0x69, 0x61, 0x6c, 0x43, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, + 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0xb0, 0x01, 0x0a, 0x19, 0x45, 0x71, 0x75, + 0x69, 0x70, 0x56, 0x69, 0x70, 0x54, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, 0x72, 0x64, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x0a, 0x74, 0x72, 0x69, 0x61, 0x6c, 0x5f, + 0x63, 0x61, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, + 0x54, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, 0x72, 0x64, 0x52, 0x09, 0x74, 0x72, 0x69, 0x61, 0x6c, + 0x43, 0x61, 0x72, 0x64, 0x12, 0x2f, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x6f, 0x0a, 0x1a, 0x55, + 0x6e, 0x65, 0x71, 0x75, 0x69, 0x70, 0x56, 0x69, 0x70, 0x54, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, + 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, + 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x94, 0x01, 0x0a, + 0x1b, 0x55, 0x6e, 0x65, 0x71, 0x75, 0x69, 0x70, 0x56, 0x69, 0x70, 0x54, 0x72, 0x69, 0x61, 0x6c, + 0x43, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, + 0x75, 0x6e, 0x65, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0a, 0x75, 0x6e, 0x65, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x12, 0x2f, 0x0a, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, + 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x24, 0x0a, + 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, + 0x65, 0x4d, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x16, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x56, 0x69, 0x70, + 0x42, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, + 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, + 0x43, 0x6f, 0x64, 0x65, 0x22, 0xe8, 0x01, 0x0a, 0x17, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x56, 0x69, + 0x70, 0x42, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x35, 0x0a, 0x07, 0x62, 0x65, + 0x6e, 0x65, 0x66, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, + 0x70, 0x42, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, 0x52, 0x07, 0x62, 0x65, 0x6e, 0x65, 0x66, 0x69, + 0x74, 0x12, 0x2f, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x6e, 0x69, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x6e, 0x69, 0x61, + 0x6c, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x76, 0x61, 0x6c, 0x75, + 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0d, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, + 0xea, 0x02, 0x0a, 0x12, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x69, 0x63, + 0x65, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x70, 0x72, + 0x69, 0x63, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x37, 0x0a, 0x18, 0x72, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x72, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x12, 0x41, 0x0a, 0x1d, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x63, + 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1a, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, + 0x64, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x37, 0x0a, 0x08, 0x62, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, 0x73, 0x18, + 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x42, 0x65, 0x6e, 0x65, 0x66, + 0x69, 0x74, 0x52, 0x08, 0x62, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, 0x73, 0x22, 0x55, 0x0a, 0x19, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, + 0x6f, 0x64, 0x65, 0x22, 0xbf, 0x01, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, + 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x06, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x48, 0x0a, 0x0e, 0x70, + 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xbe, 0x01, 0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x3b, 0x0a, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x49, + 0x6e, 0x70, 0x75, 0x74, 0x52, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x12, 0x28, 0x0a, 0x10, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xc1, 0x01, 0x0a, 0x1c, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, + 0x12, 0x48, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x50, 0x72, + 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x70, 0x72, 0x6f, + 0x67, 0x72, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xa8, 0x02, 0x0a, 0x17, 0x43, + 0x72, 0x65, 0x64, 0x69, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, - 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, - 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x22, 0xce, 0x01, 0x0a, 0x10, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x56, 0x69, 0x70, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, - 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2a, 0x0a, - 0x03, 0x76, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, - 0x72, 0x56, 0x69, 0x70, 0x52, 0x03, 0x76, 0x69, 0x70, 0x12, 0x41, 0x0a, 0x0c, 0x72, 0x65, 0x77, - 0x61, 0x72, 0x64, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x56, 0x69, 0x70, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, - 0x0b, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x24, 0x0a, 0x0e, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, - 0x4d, 0x73, 0x22, 0xb1, 0x02, 0x0a, 0x12, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, - 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, - 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x70, - 0x72, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x09, 0x70, 0x72, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0a, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x37, 0x0a, 0x18, 0x72, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, - 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x72, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, - 0x75, 0x70, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, - 0x64, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x1d, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, - 0x72, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1a, 0x72, 0x65, 0x71, 0x75, - 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x43, 0x6f, 0x69, 0x6e, - 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x55, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, - 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x75, 0x0a, - 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x6c, - 0x65, 0x76, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, - 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x12, 0x24, - 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, - 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xbe, 0x01, 0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, - 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x3b, - 0x0a, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x49, 0x6e, - 0x70, 0x75, 0x74, 0x52, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x77, 0x0a, 0x1c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, - 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, - 0x52, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xa8, - 0x02, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x77, - 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x61, 0x73, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x79, 0x63, 0x6c, - 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x79, 0x63, - 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, - 0x11, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xb6, 0x01, 0x0a, 0x18, 0x43, 0x72, - 0x65, 0x64, 0x69, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, - 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, - 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, - 0x0a, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x4d, 0x73, 0x22, 0xdd, 0x02, 0x0a, 0x1c, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4c, 0x75, 0x63, - 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, - 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, - 0x72, 0x61, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, - 0x61, 0x77, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x17, 0x0a, - 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, - 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, - 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, + 0x0a, 0x09, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x74, 0x61, 0x73, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, + 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, + 0x73, 0x6b, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x4b, 0x65, + 0x79, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xb6, 0x01, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, + 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x72, + 0x61, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xdd, + 0x02, 0x0a, 0x1c, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, + 0x66, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, + 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x61, 0x77, 0x5f, + 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x61, 0x77, 0x49, 0x64, + 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, + 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, + 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, + 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x22, 0xbb, + 0x01, 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, + 0x66, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x72, 0x61, 0x6e, + 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xb4, 0x02, 0x0a, + 0x18, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, + 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, + 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x61, 0x77, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x77, 0x68, + 0x65, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x77, 0x68, + 0x65, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, + 0x64, 0x5f, 0x74, 0x69, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x54, 0x69, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, - 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, + 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, - 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, - 0x49, 0x64, 0x22, 0xbb, 0x01, 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4c, 0x75, 0x63, - 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, 0x62, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, - 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, - 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, - 0x22, 0xb4, 0x02, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, - 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, - 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, - 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, - 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x64, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x61, 0x77, 0x49, 0x64, 0x12, 0x19, - 0x0a, 0x08, 0x77, 0x68, 0x65, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x77, 0x68, 0x65, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x54, 0x69, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x76, - 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xb7, 0x01, 0x0a, 0x19, 0x43, 0x72, 0x65, 0x64, - 0x69, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, + 0x6e, 0x49, 0x64, 0x22, 0xb7, 0x01, 0x0a, 0x19, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x57, 0x68, + 0x65, 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x72, 0x61, + 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x90, 0x03, + 0x0a, 0x1f, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, + 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x74, + 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x17, + 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x72, 0x69, 0x6f, + 0x64, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0d, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x4d, 0x73, 0x12, + 0x22, 0x0a, 0x0d, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x65, 0x6e, 0x64, 0x5f, 0x6d, 0x73, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x45, 0x6e, + 0x64, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x6e, + 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x70, 0x65, + 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x69, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x06, 0x74, 0x69, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x74, + 0x69, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x74, 0x69, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x22, 0xbe, 0x01, 0x0a, 0x20, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, + 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, @@ -25304,80 +29720,8 @@ var file_proto_wallet_v1_wallet_proto_rawDesc = []byte{ 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, - 0x73, 0x22, 0x90, 0x03, 0x0a, 0x1f, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x52, 0x6f, 0x6f, 0x6d, - 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, - 0x0d, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x70, - 0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x65, 0x6e, - 0x64, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x69, - 0x6f, 0x64, 0x45, 0x6e, 0x64, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, - 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, - 0x6e, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x69, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x74, 0x69, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xbe, 0x01, 0x0a, 0x20, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x52, - 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, - 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, - 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, - 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, - 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xe7, 0x02, 0x0a, 0x21, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, - 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, - 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, - 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x69, 0x64, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x49, 0x64, 0x12, 0x1f, - 0x0a, 0x0b, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x17, 0x0a, 0x07, 0x74, 0x69, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x06, 0x74, 0x69, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x65, 0x72, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x65, - 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x4b, - 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x61, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x65, 0x61, 0x63, 0x68, - 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, - 0xc0, 0x01, 0x0a, 0x22, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, - 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, - 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, - 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, - 0x0a, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x4d, 0x73, 0x22, 0xcb, 0x02, 0x0a, 0x20, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x41, 0x67, 0x65, - 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x73, 0x22, 0xe7, 0x02, 0x0a, 0x21, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x49, 0x6e, 0x76, 0x69, + 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, @@ -25386,521 +29730,767 @@ var file_proto_wallet_v1_wallet_proto_rawDesc = []byte{ 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x25, 0x0a, 0x0e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, - 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x49, - 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x12, 0x17, - 0x0a, 0x07, 0x72, 0x61, 0x6e, 0x6b, 0x5f, 0x6e, 0x6f, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x06, 0x72, 0x61, 0x6e, 0x6b, 0x4e, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x63, 0x6f, 0x72, 0x65, - 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x63, - 0x6f, 0x72, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, - 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x22, 0xbf, 0x01, 0x0a, 0x21, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, - 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, - 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, - 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, - 0x0a, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x4d, 0x73, 0x22, 0x9a, 0x03, 0x0a, 0x1a, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x47, 0x61, 0x6d, 0x65, - 0x43, 0x6f, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, - 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x6c, 0x61, 0x74, - 0x66, 0x6f, 0x72, 0x6d, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x61, 0x6d, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x61, 0x6d, 0x65, 0x49, - 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, - 0x11, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, - 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x6f, 0x70, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x70, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0c, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x61, 0x73, 0x68, 0x22, - 0xa3, 0x01, 0x0a, 0x1b, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x47, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x69, - 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x32, 0x0a, 0x15, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, - 0x66, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x62, 0x61, 0x6c, 0x61, - 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x64, 0x65, 0x6d, - 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x10, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x70, 0x6c, 0x61, 0x79, 0x22, 0x9f, 0x03, 0x0a, 0x0f, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, - 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1f, - 0x0a, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x05, 0x52, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x69, 0x65, 0x72, 0x73, 0x12, - 0x21, 0x0a, 0x0c, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x65, 0x72, 0x73, 0x18, - 0x04, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x69, 0x65, - 0x72, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x5f, 0x6f, 0x70, - 0x65, 0x6e, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x12, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x65, 0x63, - 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x5f, 0x73, - 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x65, 0x78, - 0x70, 0x69, 0x72, 0x65, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x64, - 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x53, 0x65, 0x6e, 0x64, - 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2d, 0x0a, 0x13, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x10, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, 0x6d, - 0x69, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x19, 0x0a, 0x08, - 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x72, 0x75, 0x6c, 0x65, 0x55, 0x72, 0x6c, 0x22, 0xee, 0x02, 0x0a, 0x0e, 0x52, 0x65, 0x64, 0x50, - 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, - 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, - 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x49, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, - 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, - 0x15, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, - 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x61, 0x69, - 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, - 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xa0, 0x06, 0x0a, 0x09, 0x52, 0x65, 0x64, - 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, - 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, - 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1d, - 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, - 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, - 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x63, - 0x6b, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, - 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x6d, 0x61, - 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x72, - 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0c, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x0a, - 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x08, 0x6f, 0x70, 0x65, 0x6e, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, - 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, - 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x5f, 0x6d, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, - 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x42, 0x79, 0x56, 0x69, 0x65, 0x77, - 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x13, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x63, 0x6c, 0x61, - 0x69, 0x6d, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x11, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x41, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, 0x13, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6c, - 0x61, 0x69, 0x6d, 0x52, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x72, - 0x65, 0x66, 0x75, 0x6e, 0x64, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x14, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x65, - 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x16, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x72, - 0x65, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x55, 0x0a, 0x19, 0x47, - 0x65, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, - 0x64, 0x65, 0x22, 0x7c, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, - 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x38, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, - 0x22, 0xfe, 0x02, 0x0a, 0x1c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, - 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, - 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, - 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, - 0x69, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x05, 0x52, 0x0a, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x54, 0x69, 0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x5f, 0x74, 0x69, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x69, 0x65, 0x72, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x64, 0x65, 0x6c, - 0x61, 0x79, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, - 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, - 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x65, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x53, 0x65, 0x63, 0x6f, 0x6e, - 0x64, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x73, 0x65, 0x6e, 0x64, - 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x64, 0x61, - 0x69, 0x6c, 0x79, 0x53, 0x65, 0x6e, 0x64, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x28, 0x0a, 0x10, - 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x75, - 0x72, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x55, 0x72, - 0x6c, 0x22, 0x7f, 0x0a, 0x1d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, - 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x38, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0e, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, - 0x4d, 0x73, 0x22, 0xb4, 0x02, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x64, - 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, + 0x19, 0x0a, 0x08, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, + 0x69, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x74, 0x69, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x65, 0x72, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x23, + 0x0a, 0x0d, 0x72, 0x65, 0x61, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x65, 0x61, 0x63, 0x68, 0x65, 0x64, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xc0, 0x01, 0x0a, 0x22, + 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, + 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x72, + 0x61, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xcb, + 0x02, 0x0a, 0x20, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, + 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, + 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x61, + 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, + 0x09, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x61, + 0x6e, 0x6b, 0x5f, 0x6e, 0x6f, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x72, 0x61, 0x6e, + 0x6b, 0x4e, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x5f, 0x63, 0x6f, 0x69, + 0x6e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x43, + 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xbf, 0x01, 0x0a, + 0x21, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, + 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, 0x62, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x72, + 0x61, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x9a, + 0x03, 0x0a, 0x1a, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x47, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x69, 0x6e, + 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, - 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x70, 0x61, - 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x98, 0x01, 0x0a, 0x17, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x06, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, - 0x74, 0x52, 0x06, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x24, - 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, - 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xa6, 0x01, 0x0a, 0x15, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x52, 0x65, - 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, - 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, - 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x63, 0x6b, 0x65, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, - 0x65, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xce, 0x01, - 0x0a, 0x16, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x63, 0x6c, 0x61, 0x69, - 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, - 0x6b, 0x65, 0x74, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x52, 0x05, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x12, + 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x23, 0x0a, 0x0d, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x69, 0x64, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x2a, 0x0a, + 0x11, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x6f, + 0x75, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x6f, 0x70, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x70, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, + 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x61, 0x73, 0x68, 0x22, 0xa3, 0x01, 0x0a, 0x1b, + 0x41, 0x70, 0x70, 0x6c, 0x79, 0x47, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, - 0x66, 0x74, 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x32, 0x0a, 0x06, 0x70, 0x61, - 0x63, 0x6b, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x64, - 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x06, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x22, 0xf9, - 0x02, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, - 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, - 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, - 0x0a, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, - 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0c, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1e, 0x0a, - 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1a, 0x0a, - 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x07, 0x65, 0x6e, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, - 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, - 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x8a, 0x01, 0x0a, 0x16, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, - 0x65, 0x74, 0x52, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, - 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, - 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xb9, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x52, - 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, - 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x63, - 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, - 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, - 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x43, 0x6c, 0x61, - 0x69, 0x6d, 0x73, 0x22, 0x70, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, - 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x06, 0x70, - 0x61, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x06, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, - 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, - 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x72, 0x0a, 0x17, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x52, - 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, - 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x61, - 0x74, 0x63, 0x68, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, - 0x62, 0x61, 0x74, 0x63, 0x68, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x8e, 0x01, 0x0a, 0x18, 0x45, 0x78, - 0x70, 0x69, 0x72, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, - 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x72, - 0x65, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x41, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x9e, 0x01, 0x0a, 0x1b, 0x52, - 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x66, - 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x49, - 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xa1, 0x01, 0x0a, 0x1c, - 0x52, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, - 0x66, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x06, - 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x06, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, - 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x66, 0x75, 0x6e, - 0x64, 0x65, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, - 0xc3, 0x02, 0x0a, 0x10, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, + 0x66, 0x74, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, + 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x10, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x61, + 0x79, 0x22, 0x9f, 0x03, 0x0a, 0x0f, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x05, 0x52, + 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x69, 0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x03, 0x52, 0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x69, 0x65, 0x72, 0x73, 0x12, 0x30, + 0x0a, 0x14, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x73, + 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x64, 0x65, + 0x6c, 0x61, 0x79, 0x65, 0x64, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, + 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, + 0x64, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, + 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x64, 0x61, 0x69, 0x6c, 0x79, + 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0e, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x53, 0x65, 0x6e, 0x64, 0x4c, 0x69, 0x6d, 0x69, + 0x74, 0x12, 0x2d, 0x0a, 0x13, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, + 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, + 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x75, 0x6c, 0x65, + 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x75, 0x6c, 0x65, + 0x55, 0x72, 0x6c, 0x22, 0xee, 0x02, 0x0a, 0x0e, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, + 0x74, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, + 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, + 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, + 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0d, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, + 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, + 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x4d, 0x73, 0x22, 0xa0, 0x06, 0x0a, 0x09, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, + 0x65, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x63, 0x6b, + 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63, + 0x6b, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x10, + 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, + 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x6d, 0x61, 0x69, + 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x0a, 0x6f, 0x70, 0x65, 0x6e, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6f, 0x70, + 0x65, 0x6e, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, + 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, + 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, + 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x4d, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x79, + 0x5f, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x63, + 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x42, 0x79, 0x56, 0x69, 0x65, 0x77, 0x65, 0x72, 0x12, 0x2e, + 0x0a, 0x13, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x76, 0x69, 0x65, + 0x77, 0x65, 0x72, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x37, + 0x0a, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x52, + 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x75, 0x6e, + 0x64, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, + 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x15, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x5f, 0x6d, 0x73, 0x18, 0x16, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x75, 0x6e, + 0x64, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x55, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x52, 0x65, + 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x15, - 0x0a, 0x06, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x72, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x73, 0x69, 0x7a, 0x65, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x62, 0x61, 0x74, 0x63, 0x68, 0x53, 0x69, 0x7a, - 0x65, 0x12, 0x1e, 0x0a, 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x74, 0x6c, 0x5f, 0x6d, 0x73, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x74, 0x6c, 0x4d, - 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x5f, 0x6d, 0x6f, 0x64, - 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, - 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, 0x6b, 0x65, - 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x4b, 0x65, - 0x79, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x09, 0x20, - 0x03, 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x27, 0x0a, 0x0f, - 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x22, 0xc6, 0x01, 0x0a, 0x11, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, - 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, - 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x0c, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x63, 0x65, - 0x73, 0x73, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x75, 0x63, - 0x63, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0c, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, - 0x0a, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x68, 0x61, 0x73, 0x5f, 0x6d, 0x6f, 0x72, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x61, 0x73, 0x4d, 0x6f, 0x72, 0x65, 0x22, 0xae, - 0x04, 0x0a, 0x19, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x50, - 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, - 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, - 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x73, 0x68, - 0x6f, 0x70, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0a, 0x73, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x35, 0x0a, - 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x64, 0x61, 0x79, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x64, 0x75, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x79, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x69, - 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x70, - 0x72, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x32, 0x0a, 0x15, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x64, - 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0f, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, - 0x82, 0x02, 0x0a, 0x25, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x53, 0x68, 0x6f, 0x70, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x7c, + 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x06, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xfe, 0x02, 0x0a, + 0x1c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, + 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, + 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x65, 0x72, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x05, 0x52, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x69, 0x65, + 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x65, + 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x54, 0x69, 0x65, 0x72, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, + 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x12, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x4f, 0x70, 0x65, 0x6e, + 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x65, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x28, + 0x0a, 0x10, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x53, + 0x65, 0x6e, 0x64, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x55, 0x72, 0x6c, 0x22, 0x7f, 0x0a, + 0x1d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, + 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xb4, + 0x02, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, + 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, - 0x77, 0x6f, 0x72, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, - 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, - 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, - 0x53, 0x69, 0x7a, 0x65, 0x22, 0x82, 0x01, 0x0a, 0x26, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, - 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x42, 0x0a, 0x06, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x50, 0x75, - 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x06, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x58, 0x0a, 0x1c, 0x47, 0x65, 0x74, - 0x47, 0x69, 0x66, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, - 0x6f, 0x64, 0x65, 0x22, 0x39, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, 0x61, - 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x32, 0xe0, - 0x02, 0x0a, 0x11, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x43, 0x72, 0x6f, 0x6e, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x6e, 0x0a, 0x25, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x48, - 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x53, 0x65, - 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x21, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, + 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, + 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, + 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, + 0x0a, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x21, 0x0a, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x98, 0x01, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x32, 0x0a, 0x06, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x06, 0x70, + 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x62, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, + 0x22, 0xa6, 0x01, 0x0a, 0x15, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, + 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, + 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, + 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xce, 0x01, 0x0a, 0x16, 0x43, 0x6c, + 0x61, 0x69, 0x6d, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, + 0x6c, 0x61, 0x69, 0x6d, 0x52, 0x05, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x23, 0x0a, 0x0d, 0x62, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0c, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, + 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, + 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x32, 0x0a, 0x06, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, + 0x65, 0x74, 0x52, 0x06, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x22, 0xf9, 0x02, 0x0a, 0x15, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, + 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, + 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, + 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, 0x69, 0x65, + 0x77, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1a, 0x0a, 0x09, 0x65, 0x6e, 0x64, + 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, 0x6e, + 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, + 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, + 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x8a, 0x01, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x52, + 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x34, 0x0a, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x07, + 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x24, 0x0a, + 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, + 0x65, 0x4d, 0x73, 0x22, 0xb9, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, + 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, + 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, + 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, + 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, 0x69, 0x65, 0x77, + 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x63, 0x6c, + 0x75, 0x64, 0x65, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x22, + 0x70, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x06, 0x70, 0x61, 0x63, 0x6b, 0x65, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, + 0x6b, 0x65, 0x74, 0x52, 0x06, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, + 0x73, 0x22, 0x72, 0x0a, 0x17, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, + 0x63, 0x6b, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, + 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, + 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, + 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x62, 0x61, 0x74, 0x63, + 0x68, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x8e, 0x01, 0x0a, 0x18, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, + 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x66, 0x75, 0x6e, + 0x64, 0x65, 0x64, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0e, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, + 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x9e, 0x01, 0x0a, 0x1b, 0x52, 0x65, 0x74, 0x72, 0x79, + 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, + 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x28, 0x0a, + 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, + 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xa1, 0x01, 0x0a, 0x1c, 0x52, 0x65, 0x74, 0x72, + 0x79, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x66, 0x75, 0x6e, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x06, 0x70, 0x61, 0x63, 0x6b, + 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x64, 0x50, 0x61, + 0x63, 0x6b, 0x65, 0x74, 0x52, 0x06, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x27, 0x0a, 0x0f, + 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x41, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xc3, 0x02, 0x0a, 0x10, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x29, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x48, - 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x48, 0x61, 0x6c, 0x66, 0x4d, 0x6f, 0x6e, - 0x74, 0x68, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, 0x1e, 0x50, 0x72, 0x6f, 0x63, - 0x65, 0x73, 0x73, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x4d, 0x6f, 0x6e, - 0x74, 0x68, 0x45, 0x6e, 0x64, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, - 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x32, 0x91, 0x60, 0x0a, 0x0d, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x12, 0x52, 0x0a, 0x09, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, - 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x0f, 0x44, 0x65, 0x62, 0x69, 0x74, - 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x47, 0x69, 0x66, 0x74, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, - 0x69, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x0e, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, - 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x0e, 0x44, 0x65, - 0x62, 0x69, 0x74, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x47, 0x69, 0x66, 0x74, 0x12, 0x26, 0x2e, 0x68, + 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, + 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x75, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x72, 0x75, 0x6e, 0x49, + 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x09, 0x62, 0x61, 0x74, 0x63, 0x68, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1e, 0x0a, + 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x74, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x09, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x74, 0x6c, 0x4d, 0x73, 0x12, 0x21, 0x0a, + 0x0c, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x65, + 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, + 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x03, 0x52, + 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x74, 0x74, + 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6c, + 0x65, 0x22, 0xc6, 0x01, 0x0a, 0x11, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x61, 0x69, 0x6d, + 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, + 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, + 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x73, 0x75, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, + 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x19, 0x0a, 0x08, 0x68, 0x61, 0x73, 0x5f, 0x6d, 0x6f, 0x72, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x68, 0x61, 0x73, 0x4d, 0x6f, 0x72, 0x65, 0x22, 0xae, 0x04, 0x0a, 0x19, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x50, 0x75, 0x72, 0x63, 0x68, + 0x61, 0x73, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, + 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, + 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x73, 0x68, 0x6f, 0x70, 0x5f, 0x69, + 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x68, + 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x08, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x79, + 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x44, 0x61, 0x79, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x63, + 0x6f, 0x69, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x70, 0x72, 0x69, 0x63, 0x65, + 0x43, 0x6f, 0x69, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x15, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x72, 0x61, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x82, 0x02, 0x0a, 0x25, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, + 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x12, + 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, + 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, + 0x22, 0x82, 0x01, 0x0a, 0x26, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x53, 0x68, 0x6f, 0x70, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x06, 0x6f, + 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, + 0x73, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x06, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, + 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x58, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, + 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, + 0x39, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, + 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x8d, 0x01, 0x0a, 0x10, 0x48, + 0x6f, 0x73, 0x74, 0x52, 0x65, 0x76, 0x65, 0x6e, 0x75, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, + 0x29, 0x0a, 0x10, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x5f, 0x65, 0x61, 0x72, 0x6e, 0x69, + 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x64, 0x69, 0x61, 0x6d, 0x6f, + 0x6e, 0x64, 0x45, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x69, + 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x5f, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x64, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x45, 0x78, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x67, 0x69, 0x66, 0x74, 0x5f, + 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x67, + 0x69, 0x66, 0x74, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x22, 0xb4, 0x01, 0x0a, 0x1a, 0x47, + 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x76, 0x65, 0x6e, 0x75, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, + 0x6f, 0x64, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x61, + 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1a, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x41, 0x74, 0x4d, + 0x73, 0x22, 0x56, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x76, 0x65, + 0x6e, 0x75, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x37, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x76, 0x65, 0x6e, 0x75, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x66, 0x0a, 0x15, 0x41, 0x67, 0x65, + 0x6e, 0x63, 0x79, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x68, 0x61, 0x72, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x65, 0x5f, 0x69, 0x6e, 0x63, 0x6f, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x73, 0x68, 0x61, 0x72, 0x65, 0x49, + 0x6e, 0x63, 0x6f, 0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x69, 0x66, 0x74, 0x65, 0x64, 0x5f, + 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x22, 0xc8, 0x01, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x50, + 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x68, 0x61, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x2f, 0x0a, 0x14, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x61, + 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x1e, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x41, 0x74, 0x4d, 0x73, + 0x12, 0x1a, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x60, 0x0a, 0x20, + 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x68, + 0x61, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3c, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x68, 0x61, + 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x62, + 0x0a, 0x13, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x47, 0x69, 0x66, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x6e, + 0x63, 0x6f, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x67, 0x69, 0x66, 0x74, + 0x49, 0x6e, 0x63, 0x6f, 0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x69, 0x66, 0x74, 0x65, 0x64, + 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x22, 0xb9, 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, + 0x48, 0x6f, 0x73, 0x74, 0x47, 0x69, 0x66, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x22, + 0x0a, 0x0d, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, + 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x41, 0x74, + 0x4d, 0x73, 0x12, 0x1a, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x5c, + 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x47, + 0x69, 0x66, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3a, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x47, 0x69, 0x66, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0xe6, 0x02, 0x0a, + 0x1f, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, + 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, + 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x63, 0x6f, + 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x10, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x13, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, + 0x43, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, + 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, + 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, + 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xaf, 0x01, 0x0a, 0x25, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, + 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x43, 0x6f, 0x69, + 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, + 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x29, 0x0a, 0x10, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x44, + 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x74, 0x0a, 0x26, 0x4c, 0x69, 0x73, 0x74, 0x50, + 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x43, 0x6f, + 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x4a, 0x0a, 0x07, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, + 0x61, 0x77, 0x61, 0x6c, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x22, 0x93, 0x02, + 0x0a, 0x20, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x54, + 0x6f, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, + 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6c, 0x6c, + 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x22, 0xf1, 0x02, 0x0a, 0x21, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x54, 0x6f, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x12, 0x3b, 0x0a, 0x1a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x17, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x30, 0x0a, + 0x14, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, + 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x73, 0x65, 0x6c, + 0x6c, 0x65, 0x72, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, + 0x21, 0x0a, 0x0c, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x69, + 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, + 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x2c, 0x0a, 0x12, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x37, + 0x0a, 0x18, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x63, + 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x15, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x69, + 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x8f, 0x01, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x50, + 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, + 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, + 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, + 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6e, 0x6f, 0x77, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x05, 0x6e, 0x6f, 0x77, 0x4d, 0x73, 0x22, 0xd0, 0x01, 0x0a, 0x20, 0x47, 0x65, + 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, + 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x5f, 0x70, + 0x65, 0x72, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x73, 0x50, 0x65, 0x72, 0x55, 0x73, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, + 0x65, 0x5f, 0x62, 0x70, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x66, 0x65, 0x65, + 0x42, 0x70, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x69, 0x6e, + 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x70, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x32, 0xf4, 0x03, 0x0a, + 0x11, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x43, 0x72, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x12, 0x6e, 0x0a, 0x25, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x48, 0x6f, 0x73, + 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x53, 0x65, 0x74, 0x74, + 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x21, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, + 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x72, 0x0a, 0x29, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x48, 0x6f, 0x73, + 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x48, 0x61, 0x6c, 0x66, 0x4d, 0x6f, 0x6e, 0x74, 0x68, + 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, + 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, 0x1e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, + 0x73, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x4d, 0x6f, 0x6e, 0x74, 0x68, + 0x45, 0x6e, 0x64, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x42, + 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, + 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x91, 0x01, 0x0a, 0x1e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x56, 0x69, 0x70, 0x44, 0x61, + 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x62, 0x61, 0x74, 0x65, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x12, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x56, 0x69, 0x70, 0x44, + 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x62, 0x61, 0x74, 0x65, 0x42, 0x61, + 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, + 0x63, 0x65, 0x73, 0x73, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x69, 0x6e, + 0x52, 0x65, 0x62, 0x61, 0x74, 0x65, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x32, 0xe9, 0x70, 0x0a, 0x0d, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x52, 0x0a, 0x09, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, + 0x66, 0x74, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x0f, 0x44, 0x65, 0x62, + 0x69, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x47, 0x69, 0x66, 0x74, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, - 0x65, 0x62, 0x69, 0x74, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x42, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x82, 0x01, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, - 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x12, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x48, 0x6f, 0x73, 0x74, - 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x48, - 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x48, 0x6f, - 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, - 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, - 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, - 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x65, 0x62, 0x69, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x0e, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, + 0x74, 0x63, 0x68, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x62, 0x69, 0x74, + 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x0e, + 0x44, 0x65, 0x62, 0x69, 0x74, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x47, 0x69, 0x66, 0x74, 0x12, 0x26, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, + 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0b, 0x47, 0x65, + 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x82, 0x01, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x12, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x48, 0x6f, + 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, + 0x65, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x15, 0x47, 0x65, 0x74, + 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, + 0x73, 0x73, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, + 0x72, 0x79, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, + 0x79, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x70, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x76, 0x65, + 0x6e, 0x75, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, + 0x73, 0x74, 0x52, 0x65, 0x76, 0x65, 0x6e, 0x75, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, + 0x65, 0x76, 0x65, 0x6e, 0x75, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x7f, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x68, 0x61, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, + 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x6f, 0x69, 0x6e, 0x74, + 0x53, 0x68, 0x61, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x53, 0x68, 0x61, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, + 0x79, 0x48, 0x6f, 0x73, 0x74, 0x47, 0x69, 0x66, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x2e, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x47, 0x69, + 0x66, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x47, 0x69, + 0x66, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x54, 0x65, 0x61, 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, @@ -25967,667 +30557,778 @@ var file_proto_wallet_v1_wallet_proto_rawDesc = []byte{ 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x54, 0x6f, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x79, 0x0a, 0x16, 0x46, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, - 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x72, 0x65, - 0x65, 0x7a, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, - 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x72, 0x65, - 0x65, 0x7a, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, - 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, 0x16, 0x53, - 0x65, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, - 0x72, 0x61, 0x77, 0x61, 0x6c, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x61, - 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x61, - 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7c, 0x0a, 0x17, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, - 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, - 0x6c, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, - 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x53, 0x61, 0x6c, 0x61, - 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x15, 0x46, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x50, 0x6f, - 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x12, 0x2d, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x46, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, - 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x46, - 0x72, 0x65, 0x65, 0x7a, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, - 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x15, - 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, - 0x72, 0x61, 0x77, 0x61, 0x6c, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x50, 0x6f, - 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x50, 0x6f, 0x69, - 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, 0x16, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x50, - 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x12, 0x2e, + 0x12, 0x91, 0x01, 0x0a, 0x1e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, + 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, + 0x65, 0x72, 0x73, 0x12, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, + 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, + 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, + 0x6c, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x82, 0x01, 0x0a, 0x19, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, + 0x72, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x54, 0x6f, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, + 0x65, 0x72, 0x12, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x54, 0x6f, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x54, 0x6f, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7f, 0x0a, 0x18, 0x47, 0x65, 0x74, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, + 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, 0x16, 0x46, 0x72, + 0x65, 0x65, 0x7a, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, + 0x61, 0x77, 0x61, 0x6c, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x53, 0x61, 0x6c, + 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x53, 0x61, 0x6c, + 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x53, + 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x12, + 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, + 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, + 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x7c, 0x0a, 0x17, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, + 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x12, 0x2f, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x6c, 0x65, 0x61, 0x73, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, + 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x53, 0x61, 0x6c, 0x61, 0x72, 0x79, 0x57, 0x69, 0x74, 0x68, + 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, + 0x0a, 0x15, 0x46, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, + 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x72, 0x65, 0x65, 0x7a, 0x65, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x50, + 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x12, + 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, + 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, - 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, - 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x5e, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x58, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x23, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x0e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x26, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x73, 0x0a, 0x14, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2c, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, - 0x74, 0x63, 0x68, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x2a, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, - 0x75, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, + 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, + 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, + 0x0a, 0x16, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, + 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, + 0x73, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, + 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, + 0x73, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, + 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x0d, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x28, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x6a, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, - 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x13, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, - 0x6f, 0x75, 0x70, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, - 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x0f, 0x4c, 0x69, - 0x73, 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, 0x27, 0x2e, + 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0b, 0x47, 0x65, 0x74, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x69, 0x66, - 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x70, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x69, - 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x69, 0x66, 0x74, 0x54, - 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x76, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, 0x61, 0x74, - 0x61, 0x6c, 0x6f, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x47, 0x69, 0x66, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x10, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x28, + 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x5b, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, + 0x11, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x5b, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x73, 0x0a, + 0x14, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x67, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x13, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x69, 0x66, 0x74, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, - 0x16, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x28, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, 0x13, 0x53, - 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x69, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x2b, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, + 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x70, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2e, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x69, 0x66, 0x74, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x69, + 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x13, 0x4c, 0x69, + 0x73, 0x74, 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x73, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x15, + 0x47, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, + 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, 0x61, + 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x14, 0x55, 0x70, 0x73, 0x65, 0x72, - 0x74, 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x0d, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x12, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x2a, 0x2e, 0x68, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, 0x16, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x73, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, + 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, + 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x61, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, + 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2b, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x69, 0x66, 0x74, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, + 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x72, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, - 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x6d, 0x0a, 0x14, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x47, 0x69, 0x66, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, + 0x72, 0x74, 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x69, 0x66, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x5e, 0x0a, 0x0d, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x6a, 0x0a, 0x13, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, - 0x72, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x11, - 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x11, 0x45, 0x71, 0x75, 0x69, - 0x70, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x29, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x45, 0x71, 0x75, 0x69, 0x70, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x71, 0x75, 0x69, 0x70, - 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x13, 0x55, 0x6e, 0x65, 0x71, 0x75, 0x69, 0x70, 0x55, - 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2b, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, - 0x65, 0x71, 0x75, 0x69, 0x70, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x65, 0x71, 0x75, - 0x69, 0x70, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8e, 0x01, 0x0a, 0x1d, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x45, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x45, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x45, 0x71, - 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, - 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, - 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, + 0x12, 0x68, 0x0a, 0x12, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x13, 0x52, 0x65, + 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, + 0x74, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, - 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7c, - 0x0a, 0x17, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, + 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x11, 0x45, 0x71, 0x75, 0x69, 0x70, 0x55, 0x73, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x71, 0x75, 0x69, 0x70, 0x55, + 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x71, 0x75, 0x69, 0x70, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, + 0x0a, 0x13, 0x55, 0x6e, 0x65, 0x71, 0x75, 0x69, 0x70, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x65, 0x71, 0x75, 0x69, 0x70, 0x55, + 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x65, 0x71, 0x75, 0x69, 0x70, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x8e, 0x01, 0x0a, 0x1d, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x45, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x45, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x45, 0x71, 0x75, 0x69, 0x70, 0x70, 0x65, 0x64, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x6d, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x76, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7c, 0x0a, 0x17, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, - 0x65, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, - 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, - 0x74, 0x65, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, 0x19, - 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, - 0x74, 0x65, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x91, 0x01, 0x0a, 0x1e, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x50, 0x75, 0x72, 0x63, - 0x68, 0x61, 0x73, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x36, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x50, 0x75, 0x72, - 0x63, 0x68, 0x61, 0x73, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x53, 0x68, 0x6f, 0x70, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x4f, 0x72, 0x64, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7f, 0x0a, 0x18, 0x50, - 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, - 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, - 0x73, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, - 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x72, 0x63, - 0x68, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, - 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x11, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, - 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, - 0x42, 0x69, 0x6c, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, + 0x65, 0x6d, 0x73, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, 0x19, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x91, 0x01, 0x0a, 0x1e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x73, 0x12, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x52, - 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, - 0x72, 0x79, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, - 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, - 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x7c, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, - 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x12, 0x2f, + 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x50, + 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7f, 0x0a, 0x18, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, + 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, + 0x6d, 0x12, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, + 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x79, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, + 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x2e, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x75, + 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x53, 0x75, + 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x82, 0x01, + 0x0a, 0x19, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, + 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x31, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, + 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x63, + 0x68, 0x61, 0x72, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x7c, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, + 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x12, 0x2f, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x4f, + 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, 0x6c, - 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x42, 0x69, 0x6c, - 0x6c, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x85, 0x01, 0x0a, 0x1a, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x47, 0x6f, 0x6f, + 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x85, 0x01, 0x0a, 0x1a, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x47, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x73, 0x12, + 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x73, - 0x12, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x47, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x11, 0x47, 0x65, 0x74, - 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x12, 0x29, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, - 0x65, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x57, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x2d, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, - 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x57, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x12, 0x29, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, + 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x75, - 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, - 0x0f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x47, 0x69, 0x66, 0x74, 0x57, 0x61, 0x6c, 0x6c, - 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x47, 0x69, 0x66, 0x74, 0x57, 0x61, - 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x47, 0x69, 0x66, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x73, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, - 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x2c, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x73, 0x0a, 0x14, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x72, 0x6d, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x82, 0x01, - 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x68, 0x61, - 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x31, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, - 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, - 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x70, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, 0x68, - 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x2d, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, - 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, - 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x2d, 0x2e, + 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x0f, + 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x47, 0x69, 0x66, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x12, + 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x47, 0x69, 0x66, 0x74, 0x57, 0x61, 0x6c, + 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, + 0x65, 0x72, 0x47, 0x69, 0x66, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x73, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, + 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x73, 0x0a, 0x14, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x72, 0x6d, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, + 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, - 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, - 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, - 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x82, 0x01, 0x0a, + 0x19, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x31, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, + 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, + 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x70, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, + 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x68, + 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, + 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x2d, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, - 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8e, - 0x01, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, - 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, - 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, - 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x68, - 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x8e, 0x01, 0x0a, 0x20, 0x53, 0x65, 0x74, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, - 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, - 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x84, 0x01, 0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x68, 0x69, 0x72, 0x64, - 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x74, 0x65, - 0x12, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, - 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, - 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8b, 0x01, 0x0a, 0x1c, 0x53, 0x79, 0x6e, 0x63, + 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x72, + 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8e, 0x01, + 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, + 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x68, 0x69, + 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8e, + 0x01, 0x0a, 0x20, 0x53, 0x65, 0x74, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, + 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x12, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x54, + 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x84, 0x01, 0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, + 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x74, 0x65, 0x12, + 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, + 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, + 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8b, 0x01, 0x0a, 0x1c, 0x53, 0x79, 0x6e, 0x63, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x35, 0x52, - 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x29, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x35, 0x52, 0x65, - 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, - 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x2d, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, - 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, + 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x12, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x54, 0x68, + 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x1c, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, - 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x53, 0x79, 0x6e, 0x63, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x35, 0x52, 0x65, + 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x29, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x35, 0x52, 0x65, 0x63, + 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x35, + 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x2d, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, + 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x1c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, - 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, + 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, + 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, + 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x54, 0x65, 0x6d, + 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x12, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, + 0x72, 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, + 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x88, 0x01, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, + 0x72, 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, + 0x12, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, + 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x6d, 0x70, + 0x6f, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x12, 0x53, + 0x75, 0x62, 0x6d, 0x69, 0x74, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x54, + 0x78, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, + 0x61, 0x72, 0x67, 0x65, 0x54, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x54, 0x65, - 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x12, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, - 0x61, 0x72, 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, - 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x88, 0x01, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, - 0x61, 0x72, 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x73, 0x12, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, - 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x6d, - 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, - 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x12, - 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, - 0x54, 0x78, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x48, 0x35, 0x52, 0x65, 0x63, - 0x68, 0x61, 0x72, 0x67, 0x65, 0x54, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x48, - 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x2a, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, - 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x35, 0x52, - 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x94, 0x01, 0x0a, 0x1f, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, - 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, - 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x12, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, - 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x63, 0x68, 0x61, - 0x72, 0x67, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, - 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x63, 0x65, - 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x13, 0x48, - 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x4d, 0x69, 0x66, 0x61, 0x70, 0x61, 0x79, 0x4e, 0x6f, 0x74, 0x69, - 0x66, 0x79, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x4d, 0x69, 0x66, 0x61, 0x70, - 0x61, 0x79, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x4d, 0x69, 0x66, 0x61, 0x70, 0x61, 0x79, 0x4e, - 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, - 0x11, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x56, 0x35, 0x50, 0x61, 0x79, 0x4e, 0x6f, 0x74, 0x69, - 0x66, 0x79, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x56, 0x35, 0x50, 0x61, 0x79, - 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x48, 0x35, + 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x48, 0x35, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x35, 0x52, 0x65, + 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x94, 0x01, 0x0a, 0x1f, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x6f, + 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, + 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x12, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, + 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, + 0x6c, 0x65, 0x72, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, + 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x13, 0x48, 0x61, + 0x6e, 0x64, 0x6c, 0x65, 0x4d, 0x69, 0x66, 0x61, 0x70, 0x61, 0x79, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x79, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x4d, 0x69, 0x66, 0x61, 0x70, 0x61, + 0x79, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x4d, 0x69, 0x66, 0x61, 0x70, 0x61, 0x79, 0x4e, 0x6f, + 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x11, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x56, 0x35, 0x50, 0x61, 0x79, 0x4e, 0x6f, 0x74, 0x69, 0x66, - 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7f, 0x0a, 0x18, 0x47, 0x65, 0x74, - 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x69, 0x61, 0x6d, 0x6f, - 0x6e, 0x64, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x69, 0x61, - 0x6d, 0x6f, 0x6e, 0x64, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, 0x16, 0x4c, 0x69, - 0x73, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x69, 0x70, - 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, - 0x69, 0x70, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x69, 0x70, 0x50, 0x61, 0x63, 0x6b, 0x61, - 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x08, 0x47, - 0x65, 0x74, 0x4d, 0x79, 0x56, 0x69, 0x70, 0x12, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x56, - 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, - 0x79, 0x56, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0b, - 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x56, 0x69, 0x70, 0x12, 0x23, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, - 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x56, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x56, 0x69, 0x70, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x11, 0x44, 0x65, 0x62, 0x69, 0x74, 0x43, - 0x50, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x75, 0x70, 0x46, 0x65, 0x65, 0x12, 0x29, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, - 0x62, 0x69, 0x74, 0x43, 0x50, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x75, 0x70, 0x46, 0x65, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x43, 0x50, - 0x42, 0x72, 0x65, 0x61, 0x6b, 0x75, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x61, 0x0a, 0x0e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, - 0x44, 0x72, 0x61, 0x77, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x57, 0x68, 0x65, 0x65, - 0x6c, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, - 0x65, 0x62, 0x69, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x08, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x56, 0x69, - 0x70, 0x12, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x56, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x56, 0x69, 0x70, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, - 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x12, 0x2a, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, - 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x73, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, - 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x12, 0x2c, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, - 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, 0x10, 0x43, 0x72, - 0x65, 0x64, 0x69, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x28, + 0x79, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x56, 0x35, 0x50, 0x61, 0x79, 0x4e, + 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, + 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x56, 0x35, 0x50, 0x61, 0x79, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7f, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x44, + 0x69, 0x61, 0x6d, 0x6f, 0x6e, 0x64, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x69, 0x61, 0x6d, 0x6f, 0x6e, + 0x64, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x69, 0x61, 0x6d, + 0x6f, 0x6e, 0x64, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, 0x16, 0x4c, 0x69, 0x73, + 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x69, 0x70, 0x50, + 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x69, + 0x70, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x69, 0x70, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x13, 0x47, 0x65, + 0x74, 0x56, 0x69, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x56, 0x69, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, + 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, - 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4c, 0x75, 0x63, - 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x2d, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x72, 0x65, 0x64, 0x69, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x64, 0x69, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x77, - 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x11, 0x43, - 0x72, 0x65, 0x64, 0x69, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x52, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x64, 0x69, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7f, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x64, 0x69, - 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, - 0x61, 0x72, 0x64, 0x12, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x52, 0x6f, 0x6f, 0x6d, - 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x52, 0x6f, - 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x85, 0x01, 0x0a, 0x1a, 0x43, 0x72, 0x65, - 0x64, 0x69, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, - 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, - 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x64, 0x69, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, - 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x82, 0x01, 0x0a, 0x19, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, - 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x31, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, - 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, - 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x13, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x47, 0x61, - 0x6d, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x2b, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, - 0x70, 0x70, 0x6c, 0x79, 0x47, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, - 0x79, 0x47, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x52, 0x65, - 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2a, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, - 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, - 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, - 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, - 0x74, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, - 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x0e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x52, 0x65, 0x64, - 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x52, 0x65, - 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, - 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x0c, 0x47, 0x65, - 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, + 0x2e, 0x47, 0x65, 0x74, 0x56, 0x69, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x08, + 0x47, 0x65, 0x74, 0x4d, 0x79, 0x56, 0x69, 0x70, 0x12, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x79, + 0x56, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, 0x10, 0x45, 0x78, 0x70, 0x69, 0x72, - 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x28, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, - 0x70, 0x69, 0x72, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x4d, 0x79, 0x56, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, + 0x0f, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x56, 0x69, 0x70, 0x42, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, + 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x56, 0x69, 0x70, 0x42, 0x65, 0x6e, 0x65, 0x66, + 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x56, 0x69, 0x70, 0x42, 0x65, 0x6e, 0x65, 0x66, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0b, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x56, + 0x69, 0x70, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x56, 0x69, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, + 0x73, 0x65, 0x56, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, + 0x11, 0x44, 0x65, 0x62, 0x69, 0x74, 0x43, 0x50, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x75, 0x70, 0x46, + 0x65, 0x65, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x43, 0x50, 0x42, 0x72, 0x65, 0x61, + 0x6b, 0x75, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x44, 0x65, 0x62, 0x69, 0x74, 0x43, 0x50, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x75, 0x70, 0x46, 0x65, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x0e, 0x44, 0x65, 0x62, + 0x69, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x44, 0x72, 0x61, 0x77, 0x12, 0x26, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, + 0x62, 0x69, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, + 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x08, + 0x47, 0x72, 0x61, 0x6e, 0x74, 0x56, 0x69, 0x70, 0x12, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x72, 0x61, 0x6e, 0x74, + 0x56, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x72, 0x61, + 0x6e, 0x74, 0x56, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, + 0x11, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x56, 0x69, 0x70, 0x54, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, + 0x72, 0x64, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x56, 0x69, 0x70, 0x54, 0x72, 0x69, + 0x61, 0x6c, 0x43, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x72, 0x61, 0x6e, 0x74, 0x56, 0x69, 0x70, 0x54, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, 0x72, + 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x11, 0x45, 0x71, 0x75, + 0x69, 0x70, 0x56, 0x69, 0x70, 0x54, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, 0x72, 0x64, 0x12, 0x29, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x45, 0x71, 0x75, 0x69, 0x70, 0x56, 0x69, 0x70, 0x54, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, + 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x71, 0x75, 0x69, + 0x70, 0x56, 0x69, 0x70, 0x54, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x13, 0x55, 0x6e, 0x65, 0x71, 0x75, 0x69, 0x70, + 0x56, 0x69, 0x70, 0x54, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, 0x72, 0x64, 0x12, 0x2b, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, + 0x6e, 0x65, 0x71, 0x75, 0x69, 0x70, 0x56, 0x69, 0x70, 0x54, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, + 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x65, 0x71, + 0x75, 0x69, 0x70, 0x56, 0x69, 0x70, 0x54, 0x72, 0x69, 0x61, 0x6c, 0x43, 0x61, 0x72, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x41, + 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x12, 0x2a, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x73, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x12, 0x2c, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, + 0x65, 0x76, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x88, 0x01, 0x0a, 0x1b, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x50, 0x72, + 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x33, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, 0x70, 0x50, 0x72, 0x6f, 0x67, + 0x72, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x56, 0x69, + 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x4d, 0x79, 0x56, 0x69, 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x2b, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x79, 0x56, 0x69, 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, + 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x4d, 0x79, 0x56, 0x69, 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x91, 0x01, 0x0a, 0x1e, 0x47, 0x65, 0x74, + 0x4d, 0x79, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, + 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x62, 0x61, 0x74, 0x65, 0x12, 0x36, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x4d, 0x79, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, + 0x6c, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x62, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x43, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x74, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, + 0x62, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x97, 0x01, 0x0a, + 0x20, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x79, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, + 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x62, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, + 0x73, 0x12, 0x38, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x79, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, + 0x6c, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x62, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x4d, 0x79, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x69, 0x6e, + 0x52, 0x65, 0x62, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7c, 0x0a, 0x17, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x56, + 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x62, 0x61, 0x74, + 0x65, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, 0x6c, + 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x62, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x56, 0x69, 0x70, 0x44, 0x61, 0x69, + 0x6c, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x62, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x54, 0x61, + 0x73, 0x6b, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, + 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, + 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, + 0x15, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, + 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4c, + 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4c, 0x75, + 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x57, + 0x68, 0x65, 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, + 0x64, 0x69, 0x74, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x57, 0x68, + 0x65, 0x65, 0x6c, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x7f, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, + 0x75, 0x72, 0x6e, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x30, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, 0x6f, 0x76, + 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x75, 0x72, 0x6e, + 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x85, 0x01, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x49, 0x6e, 0x76, + 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, + 0x64, 0x12, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, + 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x49, 0x6e, + 0x76, 0x69, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x82, 0x01, 0x0a, 0x19, 0x43, + 0x72, 0x65, 0x64, 0x69, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, + 0x6e, 0x67, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, + 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, + 0x65, 0x64, 0x69, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, + 0x67, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x70, 0x0a, 0x13, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x47, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x69, 0x6e, + 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x47, 0x61, + 0x6d, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x47, 0x61, 0x6d, 0x65, 0x43, + 0x6f, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x6d, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, + 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x64, + 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, + 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x76, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, + 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x27, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x64, + 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, + 0x0a, 0x0e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, + 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, + 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x61, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, + 0x65, 0x74, 0x73, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, + 0x6b, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, + 0x63, 0x6b, 0x65, 0x74, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, + 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x67, 0x0a, 0x10, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, + 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x52, 0x65, - 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x73, 0x0a, 0x14, 0x52, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, - 0x65, 0x74, 0x52, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x79, - 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, - 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2a, 0x5a, 0x28, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, - 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, 0x76, 0x31, 0x3b, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x76, - 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, + 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x73, 0x0a, 0x14, 0x52, 0x65, + 0x74, 0x72, 0x79, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x66, 0x75, + 0x6e, 0x64, 0x12, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, + 0x6b, 0x65, 0x74, 0x52, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, + 0x74, 0x52, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, + 0x2a, 0x5a, 0x28, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, + 0x76, 0x31, 0x3b, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -26642,7 +31343,7 @@ func file_proto_wallet_v1_wallet_proto_rawDescGZIP() []byte { return file_proto_wallet_v1_wallet_proto_rawDescData } -var file_proto_wallet_v1_wallet_proto_msgTypes = make([]protoimpl.MessageInfo, 244) +var file_proto_wallet_v1_wallet_proto_msgTypes = make([]protoimpl.MessageInfo, 292) var file_proto_wallet_v1_wallet_proto_goTypes = []any{ (*DebitGiftRequest)(nil), // 0: hyapp.wallet.v1.DebitGiftRequest (*DebitDirectGiftRequest)(nil), // 1: hyapp.wallet.v1.DebitDirectGiftRequest @@ -26764,130 +31465,178 @@ var file_proto_wallet_v1_wallet_proto_goTypes = []any{ (*GetRechargeBillSummaryRequest)(nil), // 117: hyapp.wallet.v1.GetRechargeBillSummaryRequest (*RechargeBillSummaryBucket)(nil), // 118: hyapp.wallet.v1.RechargeBillSummaryBucket (*GetRechargeBillSummaryResponse)(nil), // 119: hyapp.wallet.v1.GetRechargeBillSummaryResponse - (*GetRechargeBillOverviewRequest)(nil), // 120: hyapp.wallet.v1.GetRechargeBillOverviewRequest - (*RechargeBillDailyBucket)(nil), // 121: hyapp.wallet.v1.RechargeBillDailyBucket - (*RechargeBillRegionBucket)(nil), // 122: hyapp.wallet.v1.RechargeBillRegionBucket - (*RechargeBillGooglePaidStats)(nil), // 123: hyapp.wallet.v1.RechargeBillGooglePaidStats - (*RechargeBillSalaryTransferStats)(nil), // 124: hyapp.wallet.v1.RechargeBillSalaryTransferStats - (*GetRechargeBillOverviewResponse)(nil), // 125: hyapp.wallet.v1.GetRechargeBillOverviewResponse - (*RefreshGooglePaymentPricesRequest)(nil), // 126: hyapp.wallet.v1.RefreshGooglePaymentPricesRequest - (*GooglePaymentPrice)(nil), // 127: hyapp.wallet.v1.GooglePaymentPrice - (*RefreshGooglePaymentPricesResponse)(nil), // 128: hyapp.wallet.v1.RefreshGooglePaymentPricesResponse - (*WalletFeatureFlags)(nil), // 129: hyapp.wallet.v1.WalletFeatureFlags - (*GetWalletOverviewRequest)(nil), // 130: hyapp.wallet.v1.GetWalletOverviewRequest - (*GetWalletOverviewResponse)(nil), // 131: hyapp.wallet.v1.GetWalletOverviewResponse - (*WalletValueSummary)(nil), // 132: hyapp.wallet.v1.WalletValueSummary - (*GetWalletValueSummaryRequest)(nil), // 133: hyapp.wallet.v1.GetWalletValueSummaryRequest - (*GetWalletValueSummaryResponse)(nil), // 134: hyapp.wallet.v1.GetWalletValueSummaryResponse - (*GiftWallItem)(nil), // 135: hyapp.wallet.v1.GiftWallItem - (*GetUserGiftWallRequest)(nil), // 136: hyapp.wallet.v1.GetUserGiftWallRequest - (*GetUserGiftWallResponse)(nil), // 137: hyapp.wallet.v1.GetUserGiftWallResponse - (*RechargeProduct)(nil), // 138: hyapp.wallet.v1.RechargeProduct - (*ListRechargeProductsRequest)(nil), // 139: hyapp.wallet.v1.ListRechargeProductsRequest - (*ListRechargeProductsResponse)(nil), // 140: hyapp.wallet.v1.ListRechargeProductsResponse - (*ConfirmGooglePaymentRequest)(nil), // 141: hyapp.wallet.v1.ConfirmGooglePaymentRequest - (*ConfirmGooglePaymentResponse)(nil), // 142: hyapp.wallet.v1.ConfirmGooglePaymentResponse - (*ListAdminRechargeProductsRequest)(nil), // 143: hyapp.wallet.v1.ListAdminRechargeProductsRequest - (*ListAdminRechargeProductsResponse)(nil), // 144: hyapp.wallet.v1.ListAdminRechargeProductsResponse - (*CreateRechargeProductRequest)(nil), // 145: hyapp.wallet.v1.CreateRechargeProductRequest - (*UpdateRechargeProductRequest)(nil), // 146: hyapp.wallet.v1.UpdateRechargeProductRequest - (*DeleteRechargeProductRequest)(nil), // 147: hyapp.wallet.v1.DeleteRechargeProductRequest - (*RechargeProductResponse)(nil), // 148: hyapp.wallet.v1.RechargeProductResponse - (*DeleteRechargeProductResponse)(nil), // 149: hyapp.wallet.v1.DeleteRechargeProductResponse - (*ThirdPartyPaymentMethod)(nil), // 150: hyapp.wallet.v1.ThirdPartyPaymentMethod - (*ThirdPartyPaymentChannel)(nil), // 151: hyapp.wallet.v1.ThirdPartyPaymentChannel - (*ListThirdPartyPaymentChannelsRequest)(nil), // 152: hyapp.wallet.v1.ListThirdPartyPaymentChannelsRequest - (*ListThirdPartyPaymentChannelsResponse)(nil), // 153: hyapp.wallet.v1.ListThirdPartyPaymentChannelsResponse - (*SetThirdPartyPaymentMethodStatusRequest)(nil), // 154: hyapp.wallet.v1.SetThirdPartyPaymentMethodStatusRequest - (*ThirdPartyPaymentMethodResponse)(nil), // 155: hyapp.wallet.v1.ThirdPartyPaymentMethodResponse - (*UpdateThirdPartyPaymentRateRequest)(nil), // 156: hyapp.wallet.v1.UpdateThirdPartyPaymentRateRequest - (*ThirdPartyPaymentMethodSyncIssue)(nil), // 157: hyapp.wallet.v1.ThirdPartyPaymentMethodSyncIssue - (*SyncThirdPartyPaymentMethodsRequest)(nil), // 158: hyapp.wallet.v1.SyncThirdPartyPaymentMethodsRequest - (*SyncThirdPartyPaymentMethodsResponse)(nil), // 159: hyapp.wallet.v1.SyncThirdPartyPaymentMethodsResponse - (*H5RechargeOptionsRequest)(nil), // 160: hyapp.wallet.v1.H5RechargeOptionsRequest - (*H5RechargeOptionsResponse)(nil), // 161: hyapp.wallet.v1.H5RechargeOptionsResponse - (*ExternalRechargeOrder)(nil), // 162: hyapp.wallet.v1.ExternalRechargeOrder - (*CreateH5RechargeOrderRequest)(nil), // 163: hyapp.wallet.v1.CreateH5RechargeOrderRequest - (*CreateTemporaryRechargeOrderRequest)(nil), // 164: hyapp.wallet.v1.CreateTemporaryRechargeOrderRequest - (*GetTemporaryRechargeOrderRequest)(nil), // 165: hyapp.wallet.v1.GetTemporaryRechargeOrderRequest - (*ListTemporaryRechargeOrdersRequest)(nil), // 166: hyapp.wallet.v1.ListTemporaryRechargeOrdersRequest - (*ListTemporaryRechargeOrdersResponse)(nil), // 167: hyapp.wallet.v1.ListTemporaryRechargeOrdersResponse - (*SubmitH5RechargeTxRequest)(nil), // 168: hyapp.wallet.v1.SubmitH5RechargeTxRequest - (*GetH5RechargeOrderRequest)(nil), // 169: hyapp.wallet.v1.GetH5RechargeOrderRequest - (*H5RechargeOrderResponse)(nil), // 170: hyapp.wallet.v1.H5RechargeOrderResponse - (*VerifyCoinSellerRechargeReceiptRequest)(nil), // 171: hyapp.wallet.v1.VerifyCoinSellerRechargeReceiptRequest - (*VerifyCoinSellerRechargeReceiptResponse)(nil), // 172: hyapp.wallet.v1.VerifyCoinSellerRechargeReceiptResponse - (*HandleMifapayNotifyRequest)(nil), // 173: hyapp.wallet.v1.HandleMifapayNotifyRequest - (*HandleMifapayNotifyResponse)(nil), // 174: hyapp.wallet.v1.HandleMifapayNotifyResponse - (*HandleV5PayNotifyRequest)(nil), // 175: hyapp.wallet.v1.HandleV5PayNotifyRequest - (*HandleV5PayNotifyResponse)(nil), // 176: hyapp.wallet.v1.HandleV5PayNotifyResponse - (*DiamondExchangeRule)(nil), // 177: hyapp.wallet.v1.DiamondExchangeRule - (*GetDiamondExchangeConfigRequest)(nil), // 178: hyapp.wallet.v1.GetDiamondExchangeConfigRequest - (*GetDiamondExchangeConfigResponse)(nil), // 179: hyapp.wallet.v1.GetDiamondExchangeConfigResponse - (*WalletTransaction)(nil), // 180: hyapp.wallet.v1.WalletTransaction - (*ListWalletTransactionsRequest)(nil), // 181: hyapp.wallet.v1.ListWalletTransactionsRequest - (*ListWalletTransactionsResponse)(nil), // 182: hyapp.wallet.v1.ListWalletTransactionsResponse - (*VipRewardItem)(nil), // 183: hyapp.wallet.v1.VipRewardItem - (*VipLevel)(nil), // 184: hyapp.wallet.v1.VipLevel - (*UserVip)(nil), // 185: hyapp.wallet.v1.UserVip - (*ListVipPackagesRequest)(nil), // 186: hyapp.wallet.v1.ListVipPackagesRequest - (*ListVipPackagesResponse)(nil), // 187: hyapp.wallet.v1.ListVipPackagesResponse - (*GetMyVipRequest)(nil), // 188: hyapp.wallet.v1.GetMyVipRequest - (*GetMyVipResponse)(nil), // 189: hyapp.wallet.v1.GetMyVipResponse - (*PurchaseVipRequest)(nil), // 190: hyapp.wallet.v1.PurchaseVipRequest - (*PurchaseVipResponse)(nil), // 191: hyapp.wallet.v1.PurchaseVipResponse - (*DebitCPBreakupFeeRequest)(nil), // 192: hyapp.wallet.v1.DebitCPBreakupFeeRequest - (*DebitCPBreakupFeeResponse)(nil), // 193: hyapp.wallet.v1.DebitCPBreakupFeeResponse - (*DebitWheelDrawRequest)(nil), // 194: hyapp.wallet.v1.DebitWheelDrawRequest - (*DebitWheelDrawResponse)(nil), // 195: hyapp.wallet.v1.DebitWheelDrawResponse - (*GrantVipRequest)(nil), // 196: hyapp.wallet.v1.GrantVipRequest - (*GrantVipResponse)(nil), // 197: hyapp.wallet.v1.GrantVipResponse - (*AdminVipLevelInput)(nil), // 198: hyapp.wallet.v1.AdminVipLevelInput - (*ListAdminVipLevelsRequest)(nil), // 199: hyapp.wallet.v1.ListAdminVipLevelsRequest - (*ListAdminVipLevelsResponse)(nil), // 200: hyapp.wallet.v1.ListAdminVipLevelsResponse - (*UpdateAdminVipLevelsRequest)(nil), // 201: hyapp.wallet.v1.UpdateAdminVipLevelsRequest - (*UpdateAdminVipLevelsResponse)(nil), // 202: hyapp.wallet.v1.UpdateAdminVipLevelsResponse - (*CreditTaskRewardRequest)(nil), // 203: hyapp.wallet.v1.CreditTaskRewardRequest - (*CreditTaskRewardResponse)(nil), // 204: hyapp.wallet.v1.CreditTaskRewardResponse - (*CreditLuckyGiftRewardRequest)(nil), // 205: hyapp.wallet.v1.CreditLuckyGiftRewardRequest - (*CreditLuckyGiftRewardResponse)(nil), // 206: hyapp.wallet.v1.CreditLuckyGiftRewardResponse - (*CreditWheelRewardRequest)(nil), // 207: hyapp.wallet.v1.CreditWheelRewardRequest - (*CreditWheelRewardResponse)(nil), // 208: hyapp.wallet.v1.CreditWheelRewardResponse - (*CreditRoomTurnoverRewardRequest)(nil), // 209: hyapp.wallet.v1.CreditRoomTurnoverRewardRequest - (*CreditRoomTurnoverRewardResponse)(nil), // 210: hyapp.wallet.v1.CreditRoomTurnoverRewardResponse - (*CreditInviteActivityRewardRequest)(nil), // 211: hyapp.wallet.v1.CreditInviteActivityRewardRequest - (*CreditInviteActivityRewardResponse)(nil), // 212: hyapp.wallet.v1.CreditInviteActivityRewardResponse - (*CreditAgencyOpeningRewardRequest)(nil), // 213: hyapp.wallet.v1.CreditAgencyOpeningRewardRequest - (*CreditAgencyOpeningRewardResponse)(nil), // 214: hyapp.wallet.v1.CreditAgencyOpeningRewardResponse - (*ApplyGameCoinChangeRequest)(nil), // 215: hyapp.wallet.v1.ApplyGameCoinChangeRequest - (*ApplyGameCoinChangeResponse)(nil), // 216: hyapp.wallet.v1.ApplyGameCoinChangeResponse - (*RedPacketConfig)(nil), // 217: hyapp.wallet.v1.RedPacketConfig - (*RedPacketClaim)(nil), // 218: hyapp.wallet.v1.RedPacketClaim - (*RedPacket)(nil), // 219: hyapp.wallet.v1.RedPacket - (*GetRedPacketConfigRequest)(nil), // 220: hyapp.wallet.v1.GetRedPacketConfigRequest - (*GetRedPacketConfigResponse)(nil), // 221: hyapp.wallet.v1.GetRedPacketConfigResponse - (*UpdateRedPacketConfigRequest)(nil), // 222: hyapp.wallet.v1.UpdateRedPacketConfigRequest - (*UpdateRedPacketConfigResponse)(nil), // 223: hyapp.wallet.v1.UpdateRedPacketConfigResponse - (*CreateRedPacketRequest)(nil), // 224: hyapp.wallet.v1.CreateRedPacketRequest - (*CreateRedPacketResponse)(nil), // 225: hyapp.wallet.v1.CreateRedPacketResponse - (*ClaimRedPacketRequest)(nil), // 226: hyapp.wallet.v1.ClaimRedPacketRequest - (*ClaimRedPacketResponse)(nil), // 227: hyapp.wallet.v1.ClaimRedPacketResponse - (*ListRedPacketsRequest)(nil), // 228: hyapp.wallet.v1.ListRedPacketsRequest - (*ListRedPacketsResponse)(nil), // 229: hyapp.wallet.v1.ListRedPacketsResponse - (*GetRedPacketRequest)(nil), // 230: hyapp.wallet.v1.GetRedPacketRequest - (*GetRedPacketResponse)(nil), // 231: hyapp.wallet.v1.GetRedPacketResponse - (*ExpireRedPacketsRequest)(nil), // 232: hyapp.wallet.v1.ExpireRedPacketsRequest - (*ExpireRedPacketsResponse)(nil), // 233: hyapp.wallet.v1.ExpireRedPacketsResponse - (*RetryRedPacketRefundRequest)(nil), // 234: hyapp.wallet.v1.RetryRedPacketRefundRequest - (*RetryRedPacketRefundResponse)(nil), // 235: hyapp.wallet.v1.RetryRedPacketRefundResponse - (*CronBatchRequest)(nil), // 236: hyapp.wallet.v1.CronBatchRequest - (*CronBatchResponse)(nil), // 237: hyapp.wallet.v1.CronBatchResponse - (*ResourceShopPurchaseOrder)(nil), // 238: hyapp.wallet.v1.ResourceShopPurchaseOrder - (*ListResourceShopPurchaseOrdersRequest)(nil), // 239: hyapp.wallet.v1.ListResourceShopPurchaseOrdersRequest - (*ListResourceShopPurchaseOrdersResponse)(nil), // 240: hyapp.wallet.v1.ListResourceShopPurchaseOrdersResponse - (*GetGiftCatalogVersionRequest)(nil), // 241: hyapp.wallet.v1.GetGiftCatalogVersionRequest - (*GetGiftCatalogVersionResponse)(nil), // 242: hyapp.wallet.v1.GetGiftCatalogVersionResponse - nil, // 243: hyapp.wallet.v1.HandleV5PayNotifyRequest.FieldsEntry + (*UserRechargeStats)(nil), // 120: hyapp.wallet.v1.UserRechargeStats + (*BatchGetUserRechargeStatsRequest)(nil), // 121: hyapp.wallet.v1.BatchGetUserRechargeStatsRequest + (*BatchGetUserRechargeStatsResponse)(nil), // 122: hyapp.wallet.v1.BatchGetUserRechargeStatsResponse + (*GetRechargeBillOverviewRequest)(nil), // 123: hyapp.wallet.v1.GetRechargeBillOverviewRequest + (*RechargeBillDailyBucket)(nil), // 124: hyapp.wallet.v1.RechargeBillDailyBucket + (*RechargeBillRegionBucket)(nil), // 125: hyapp.wallet.v1.RechargeBillRegionBucket + (*RechargeBillGooglePaidStats)(nil), // 126: hyapp.wallet.v1.RechargeBillGooglePaidStats + (*RechargeBillSalaryTransferStats)(nil), // 127: hyapp.wallet.v1.RechargeBillSalaryTransferStats + (*GetRechargeBillOverviewResponse)(nil), // 128: hyapp.wallet.v1.GetRechargeBillOverviewResponse + (*RefreshGooglePaymentPricesRequest)(nil), // 129: hyapp.wallet.v1.RefreshGooglePaymentPricesRequest + (*GooglePaymentPrice)(nil), // 130: hyapp.wallet.v1.GooglePaymentPrice + (*RefreshGooglePaymentPricesResponse)(nil), // 131: hyapp.wallet.v1.RefreshGooglePaymentPricesResponse + (*WalletFeatureFlags)(nil), // 132: hyapp.wallet.v1.WalletFeatureFlags + (*GetWalletOverviewRequest)(nil), // 133: hyapp.wallet.v1.GetWalletOverviewRequest + (*GetWalletOverviewResponse)(nil), // 134: hyapp.wallet.v1.GetWalletOverviewResponse + (*WalletValueSummary)(nil), // 135: hyapp.wallet.v1.WalletValueSummary + (*GetWalletValueSummaryRequest)(nil), // 136: hyapp.wallet.v1.GetWalletValueSummaryRequest + (*GetWalletValueSummaryResponse)(nil), // 137: hyapp.wallet.v1.GetWalletValueSummaryResponse + (*GiftWallItem)(nil), // 138: hyapp.wallet.v1.GiftWallItem + (*GetUserGiftWallRequest)(nil), // 139: hyapp.wallet.v1.GetUserGiftWallRequest + (*GetUserGiftWallResponse)(nil), // 140: hyapp.wallet.v1.GetUserGiftWallResponse + (*RechargeProduct)(nil), // 141: hyapp.wallet.v1.RechargeProduct + (*ListRechargeProductsRequest)(nil), // 142: hyapp.wallet.v1.ListRechargeProductsRequest + (*ListRechargeProductsResponse)(nil), // 143: hyapp.wallet.v1.ListRechargeProductsResponse + (*ConfirmGooglePaymentRequest)(nil), // 144: hyapp.wallet.v1.ConfirmGooglePaymentRequest + (*ConfirmGooglePaymentResponse)(nil), // 145: hyapp.wallet.v1.ConfirmGooglePaymentResponse + (*ListAdminRechargeProductsRequest)(nil), // 146: hyapp.wallet.v1.ListAdminRechargeProductsRequest + (*ListAdminRechargeProductsResponse)(nil), // 147: hyapp.wallet.v1.ListAdminRechargeProductsResponse + (*CreateRechargeProductRequest)(nil), // 148: hyapp.wallet.v1.CreateRechargeProductRequest + (*UpdateRechargeProductRequest)(nil), // 149: hyapp.wallet.v1.UpdateRechargeProductRequest + (*DeleteRechargeProductRequest)(nil), // 150: hyapp.wallet.v1.DeleteRechargeProductRequest + (*RechargeProductResponse)(nil), // 151: hyapp.wallet.v1.RechargeProductResponse + (*DeleteRechargeProductResponse)(nil), // 152: hyapp.wallet.v1.DeleteRechargeProductResponse + (*ThirdPartyPaymentMethod)(nil), // 153: hyapp.wallet.v1.ThirdPartyPaymentMethod + (*ThirdPartyPaymentChannel)(nil), // 154: hyapp.wallet.v1.ThirdPartyPaymentChannel + (*ListThirdPartyPaymentChannelsRequest)(nil), // 155: hyapp.wallet.v1.ListThirdPartyPaymentChannelsRequest + (*ListThirdPartyPaymentChannelsResponse)(nil), // 156: hyapp.wallet.v1.ListThirdPartyPaymentChannelsResponse + (*SetThirdPartyPaymentMethodStatusRequest)(nil), // 157: hyapp.wallet.v1.SetThirdPartyPaymentMethodStatusRequest + (*ThirdPartyPaymentMethodResponse)(nil), // 158: hyapp.wallet.v1.ThirdPartyPaymentMethodResponse + (*UpdateThirdPartyPaymentRateRequest)(nil), // 159: hyapp.wallet.v1.UpdateThirdPartyPaymentRateRequest + (*ThirdPartyPaymentMethodSyncIssue)(nil), // 160: hyapp.wallet.v1.ThirdPartyPaymentMethodSyncIssue + (*SyncThirdPartyPaymentMethodsRequest)(nil), // 161: hyapp.wallet.v1.SyncThirdPartyPaymentMethodsRequest + (*SyncThirdPartyPaymentMethodsResponse)(nil), // 162: hyapp.wallet.v1.SyncThirdPartyPaymentMethodsResponse + (*H5RechargeOptionsRequest)(nil), // 163: hyapp.wallet.v1.H5RechargeOptionsRequest + (*H5RechargeOptionsResponse)(nil), // 164: hyapp.wallet.v1.H5RechargeOptionsResponse + (*ExternalRechargeOrder)(nil), // 165: hyapp.wallet.v1.ExternalRechargeOrder + (*CreateH5RechargeOrderRequest)(nil), // 166: hyapp.wallet.v1.CreateH5RechargeOrderRequest + (*CreateTemporaryRechargeOrderRequest)(nil), // 167: hyapp.wallet.v1.CreateTemporaryRechargeOrderRequest + (*GetTemporaryRechargeOrderRequest)(nil), // 168: hyapp.wallet.v1.GetTemporaryRechargeOrderRequest + (*ListTemporaryRechargeOrdersRequest)(nil), // 169: hyapp.wallet.v1.ListTemporaryRechargeOrdersRequest + (*ListTemporaryRechargeOrdersResponse)(nil), // 170: hyapp.wallet.v1.ListTemporaryRechargeOrdersResponse + (*SubmitH5RechargeTxRequest)(nil), // 171: hyapp.wallet.v1.SubmitH5RechargeTxRequest + (*GetH5RechargeOrderRequest)(nil), // 172: hyapp.wallet.v1.GetH5RechargeOrderRequest + (*H5RechargeOrderResponse)(nil), // 173: hyapp.wallet.v1.H5RechargeOrderResponse + (*VerifyCoinSellerRechargeReceiptRequest)(nil), // 174: hyapp.wallet.v1.VerifyCoinSellerRechargeReceiptRequest + (*VerifyCoinSellerRechargeReceiptResponse)(nil), // 175: hyapp.wallet.v1.VerifyCoinSellerRechargeReceiptResponse + (*HandleMifapayNotifyRequest)(nil), // 176: hyapp.wallet.v1.HandleMifapayNotifyRequest + (*HandleMifapayNotifyResponse)(nil), // 177: hyapp.wallet.v1.HandleMifapayNotifyResponse + (*HandleV5PayNotifyRequest)(nil), // 178: hyapp.wallet.v1.HandleV5PayNotifyRequest + (*HandleV5PayNotifyResponse)(nil), // 179: hyapp.wallet.v1.HandleV5PayNotifyResponse + (*DiamondExchangeRule)(nil), // 180: hyapp.wallet.v1.DiamondExchangeRule + (*GetDiamondExchangeConfigRequest)(nil), // 181: hyapp.wallet.v1.GetDiamondExchangeConfigRequest + (*GetDiamondExchangeConfigResponse)(nil), // 182: hyapp.wallet.v1.GetDiamondExchangeConfigResponse + (*WalletTransaction)(nil), // 183: hyapp.wallet.v1.WalletTransaction + (*ListWalletTransactionsRequest)(nil), // 184: hyapp.wallet.v1.ListWalletTransactionsRequest + (*ListWalletTransactionsResponse)(nil), // 185: hyapp.wallet.v1.ListWalletTransactionsResponse + (*VipRewardItem)(nil), // 186: hyapp.wallet.v1.VipRewardItem + (*VipProgramConfig)(nil), // 187: hyapp.wallet.v1.VipProgramConfig + (*VipBenefit)(nil), // 188: hyapp.wallet.v1.VipBenefit + (*VipLevel)(nil), // 189: hyapp.wallet.v1.VipLevel + (*UserVip)(nil), // 190: hyapp.wallet.v1.UserVip + (*VipTrialCard)(nil), // 191: hyapp.wallet.v1.VipTrialCard + (*VipUserSettings)(nil), // 192: hyapp.wallet.v1.VipUserSettings + (*VipState)(nil), // 193: hyapp.wallet.v1.VipState + (*ListVipPackagesRequest)(nil), // 194: hyapp.wallet.v1.ListVipPackagesRequest + (*ListVipPackagesResponse)(nil), // 195: hyapp.wallet.v1.ListVipPackagesResponse + (*GetMyVipRequest)(nil), // 196: hyapp.wallet.v1.GetMyVipRequest + (*GetMyVipResponse)(nil), // 197: hyapp.wallet.v1.GetMyVipResponse + (*PurchaseVipRequest)(nil), // 198: hyapp.wallet.v1.PurchaseVipRequest + (*PurchaseVipResponse)(nil), // 199: hyapp.wallet.v1.PurchaseVipResponse + (*DebitCPBreakupFeeRequest)(nil), // 200: hyapp.wallet.v1.DebitCPBreakupFeeRequest + (*DebitCPBreakupFeeResponse)(nil), // 201: hyapp.wallet.v1.DebitCPBreakupFeeResponse + (*DebitWheelDrawRequest)(nil), // 202: hyapp.wallet.v1.DebitWheelDrawRequest + (*DebitWheelDrawResponse)(nil), // 203: hyapp.wallet.v1.DebitWheelDrawResponse + (*GrantVipRequest)(nil), // 204: hyapp.wallet.v1.GrantVipRequest + (*GrantVipResponse)(nil), // 205: hyapp.wallet.v1.GrantVipResponse + (*GetVipProgramConfigRequest)(nil), // 206: hyapp.wallet.v1.GetVipProgramConfigRequest + (*GetVipProgramConfigResponse)(nil), // 207: hyapp.wallet.v1.GetVipProgramConfigResponse + (*UpdateAdminVipProgramConfigRequest)(nil), // 208: hyapp.wallet.v1.UpdateAdminVipProgramConfigRequest + (*UpdateAdminVipProgramConfigResponse)(nil), // 209: hyapp.wallet.v1.UpdateAdminVipProgramConfigResponse + (*UpdateMyVipSettingsRequest)(nil), // 210: hyapp.wallet.v1.UpdateMyVipSettingsRequest + (*UpdateMyVipSettingsResponse)(nil), // 211: hyapp.wallet.v1.UpdateMyVipSettingsResponse + (*VipDailyCoinRebateRun)(nil), // 212: hyapp.wallet.v1.VipDailyCoinRebateRun + (*VipDailyCoinRebate)(nil), // 213: hyapp.wallet.v1.VipDailyCoinRebate + (*ProcessVipDailyCoinRebateBatchRequest)(nil), // 214: hyapp.wallet.v1.ProcessVipDailyCoinRebateBatchRequest + (*ProcessVipDailyCoinRebateBatchResponse)(nil), // 215: hyapp.wallet.v1.ProcessVipDailyCoinRebateBatchResponse + (*GetMyCurrentVipDailyCoinRebateRequest)(nil), // 216: hyapp.wallet.v1.GetMyCurrentVipDailyCoinRebateRequest + (*GetMyCurrentVipDailyCoinRebateResponse)(nil), // 217: hyapp.wallet.v1.GetMyCurrentVipDailyCoinRebateResponse + (*ListMyVipDailyCoinRebateStatusesRequest)(nil), // 218: hyapp.wallet.v1.ListMyVipDailyCoinRebateStatusesRequest + (*ListMyVipDailyCoinRebateStatusesResponse)(nil), // 219: hyapp.wallet.v1.ListMyVipDailyCoinRebateStatusesResponse + (*ClaimVipDailyCoinRebateRequest)(nil), // 220: hyapp.wallet.v1.ClaimVipDailyCoinRebateRequest + (*ClaimVipDailyCoinRebateResponse)(nil), // 221: hyapp.wallet.v1.ClaimVipDailyCoinRebateResponse + (*GrantVipTrialCardRequest)(nil), // 222: hyapp.wallet.v1.GrantVipTrialCardRequest + (*GrantVipTrialCardResponse)(nil), // 223: hyapp.wallet.v1.GrantVipTrialCardResponse + (*EquipVipTrialCardRequest)(nil), // 224: hyapp.wallet.v1.EquipVipTrialCardRequest + (*EquipVipTrialCardResponse)(nil), // 225: hyapp.wallet.v1.EquipVipTrialCardResponse + (*UnequipVipTrialCardRequest)(nil), // 226: hyapp.wallet.v1.UnequipVipTrialCardRequest + (*UnequipVipTrialCardResponse)(nil), // 227: hyapp.wallet.v1.UnequipVipTrialCardResponse + (*CheckVipBenefitRequest)(nil), // 228: hyapp.wallet.v1.CheckVipBenefitRequest + (*CheckVipBenefitResponse)(nil), // 229: hyapp.wallet.v1.CheckVipBenefitResponse + (*AdminVipLevelInput)(nil), // 230: hyapp.wallet.v1.AdminVipLevelInput + (*ListAdminVipLevelsRequest)(nil), // 231: hyapp.wallet.v1.ListAdminVipLevelsRequest + (*ListAdminVipLevelsResponse)(nil), // 232: hyapp.wallet.v1.ListAdminVipLevelsResponse + (*UpdateAdminVipLevelsRequest)(nil), // 233: hyapp.wallet.v1.UpdateAdminVipLevelsRequest + (*UpdateAdminVipLevelsResponse)(nil), // 234: hyapp.wallet.v1.UpdateAdminVipLevelsResponse + (*CreditTaskRewardRequest)(nil), // 235: hyapp.wallet.v1.CreditTaskRewardRequest + (*CreditTaskRewardResponse)(nil), // 236: hyapp.wallet.v1.CreditTaskRewardResponse + (*CreditLuckyGiftRewardRequest)(nil), // 237: hyapp.wallet.v1.CreditLuckyGiftRewardRequest + (*CreditLuckyGiftRewardResponse)(nil), // 238: hyapp.wallet.v1.CreditLuckyGiftRewardResponse + (*CreditWheelRewardRequest)(nil), // 239: hyapp.wallet.v1.CreditWheelRewardRequest + (*CreditWheelRewardResponse)(nil), // 240: hyapp.wallet.v1.CreditWheelRewardResponse + (*CreditRoomTurnoverRewardRequest)(nil), // 241: hyapp.wallet.v1.CreditRoomTurnoverRewardRequest + (*CreditRoomTurnoverRewardResponse)(nil), // 242: hyapp.wallet.v1.CreditRoomTurnoverRewardResponse + (*CreditInviteActivityRewardRequest)(nil), // 243: hyapp.wallet.v1.CreditInviteActivityRewardRequest + (*CreditInviteActivityRewardResponse)(nil), // 244: hyapp.wallet.v1.CreditInviteActivityRewardResponse + (*CreditAgencyOpeningRewardRequest)(nil), // 245: hyapp.wallet.v1.CreditAgencyOpeningRewardRequest + (*CreditAgencyOpeningRewardResponse)(nil), // 246: hyapp.wallet.v1.CreditAgencyOpeningRewardResponse + (*ApplyGameCoinChangeRequest)(nil), // 247: hyapp.wallet.v1.ApplyGameCoinChangeRequest + (*ApplyGameCoinChangeResponse)(nil), // 248: hyapp.wallet.v1.ApplyGameCoinChangeResponse + (*RedPacketConfig)(nil), // 249: hyapp.wallet.v1.RedPacketConfig + (*RedPacketClaim)(nil), // 250: hyapp.wallet.v1.RedPacketClaim + (*RedPacket)(nil), // 251: hyapp.wallet.v1.RedPacket + (*GetRedPacketConfigRequest)(nil), // 252: hyapp.wallet.v1.GetRedPacketConfigRequest + (*GetRedPacketConfigResponse)(nil), // 253: hyapp.wallet.v1.GetRedPacketConfigResponse + (*UpdateRedPacketConfigRequest)(nil), // 254: hyapp.wallet.v1.UpdateRedPacketConfigRequest + (*UpdateRedPacketConfigResponse)(nil), // 255: hyapp.wallet.v1.UpdateRedPacketConfigResponse + (*CreateRedPacketRequest)(nil), // 256: hyapp.wallet.v1.CreateRedPacketRequest + (*CreateRedPacketResponse)(nil), // 257: hyapp.wallet.v1.CreateRedPacketResponse + (*ClaimRedPacketRequest)(nil), // 258: hyapp.wallet.v1.ClaimRedPacketRequest + (*ClaimRedPacketResponse)(nil), // 259: hyapp.wallet.v1.ClaimRedPacketResponse + (*ListRedPacketsRequest)(nil), // 260: hyapp.wallet.v1.ListRedPacketsRequest + (*ListRedPacketsResponse)(nil), // 261: hyapp.wallet.v1.ListRedPacketsResponse + (*GetRedPacketRequest)(nil), // 262: hyapp.wallet.v1.GetRedPacketRequest + (*GetRedPacketResponse)(nil), // 263: hyapp.wallet.v1.GetRedPacketResponse + (*ExpireRedPacketsRequest)(nil), // 264: hyapp.wallet.v1.ExpireRedPacketsRequest + (*ExpireRedPacketsResponse)(nil), // 265: hyapp.wallet.v1.ExpireRedPacketsResponse + (*RetryRedPacketRefundRequest)(nil), // 266: hyapp.wallet.v1.RetryRedPacketRefundRequest + (*RetryRedPacketRefundResponse)(nil), // 267: hyapp.wallet.v1.RetryRedPacketRefundResponse + (*CronBatchRequest)(nil), // 268: hyapp.wallet.v1.CronBatchRequest + (*CronBatchResponse)(nil), // 269: hyapp.wallet.v1.CronBatchResponse + (*ResourceShopPurchaseOrder)(nil), // 270: hyapp.wallet.v1.ResourceShopPurchaseOrder + (*ListResourceShopPurchaseOrdersRequest)(nil), // 271: hyapp.wallet.v1.ListResourceShopPurchaseOrdersRequest + (*ListResourceShopPurchaseOrdersResponse)(nil), // 272: hyapp.wallet.v1.ListResourceShopPurchaseOrdersResponse + (*GetGiftCatalogVersionRequest)(nil), // 273: hyapp.wallet.v1.GetGiftCatalogVersionRequest + (*GetGiftCatalogVersionResponse)(nil), // 274: hyapp.wallet.v1.GetGiftCatalogVersionResponse + (*HostRevenueStats)(nil), // 275: hyapp.wallet.v1.HostRevenueStats + (*GetHostRevenueStatsRequest)(nil), // 276: hyapp.wallet.v1.GetHostRevenueStatsRequest + (*GetHostRevenueStatsResponse)(nil), // 277: hyapp.wallet.v1.GetHostRevenueStatsResponse + (*AgencyPointShareStats)(nil), // 278: hyapp.wallet.v1.AgencyPointShareStats + (*GetAgencyPointShareStatsRequest)(nil), // 279: hyapp.wallet.v1.GetAgencyPointShareStatsRequest + (*GetAgencyPointShareStatsResponse)(nil), // 280: hyapp.wallet.v1.GetAgencyPointShareStatsResponse + (*AgencyHostGiftStats)(nil), // 281: hyapp.wallet.v1.AgencyHostGiftStats + (*GetAgencyHostGiftStatsRequest)(nil), // 282: hyapp.wallet.v1.GetAgencyHostGiftStatsRequest + (*GetAgencyHostGiftStatsResponse)(nil), // 283: hyapp.wallet.v1.GetAgencyHostGiftStatsResponse + (*PointWithdrawalCoinSellerConfig)(nil), // 284: hyapp.wallet.v1.PointWithdrawalCoinSellerConfig + (*ListPointWithdrawalCoinSellersRequest)(nil), // 285: hyapp.wallet.v1.ListPointWithdrawalCoinSellersRequest + (*ListPointWithdrawalCoinSellersResponse)(nil), // 286: hyapp.wallet.v1.ListPointWithdrawalCoinSellersResponse + (*TransferPointToCoinSellerRequest)(nil), // 287: hyapp.wallet.v1.TransferPointToCoinSellerRequest + (*TransferPointToCoinSellerResponse)(nil), // 288: hyapp.wallet.v1.TransferPointToCoinSellerResponse + (*GetPointWithdrawalConfigRequest)(nil), // 289: hyapp.wallet.v1.GetPointWithdrawalConfigRequest + (*GetPointWithdrawalConfigResponse)(nil), // 290: hyapp.wallet.v1.GetPointWithdrawalConfigResponse + nil, // 291: hyapp.wallet.v1.HandleV5PayNotifyRequest.FieldsEntry } var file_proto_wallet_v1_wallet_proto_depIdxs = []int32{ 3, // 0: hyapp.wallet.v1.BatchDebitGiftRequest.targets:type_name -> hyapp.wallet.v1.DebitGiftTarget @@ -26946,286 +31695,359 @@ var file_proto_wallet_v1_wallet_proto_depIdxs = []int32{ 118, // 53: hyapp.wallet.v1.GetRechargeBillSummaryResponse.google_play:type_name -> hyapp.wallet.v1.RechargeBillSummaryBucket 118, // 54: hyapp.wallet.v1.GetRechargeBillSummaryResponse.third_party:type_name -> hyapp.wallet.v1.RechargeBillSummaryBucket 118, // 55: hyapp.wallet.v1.GetRechargeBillSummaryResponse.coin_seller:type_name -> hyapp.wallet.v1.RechargeBillSummaryBucket - 121, // 56: hyapp.wallet.v1.GetRechargeBillOverviewResponse.daily:type_name -> hyapp.wallet.v1.RechargeBillDailyBucket - 122, // 57: hyapp.wallet.v1.GetRechargeBillOverviewResponse.regions:type_name -> hyapp.wallet.v1.RechargeBillRegionBucket - 123, // 58: hyapp.wallet.v1.GetRechargeBillOverviewResponse.google_paid:type_name -> hyapp.wallet.v1.RechargeBillGooglePaidStats - 124, // 59: hyapp.wallet.v1.GetRechargeBillOverviewResponse.salary_transfer:type_name -> hyapp.wallet.v1.RechargeBillSalaryTransferStats - 127, // 60: hyapp.wallet.v1.RefreshGooglePaymentPricesResponse.prices:type_name -> hyapp.wallet.v1.GooglePaymentPrice - 8, // 61: hyapp.wallet.v1.GetWalletOverviewResponse.balances:type_name -> hyapp.wallet.v1.AssetBalance - 129, // 62: hyapp.wallet.v1.GetWalletOverviewResponse.feature_flags:type_name -> hyapp.wallet.v1.WalletFeatureFlags - 132, // 63: hyapp.wallet.v1.GetWalletValueSummaryResponse.summary:type_name -> hyapp.wallet.v1.WalletValueSummary - 48, // 64: hyapp.wallet.v1.GiftWallItem.resource:type_name -> hyapp.wallet.v1.Resource - 135, // 65: hyapp.wallet.v1.GetUserGiftWallResponse.items:type_name -> hyapp.wallet.v1.GiftWallItem - 138, // 66: hyapp.wallet.v1.ListRechargeProductsResponse.products:type_name -> hyapp.wallet.v1.RechargeProduct - 8, // 67: hyapp.wallet.v1.ConfirmGooglePaymentResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 138, // 68: hyapp.wallet.v1.ListAdminRechargeProductsResponse.products:type_name -> hyapp.wallet.v1.RechargeProduct - 138, // 69: hyapp.wallet.v1.RechargeProductResponse.product:type_name -> hyapp.wallet.v1.RechargeProduct - 150, // 70: hyapp.wallet.v1.ThirdPartyPaymentChannel.methods:type_name -> hyapp.wallet.v1.ThirdPartyPaymentMethod - 151, // 71: hyapp.wallet.v1.ListThirdPartyPaymentChannelsResponse.channels:type_name -> hyapp.wallet.v1.ThirdPartyPaymentChannel - 150, // 72: hyapp.wallet.v1.ThirdPartyPaymentMethodResponse.method:type_name -> hyapp.wallet.v1.ThirdPartyPaymentMethod - 157, // 73: hyapp.wallet.v1.SyncThirdPartyPaymentMethodsResponse.issues:type_name -> hyapp.wallet.v1.ThirdPartyPaymentMethodSyncIssue - 138, // 74: hyapp.wallet.v1.H5RechargeOptionsResponse.products:type_name -> hyapp.wallet.v1.RechargeProduct - 150, // 75: hyapp.wallet.v1.H5RechargeOptionsResponse.payment_methods:type_name -> hyapp.wallet.v1.ThirdPartyPaymentMethod - 162, // 76: hyapp.wallet.v1.ListTemporaryRechargeOrdersResponse.orders:type_name -> hyapp.wallet.v1.ExternalRechargeOrder - 162, // 77: hyapp.wallet.v1.H5RechargeOrderResponse.order:type_name -> hyapp.wallet.v1.ExternalRechargeOrder - 243, // 78: hyapp.wallet.v1.HandleV5PayNotifyRequest.fields:type_name -> hyapp.wallet.v1.HandleV5PayNotifyRequest.FieldsEntry - 177, // 79: hyapp.wallet.v1.GetDiamondExchangeConfigResponse.rules:type_name -> hyapp.wallet.v1.DiamondExchangeRule - 180, // 80: hyapp.wallet.v1.ListWalletTransactionsResponse.transactions:type_name -> hyapp.wallet.v1.WalletTransaction - 183, // 81: hyapp.wallet.v1.VipLevel.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem - 185, // 82: hyapp.wallet.v1.ListVipPackagesResponse.current_vip:type_name -> hyapp.wallet.v1.UserVip - 184, // 83: hyapp.wallet.v1.ListVipPackagesResponse.packages:type_name -> hyapp.wallet.v1.VipLevel - 185, // 84: hyapp.wallet.v1.GetMyVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip - 185, // 85: hyapp.wallet.v1.PurchaseVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip - 183, // 86: hyapp.wallet.v1.PurchaseVipResponse.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem - 8, // 87: hyapp.wallet.v1.DebitCPBreakupFeeResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 8, // 88: hyapp.wallet.v1.DebitWheelDrawResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 185, // 89: hyapp.wallet.v1.GrantVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip - 183, // 90: hyapp.wallet.v1.GrantVipResponse.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem - 184, // 91: hyapp.wallet.v1.ListAdminVipLevelsResponse.levels:type_name -> hyapp.wallet.v1.VipLevel - 198, // 92: hyapp.wallet.v1.UpdateAdminVipLevelsRequest.levels:type_name -> hyapp.wallet.v1.AdminVipLevelInput - 184, // 93: hyapp.wallet.v1.UpdateAdminVipLevelsResponse.levels:type_name -> hyapp.wallet.v1.VipLevel - 8, // 94: hyapp.wallet.v1.CreditTaskRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 8, // 95: hyapp.wallet.v1.CreditLuckyGiftRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 8, // 96: hyapp.wallet.v1.CreditWheelRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 8, // 97: hyapp.wallet.v1.CreditRoomTurnoverRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 8, // 98: hyapp.wallet.v1.CreditInviteActivityRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 8, // 99: hyapp.wallet.v1.CreditAgencyOpeningRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 218, // 100: hyapp.wallet.v1.RedPacket.claims:type_name -> hyapp.wallet.v1.RedPacketClaim - 217, // 101: hyapp.wallet.v1.GetRedPacketConfigResponse.config:type_name -> hyapp.wallet.v1.RedPacketConfig - 217, // 102: hyapp.wallet.v1.UpdateRedPacketConfigResponse.config:type_name -> hyapp.wallet.v1.RedPacketConfig - 219, // 103: hyapp.wallet.v1.CreateRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket - 218, // 104: hyapp.wallet.v1.ClaimRedPacketResponse.claim:type_name -> hyapp.wallet.v1.RedPacketClaim - 219, // 105: hyapp.wallet.v1.ClaimRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket - 219, // 106: hyapp.wallet.v1.ListRedPacketsResponse.packets:type_name -> hyapp.wallet.v1.RedPacket - 219, // 107: hyapp.wallet.v1.GetRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket - 219, // 108: hyapp.wallet.v1.RetryRedPacketRefundResponse.packet:type_name -> hyapp.wallet.v1.RedPacket - 48, // 109: hyapp.wallet.v1.ResourceShopPurchaseOrder.resource:type_name -> hyapp.wallet.v1.Resource - 238, // 110: hyapp.wallet.v1.ListResourceShopPurchaseOrdersResponse.orders:type_name -> hyapp.wallet.v1.ResourceShopPurchaseOrder - 236, // 111: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryDailySettlementBatch:input_type -> hyapp.wallet.v1.CronBatchRequest - 236, // 112: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryHalfMonthSettlementBatch:input_type -> hyapp.wallet.v1.CronBatchRequest - 236, // 113: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryMonthEndBatch:input_type -> hyapp.wallet.v1.CronBatchRequest - 0, // 114: hyapp.wallet.v1.WalletService.DebitGift:input_type -> hyapp.wallet.v1.DebitGiftRequest - 1, // 115: hyapp.wallet.v1.WalletService.DebitDirectGift:input_type -> hyapp.wallet.v1.DebitDirectGiftRequest - 4, // 116: hyapp.wallet.v1.WalletService.BatchDebitGift:input_type -> hyapp.wallet.v1.BatchDebitGiftRequest - 7, // 117: hyapp.wallet.v1.WalletService.DebitRobotGift:input_type -> hyapp.wallet.v1.DebitRobotGiftRequest - 9, // 118: hyapp.wallet.v1.WalletService.GetBalances:input_type -> hyapp.wallet.v1.GetBalancesRequest - 13, // 119: hyapp.wallet.v1.WalletService.GetActiveHostSalaryPolicy:input_type -> hyapp.wallet.v1.GetActiveHostSalaryPolicyRequest - 16, // 120: hyapp.wallet.v1.WalletService.GetHostSalaryProgress:input_type -> hyapp.wallet.v1.GetHostSalaryProgressRequest - 19, // 121: hyapp.wallet.v1.WalletService.GetTeamHostSalaryStats:input_type -> hyapp.wallet.v1.GetTeamHostSalaryStatsRequest - 21, // 122: hyapp.wallet.v1.WalletService.AdminCreditAsset:input_type -> hyapp.wallet.v1.AdminCreditAssetRequest - 23, // 123: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:input_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockRequest - 25, // 124: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:input_type -> hyapp.wallet.v1.TransferCoinFromSellerRequest - 27, // 125: hyapp.wallet.v1.WalletService.TransferCoinSellerStockToChild:input_type -> hyapp.wallet.v1.TransferCoinSellerStockToChildRequest - 30, // 126: hyapp.wallet.v1.WalletService.ListCoinSellerSalaryExchangeRateTiers:input_type -> hyapp.wallet.v1.ListCoinSellerSalaryExchangeRateTiersRequest - 32, // 127: hyapp.wallet.v1.WalletService.ExchangeSalaryToCoin:input_type -> hyapp.wallet.v1.ExchangeSalaryToCoinRequest - 34, // 128: hyapp.wallet.v1.WalletService.TransferSalaryToCoinSeller:input_type -> hyapp.wallet.v1.TransferSalaryToCoinSellerRequest - 36, // 129: hyapp.wallet.v1.WalletService.FreezeSalaryWithdrawal:input_type -> hyapp.wallet.v1.FreezeSalaryWithdrawalRequest - 38, // 130: hyapp.wallet.v1.WalletService.SettleSalaryWithdrawal:input_type -> hyapp.wallet.v1.SettleSalaryWithdrawalRequest - 40, // 131: hyapp.wallet.v1.WalletService.ReleaseSalaryWithdrawal:input_type -> hyapp.wallet.v1.ReleaseSalaryWithdrawalRequest - 42, // 132: hyapp.wallet.v1.WalletService.FreezePointWithdrawal:input_type -> hyapp.wallet.v1.FreezePointWithdrawalRequest - 44, // 133: hyapp.wallet.v1.WalletService.SettlePointWithdrawal:input_type -> hyapp.wallet.v1.SettlePointWithdrawalRequest - 46, // 134: hyapp.wallet.v1.WalletService.ReleasePointWithdrawal:input_type -> hyapp.wallet.v1.ReleasePointWithdrawalRequest - 58, // 135: hyapp.wallet.v1.WalletService.ListResources:input_type -> hyapp.wallet.v1.ListResourcesRequest - 60, // 136: hyapp.wallet.v1.WalletService.GetResource:input_type -> hyapp.wallet.v1.GetResourceRequest - 62, // 137: hyapp.wallet.v1.WalletService.CreateResource:input_type -> hyapp.wallet.v1.CreateResourceRequest - 63, // 138: hyapp.wallet.v1.WalletService.UpdateResource:input_type -> hyapp.wallet.v1.UpdateResourceRequest - 64, // 139: hyapp.wallet.v1.WalletService.SetResourceStatus:input_type -> hyapp.wallet.v1.SetResourceStatusRequest - 65, // 140: hyapp.wallet.v1.WalletService.DeleteResource:input_type -> hyapp.wallet.v1.DeleteResourceRequest - 66, // 141: hyapp.wallet.v1.WalletService.BatchDeleteResources:input_type -> hyapp.wallet.v1.BatchDeleteResourcesRequest - 69, // 142: hyapp.wallet.v1.WalletService.ListResourceGroups:input_type -> hyapp.wallet.v1.ListResourceGroupsRequest - 71, // 143: hyapp.wallet.v1.WalletService.GetResourceGroup:input_type -> hyapp.wallet.v1.GetResourceGroupRequest - 73, // 144: hyapp.wallet.v1.WalletService.CreateResourceGroup:input_type -> hyapp.wallet.v1.CreateResourceGroupRequest - 74, // 145: hyapp.wallet.v1.WalletService.UpdateResourceGroup:input_type -> hyapp.wallet.v1.UpdateResourceGroupRequest - 75, // 146: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:input_type -> hyapp.wallet.v1.SetResourceGroupStatusRequest - 77, // 147: hyapp.wallet.v1.WalletService.ListGiftConfigs:input_type -> hyapp.wallet.v1.ListGiftConfigsRequest - 79, // 148: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:input_type -> hyapp.wallet.v1.ListGiftTypeConfigsRequest - 241, // 149: hyapp.wallet.v1.WalletService.GetGiftCatalogVersion:input_type -> hyapp.wallet.v1.GetGiftCatalogVersionRequest - 83, // 150: hyapp.wallet.v1.WalletService.CreateGiftConfig:input_type -> hyapp.wallet.v1.CreateGiftConfigRequest - 84, // 151: hyapp.wallet.v1.WalletService.BatchCreateGiftConfigs:input_type -> hyapp.wallet.v1.BatchCreateGiftConfigsRequest - 86, // 152: hyapp.wallet.v1.WalletService.UpdateGiftConfig:input_type -> hyapp.wallet.v1.UpdateGiftConfigRequest - 87, // 153: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:input_type -> hyapp.wallet.v1.SetGiftConfigStatusRequest - 88, // 154: hyapp.wallet.v1.WalletService.DeleteGiftConfig:input_type -> hyapp.wallet.v1.DeleteGiftConfigRequest - 81, // 155: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:input_type -> hyapp.wallet.v1.UpsertGiftTypeConfigRequest - 90, // 156: hyapp.wallet.v1.WalletService.GrantResource:input_type -> hyapp.wallet.v1.GrantResourceRequest - 91, // 157: hyapp.wallet.v1.WalletService.GrantResourceGroup:input_type -> hyapp.wallet.v1.GrantResourceGroupRequest - 92, // 158: hyapp.wallet.v1.WalletService.RevokeResourceGrant:input_type -> hyapp.wallet.v1.RevokeResourceGrantRequest - 94, // 159: hyapp.wallet.v1.WalletService.ListUserResources:input_type -> hyapp.wallet.v1.ListUserResourcesRequest - 96, // 160: hyapp.wallet.v1.WalletService.EquipUserResource:input_type -> hyapp.wallet.v1.EquipUserResourceRequest - 98, // 161: hyapp.wallet.v1.WalletService.UnequipUserResource:input_type -> hyapp.wallet.v1.UnequipUserResourceRequest - 100, // 162: hyapp.wallet.v1.WalletService.BatchGetUserEquippedResources:input_type -> hyapp.wallet.v1.BatchGetUserEquippedResourcesRequest - 103, // 163: hyapp.wallet.v1.WalletService.ListResourceGrants:input_type -> hyapp.wallet.v1.ListResourceGrantsRequest - 106, // 164: hyapp.wallet.v1.WalletService.ListResourceShopItems:input_type -> hyapp.wallet.v1.ListResourceShopItemsRequest - 108, // 165: hyapp.wallet.v1.WalletService.UpsertResourceShopItems:input_type -> hyapp.wallet.v1.UpsertResourceShopItemsRequest - 110, // 166: hyapp.wallet.v1.WalletService.SetResourceShopItemStatus:input_type -> hyapp.wallet.v1.SetResourceShopItemStatusRequest - 239, // 167: hyapp.wallet.v1.WalletService.ListResourceShopPurchaseOrders:input_type -> hyapp.wallet.v1.ListResourceShopPurchaseOrdersRequest - 112, // 168: hyapp.wallet.v1.WalletService.PurchaseResourceShopItem:input_type -> hyapp.wallet.v1.PurchaseResourceShopItemRequest - 115, // 169: hyapp.wallet.v1.WalletService.ListRechargeBills:input_type -> hyapp.wallet.v1.ListRechargeBillsRequest - 117, // 170: hyapp.wallet.v1.WalletService.GetRechargeBillSummary:input_type -> hyapp.wallet.v1.GetRechargeBillSummaryRequest - 120, // 171: hyapp.wallet.v1.WalletService.GetRechargeBillOverview:input_type -> hyapp.wallet.v1.GetRechargeBillOverviewRequest - 126, // 172: hyapp.wallet.v1.WalletService.RefreshGooglePaymentPrices:input_type -> hyapp.wallet.v1.RefreshGooglePaymentPricesRequest - 130, // 173: hyapp.wallet.v1.WalletService.GetWalletOverview:input_type -> hyapp.wallet.v1.GetWalletOverviewRequest - 133, // 174: hyapp.wallet.v1.WalletService.GetWalletValueSummary:input_type -> hyapp.wallet.v1.GetWalletValueSummaryRequest - 136, // 175: hyapp.wallet.v1.WalletService.GetUserGiftWall:input_type -> hyapp.wallet.v1.GetUserGiftWallRequest - 139, // 176: hyapp.wallet.v1.WalletService.ListRechargeProducts:input_type -> hyapp.wallet.v1.ListRechargeProductsRequest - 141, // 177: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:input_type -> hyapp.wallet.v1.ConfirmGooglePaymentRequest - 143, // 178: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:input_type -> hyapp.wallet.v1.ListAdminRechargeProductsRequest - 145, // 179: hyapp.wallet.v1.WalletService.CreateRechargeProduct:input_type -> hyapp.wallet.v1.CreateRechargeProductRequest - 146, // 180: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:input_type -> hyapp.wallet.v1.UpdateRechargeProductRequest - 147, // 181: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:input_type -> hyapp.wallet.v1.DeleteRechargeProductRequest - 152, // 182: hyapp.wallet.v1.WalletService.ListThirdPartyPaymentChannels:input_type -> hyapp.wallet.v1.ListThirdPartyPaymentChannelsRequest - 154, // 183: hyapp.wallet.v1.WalletService.SetThirdPartyPaymentMethodStatus:input_type -> hyapp.wallet.v1.SetThirdPartyPaymentMethodStatusRequest - 156, // 184: hyapp.wallet.v1.WalletService.UpdateThirdPartyPaymentRate:input_type -> hyapp.wallet.v1.UpdateThirdPartyPaymentRateRequest - 158, // 185: hyapp.wallet.v1.WalletService.SyncThirdPartyPaymentMethods:input_type -> hyapp.wallet.v1.SyncThirdPartyPaymentMethodsRequest - 160, // 186: hyapp.wallet.v1.WalletService.ListH5RechargeOptions:input_type -> hyapp.wallet.v1.H5RechargeOptionsRequest - 163, // 187: hyapp.wallet.v1.WalletService.CreateH5RechargeOrder:input_type -> hyapp.wallet.v1.CreateH5RechargeOrderRequest - 164, // 188: hyapp.wallet.v1.WalletService.CreateTemporaryRechargeOrder:input_type -> hyapp.wallet.v1.CreateTemporaryRechargeOrderRequest - 165, // 189: hyapp.wallet.v1.WalletService.GetTemporaryRechargeOrder:input_type -> hyapp.wallet.v1.GetTemporaryRechargeOrderRequest - 166, // 190: hyapp.wallet.v1.WalletService.ListTemporaryRechargeOrders:input_type -> hyapp.wallet.v1.ListTemporaryRechargeOrdersRequest - 168, // 191: hyapp.wallet.v1.WalletService.SubmitH5RechargeTx:input_type -> hyapp.wallet.v1.SubmitH5RechargeTxRequest - 169, // 192: hyapp.wallet.v1.WalletService.GetH5RechargeOrder:input_type -> hyapp.wallet.v1.GetH5RechargeOrderRequest - 171, // 193: hyapp.wallet.v1.WalletService.VerifyCoinSellerRechargeReceipt:input_type -> hyapp.wallet.v1.VerifyCoinSellerRechargeReceiptRequest - 173, // 194: hyapp.wallet.v1.WalletService.HandleMifapayNotify:input_type -> hyapp.wallet.v1.HandleMifapayNotifyRequest - 175, // 195: hyapp.wallet.v1.WalletService.HandleV5PayNotify:input_type -> hyapp.wallet.v1.HandleV5PayNotifyRequest - 178, // 196: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:input_type -> hyapp.wallet.v1.GetDiamondExchangeConfigRequest - 181, // 197: hyapp.wallet.v1.WalletService.ListWalletTransactions:input_type -> hyapp.wallet.v1.ListWalletTransactionsRequest - 186, // 198: hyapp.wallet.v1.WalletService.ListVipPackages:input_type -> hyapp.wallet.v1.ListVipPackagesRequest - 188, // 199: hyapp.wallet.v1.WalletService.GetMyVip:input_type -> hyapp.wallet.v1.GetMyVipRequest - 190, // 200: hyapp.wallet.v1.WalletService.PurchaseVip:input_type -> hyapp.wallet.v1.PurchaseVipRequest - 192, // 201: hyapp.wallet.v1.WalletService.DebitCPBreakupFee:input_type -> hyapp.wallet.v1.DebitCPBreakupFeeRequest - 194, // 202: hyapp.wallet.v1.WalletService.DebitWheelDraw:input_type -> hyapp.wallet.v1.DebitWheelDrawRequest - 196, // 203: hyapp.wallet.v1.WalletService.GrantVip:input_type -> hyapp.wallet.v1.GrantVipRequest - 199, // 204: hyapp.wallet.v1.WalletService.ListAdminVipLevels:input_type -> hyapp.wallet.v1.ListAdminVipLevelsRequest - 201, // 205: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:input_type -> hyapp.wallet.v1.UpdateAdminVipLevelsRequest - 203, // 206: hyapp.wallet.v1.WalletService.CreditTaskReward:input_type -> hyapp.wallet.v1.CreditTaskRewardRequest - 205, // 207: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:input_type -> hyapp.wallet.v1.CreditLuckyGiftRewardRequest - 207, // 208: hyapp.wallet.v1.WalletService.CreditWheelReward:input_type -> hyapp.wallet.v1.CreditWheelRewardRequest - 209, // 209: hyapp.wallet.v1.WalletService.CreditRoomTurnoverReward:input_type -> hyapp.wallet.v1.CreditRoomTurnoverRewardRequest - 211, // 210: hyapp.wallet.v1.WalletService.CreditInviteActivityReward:input_type -> hyapp.wallet.v1.CreditInviteActivityRewardRequest - 213, // 211: hyapp.wallet.v1.WalletService.CreditAgencyOpeningReward:input_type -> hyapp.wallet.v1.CreditAgencyOpeningRewardRequest - 215, // 212: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:input_type -> hyapp.wallet.v1.ApplyGameCoinChangeRequest - 220, // 213: hyapp.wallet.v1.WalletService.GetRedPacketConfig:input_type -> hyapp.wallet.v1.GetRedPacketConfigRequest - 222, // 214: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:input_type -> hyapp.wallet.v1.UpdateRedPacketConfigRequest - 224, // 215: hyapp.wallet.v1.WalletService.CreateRedPacket:input_type -> hyapp.wallet.v1.CreateRedPacketRequest - 226, // 216: hyapp.wallet.v1.WalletService.ClaimRedPacket:input_type -> hyapp.wallet.v1.ClaimRedPacketRequest - 228, // 217: hyapp.wallet.v1.WalletService.ListRedPackets:input_type -> hyapp.wallet.v1.ListRedPacketsRequest - 230, // 218: hyapp.wallet.v1.WalletService.GetRedPacket:input_type -> hyapp.wallet.v1.GetRedPacketRequest - 232, // 219: hyapp.wallet.v1.WalletService.ExpireRedPackets:input_type -> hyapp.wallet.v1.ExpireRedPacketsRequest - 234, // 220: hyapp.wallet.v1.WalletService.RetryRedPacketRefund:input_type -> hyapp.wallet.v1.RetryRedPacketRefundRequest - 237, // 221: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryDailySettlementBatch:output_type -> hyapp.wallet.v1.CronBatchResponse - 237, // 222: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryHalfMonthSettlementBatch:output_type -> hyapp.wallet.v1.CronBatchResponse - 237, // 223: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryMonthEndBatch:output_type -> hyapp.wallet.v1.CronBatchResponse - 2, // 224: hyapp.wallet.v1.WalletService.DebitGift:output_type -> hyapp.wallet.v1.DebitGiftResponse - 2, // 225: hyapp.wallet.v1.WalletService.DebitDirectGift:output_type -> hyapp.wallet.v1.DebitGiftResponse - 6, // 226: hyapp.wallet.v1.WalletService.BatchDebitGift:output_type -> hyapp.wallet.v1.BatchDebitGiftResponse - 2, // 227: hyapp.wallet.v1.WalletService.DebitRobotGift:output_type -> hyapp.wallet.v1.DebitGiftResponse - 10, // 228: hyapp.wallet.v1.WalletService.GetBalances:output_type -> hyapp.wallet.v1.GetBalancesResponse - 14, // 229: hyapp.wallet.v1.WalletService.GetActiveHostSalaryPolicy:output_type -> hyapp.wallet.v1.GetActiveHostSalaryPolicyResponse - 17, // 230: hyapp.wallet.v1.WalletService.GetHostSalaryProgress:output_type -> hyapp.wallet.v1.GetHostSalaryProgressResponse - 20, // 231: hyapp.wallet.v1.WalletService.GetTeamHostSalaryStats:output_type -> hyapp.wallet.v1.GetTeamHostSalaryStatsResponse - 22, // 232: hyapp.wallet.v1.WalletService.AdminCreditAsset:output_type -> hyapp.wallet.v1.AdminCreditAssetResponse - 24, // 233: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:output_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockResponse - 26, // 234: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:output_type -> hyapp.wallet.v1.TransferCoinFromSellerResponse - 28, // 235: hyapp.wallet.v1.WalletService.TransferCoinSellerStockToChild:output_type -> hyapp.wallet.v1.TransferCoinSellerStockToChildResponse - 31, // 236: hyapp.wallet.v1.WalletService.ListCoinSellerSalaryExchangeRateTiers:output_type -> hyapp.wallet.v1.ListCoinSellerSalaryExchangeRateTiersResponse - 33, // 237: hyapp.wallet.v1.WalletService.ExchangeSalaryToCoin:output_type -> hyapp.wallet.v1.ExchangeSalaryToCoinResponse - 35, // 238: hyapp.wallet.v1.WalletService.TransferSalaryToCoinSeller:output_type -> hyapp.wallet.v1.TransferSalaryToCoinSellerResponse - 37, // 239: hyapp.wallet.v1.WalletService.FreezeSalaryWithdrawal:output_type -> hyapp.wallet.v1.FreezeSalaryWithdrawalResponse - 39, // 240: hyapp.wallet.v1.WalletService.SettleSalaryWithdrawal:output_type -> hyapp.wallet.v1.SettleSalaryWithdrawalResponse - 41, // 241: hyapp.wallet.v1.WalletService.ReleaseSalaryWithdrawal:output_type -> hyapp.wallet.v1.ReleaseSalaryWithdrawalResponse - 43, // 242: hyapp.wallet.v1.WalletService.FreezePointWithdrawal:output_type -> hyapp.wallet.v1.FreezePointWithdrawalResponse - 45, // 243: hyapp.wallet.v1.WalletService.SettlePointWithdrawal:output_type -> hyapp.wallet.v1.SettlePointWithdrawalResponse - 47, // 244: hyapp.wallet.v1.WalletService.ReleasePointWithdrawal:output_type -> hyapp.wallet.v1.ReleasePointWithdrawalResponse - 59, // 245: hyapp.wallet.v1.WalletService.ListResources:output_type -> hyapp.wallet.v1.ListResourcesResponse - 61, // 246: hyapp.wallet.v1.WalletService.GetResource:output_type -> hyapp.wallet.v1.GetResourceResponse - 68, // 247: hyapp.wallet.v1.WalletService.CreateResource:output_type -> hyapp.wallet.v1.ResourceResponse - 68, // 248: hyapp.wallet.v1.WalletService.UpdateResource:output_type -> hyapp.wallet.v1.ResourceResponse - 68, // 249: hyapp.wallet.v1.WalletService.SetResourceStatus:output_type -> hyapp.wallet.v1.ResourceResponse - 68, // 250: hyapp.wallet.v1.WalletService.DeleteResource:output_type -> hyapp.wallet.v1.ResourceResponse - 67, // 251: hyapp.wallet.v1.WalletService.BatchDeleteResources:output_type -> hyapp.wallet.v1.BatchDeleteResourcesResponse - 70, // 252: hyapp.wallet.v1.WalletService.ListResourceGroups:output_type -> hyapp.wallet.v1.ListResourceGroupsResponse - 72, // 253: hyapp.wallet.v1.WalletService.GetResourceGroup:output_type -> hyapp.wallet.v1.GetResourceGroupResponse - 76, // 254: hyapp.wallet.v1.WalletService.CreateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 76, // 255: hyapp.wallet.v1.WalletService.UpdateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 76, // 256: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 78, // 257: hyapp.wallet.v1.WalletService.ListGiftConfigs:output_type -> hyapp.wallet.v1.ListGiftConfigsResponse - 80, // 258: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:output_type -> hyapp.wallet.v1.ListGiftTypeConfigsResponse - 242, // 259: hyapp.wallet.v1.WalletService.GetGiftCatalogVersion:output_type -> hyapp.wallet.v1.GetGiftCatalogVersionResponse - 89, // 260: hyapp.wallet.v1.WalletService.CreateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse - 85, // 261: hyapp.wallet.v1.WalletService.BatchCreateGiftConfigs:output_type -> hyapp.wallet.v1.BatchCreateGiftConfigsResponse - 89, // 262: hyapp.wallet.v1.WalletService.UpdateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse - 89, // 263: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:output_type -> hyapp.wallet.v1.GiftConfigResponse - 89, // 264: hyapp.wallet.v1.WalletService.DeleteGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse - 82, // 265: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:output_type -> hyapp.wallet.v1.GiftTypeConfigResponse - 93, // 266: hyapp.wallet.v1.WalletService.GrantResource:output_type -> hyapp.wallet.v1.ResourceGrantResponse - 93, // 267: hyapp.wallet.v1.WalletService.GrantResourceGroup:output_type -> hyapp.wallet.v1.ResourceGrantResponse - 93, // 268: hyapp.wallet.v1.WalletService.RevokeResourceGrant:output_type -> hyapp.wallet.v1.ResourceGrantResponse - 95, // 269: hyapp.wallet.v1.WalletService.ListUserResources:output_type -> hyapp.wallet.v1.ListUserResourcesResponse - 97, // 270: hyapp.wallet.v1.WalletService.EquipUserResource:output_type -> hyapp.wallet.v1.EquipUserResourceResponse - 99, // 271: hyapp.wallet.v1.WalletService.UnequipUserResource:output_type -> hyapp.wallet.v1.UnequipUserResourceResponse - 102, // 272: hyapp.wallet.v1.WalletService.BatchGetUserEquippedResources:output_type -> hyapp.wallet.v1.BatchGetUserEquippedResourcesResponse - 104, // 273: hyapp.wallet.v1.WalletService.ListResourceGrants:output_type -> hyapp.wallet.v1.ListResourceGrantsResponse - 107, // 274: hyapp.wallet.v1.WalletService.ListResourceShopItems:output_type -> hyapp.wallet.v1.ListResourceShopItemsResponse - 109, // 275: hyapp.wallet.v1.WalletService.UpsertResourceShopItems:output_type -> hyapp.wallet.v1.UpsertResourceShopItemsResponse - 111, // 276: hyapp.wallet.v1.WalletService.SetResourceShopItemStatus:output_type -> hyapp.wallet.v1.ResourceShopItemResponse - 240, // 277: hyapp.wallet.v1.WalletService.ListResourceShopPurchaseOrders:output_type -> hyapp.wallet.v1.ListResourceShopPurchaseOrdersResponse - 113, // 278: hyapp.wallet.v1.WalletService.PurchaseResourceShopItem:output_type -> hyapp.wallet.v1.PurchaseResourceShopItemResponse - 116, // 279: hyapp.wallet.v1.WalletService.ListRechargeBills:output_type -> hyapp.wallet.v1.ListRechargeBillsResponse - 119, // 280: hyapp.wallet.v1.WalletService.GetRechargeBillSummary:output_type -> hyapp.wallet.v1.GetRechargeBillSummaryResponse - 125, // 281: hyapp.wallet.v1.WalletService.GetRechargeBillOverview:output_type -> hyapp.wallet.v1.GetRechargeBillOverviewResponse - 128, // 282: hyapp.wallet.v1.WalletService.RefreshGooglePaymentPrices:output_type -> hyapp.wallet.v1.RefreshGooglePaymentPricesResponse - 131, // 283: hyapp.wallet.v1.WalletService.GetWalletOverview:output_type -> hyapp.wallet.v1.GetWalletOverviewResponse - 134, // 284: hyapp.wallet.v1.WalletService.GetWalletValueSummary:output_type -> hyapp.wallet.v1.GetWalletValueSummaryResponse - 137, // 285: hyapp.wallet.v1.WalletService.GetUserGiftWall:output_type -> hyapp.wallet.v1.GetUserGiftWallResponse - 140, // 286: hyapp.wallet.v1.WalletService.ListRechargeProducts:output_type -> hyapp.wallet.v1.ListRechargeProductsResponse - 142, // 287: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:output_type -> hyapp.wallet.v1.ConfirmGooglePaymentResponse - 144, // 288: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:output_type -> hyapp.wallet.v1.ListAdminRechargeProductsResponse - 148, // 289: hyapp.wallet.v1.WalletService.CreateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse - 148, // 290: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse - 149, // 291: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:output_type -> hyapp.wallet.v1.DeleteRechargeProductResponse - 153, // 292: hyapp.wallet.v1.WalletService.ListThirdPartyPaymentChannels:output_type -> hyapp.wallet.v1.ListThirdPartyPaymentChannelsResponse - 155, // 293: hyapp.wallet.v1.WalletService.SetThirdPartyPaymentMethodStatus:output_type -> hyapp.wallet.v1.ThirdPartyPaymentMethodResponse - 155, // 294: hyapp.wallet.v1.WalletService.UpdateThirdPartyPaymentRate:output_type -> hyapp.wallet.v1.ThirdPartyPaymentMethodResponse - 159, // 295: hyapp.wallet.v1.WalletService.SyncThirdPartyPaymentMethods:output_type -> hyapp.wallet.v1.SyncThirdPartyPaymentMethodsResponse - 161, // 296: hyapp.wallet.v1.WalletService.ListH5RechargeOptions:output_type -> hyapp.wallet.v1.H5RechargeOptionsResponse - 170, // 297: hyapp.wallet.v1.WalletService.CreateH5RechargeOrder:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse - 170, // 298: hyapp.wallet.v1.WalletService.CreateTemporaryRechargeOrder:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse - 170, // 299: hyapp.wallet.v1.WalletService.GetTemporaryRechargeOrder:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse - 167, // 300: hyapp.wallet.v1.WalletService.ListTemporaryRechargeOrders:output_type -> hyapp.wallet.v1.ListTemporaryRechargeOrdersResponse - 170, // 301: hyapp.wallet.v1.WalletService.SubmitH5RechargeTx:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse - 170, // 302: hyapp.wallet.v1.WalletService.GetH5RechargeOrder:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse - 172, // 303: hyapp.wallet.v1.WalletService.VerifyCoinSellerRechargeReceipt:output_type -> hyapp.wallet.v1.VerifyCoinSellerRechargeReceiptResponse - 174, // 304: hyapp.wallet.v1.WalletService.HandleMifapayNotify:output_type -> hyapp.wallet.v1.HandleMifapayNotifyResponse - 176, // 305: hyapp.wallet.v1.WalletService.HandleV5PayNotify:output_type -> hyapp.wallet.v1.HandleV5PayNotifyResponse - 179, // 306: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:output_type -> hyapp.wallet.v1.GetDiamondExchangeConfigResponse - 182, // 307: hyapp.wallet.v1.WalletService.ListWalletTransactions:output_type -> hyapp.wallet.v1.ListWalletTransactionsResponse - 187, // 308: hyapp.wallet.v1.WalletService.ListVipPackages:output_type -> hyapp.wallet.v1.ListVipPackagesResponse - 189, // 309: hyapp.wallet.v1.WalletService.GetMyVip:output_type -> hyapp.wallet.v1.GetMyVipResponse - 191, // 310: hyapp.wallet.v1.WalletService.PurchaseVip:output_type -> hyapp.wallet.v1.PurchaseVipResponse - 193, // 311: hyapp.wallet.v1.WalletService.DebitCPBreakupFee:output_type -> hyapp.wallet.v1.DebitCPBreakupFeeResponse - 195, // 312: hyapp.wallet.v1.WalletService.DebitWheelDraw:output_type -> hyapp.wallet.v1.DebitWheelDrawResponse - 197, // 313: hyapp.wallet.v1.WalletService.GrantVip:output_type -> hyapp.wallet.v1.GrantVipResponse - 200, // 314: hyapp.wallet.v1.WalletService.ListAdminVipLevels:output_type -> hyapp.wallet.v1.ListAdminVipLevelsResponse - 202, // 315: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:output_type -> hyapp.wallet.v1.UpdateAdminVipLevelsResponse - 204, // 316: hyapp.wallet.v1.WalletService.CreditTaskReward:output_type -> hyapp.wallet.v1.CreditTaskRewardResponse - 206, // 317: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:output_type -> hyapp.wallet.v1.CreditLuckyGiftRewardResponse - 208, // 318: hyapp.wallet.v1.WalletService.CreditWheelReward:output_type -> hyapp.wallet.v1.CreditWheelRewardResponse - 210, // 319: hyapp.wallet.v1.WalletService.CreditRoomTurnoverReward:output_type -> hyapp.wallet.v1.CreditRoomTurnoverRewardResponse - 212, // 320: hyapp.wallet.v1.WalletService.CreditInviteActivityReward:output_type -> hyapp.wallet.v1.CreditInviteActivityRewardResponse - 214, // 321: hyapp.wallet.v1.WalletService.CreditAgencyOpeningReward:output_type -> hyapp.wallet.v1.CreditAgencyOpeningRewardResponse - 216, // 322: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:output_type -> hyapp.wallet.v1.ApplyGameCoinChangeResponse - 221, // 323: hyapp.wallet.v1.WalletService.GetRedPacketConfig:output_type -> hyapp.wallet.v1.GetRedPacketConfigResponse - 223, // 324: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:output_type -> hyapp.wallet.v1.UpdateRedPacketConfigResponse - 225, // 325: hyapp.wallet.v1.WalletService.CreateRedPacket:output_type -> hyapp.wallet.v1.CreateRedPacketResponse - 227, // 326: hyapp.wallet.v1.WalletService.ClaimRedPacket:output_type -> hyapp.wallet.v1.ClaimRedPacketResponse - 229, // 327: hyapp.wallet.v1.WalletService.ListRedPackets:output_type -> hyapp.wallet.v1.ListRedPacketsResponse - 231, // 328: hyapp.wallet.v1.WalletService.GetRedPacket:output_type -> hyapp.wallet.v1.GetRedPacketResponse - 233, // 329: hyapp.wallet.v1.WalletService.ExpireRedPackets:output_type -> hyapp.wallet.v1.ExpireRedPacketsResponse - 235, // 330: hyapp.wallet.v1.WalletService.RetryRedPacketRefund:output_type -> hyapp.wallet.v1.RetryRedPacketRefundResponse - 221, // [221:331] is the sub-list for method output_type - 111, // [111:221] is the sub-list for method input_type - 111, // [111:111] is the sub-list for extension type_name - 111, // [111:111] is the sub-list for extension extendee - 0, // [0:111] is the sub-list for field type_name + 120, // 56: hyapp.wallet.v1.BatchGetUserRechargeStatsResponse.items:type_name -> hyapp.wallet.v1.UserRechargeStats + 124, // 57: hyapp.wallet.v1.GetRechargeBillOverviewResponse.daily:type_name -> hyapp.wallet.v1.RechargeBillDailyBucket + 125, // 58: hyapp.wallet.v1.GetRechargeBillOverviewResponse.regions:type_name -> hyapp.wallet.v1.RechargeBillRegionBucket + 126, // 59: hyapp.wallet.v1.GetRechargeBillOverviewResponse.google_paid:type_name -> hyapp.wallet.v1.RechargeBillGooglePaidStats + 127, // 60: hyapp.wallet.v1.GetRechargeBillOverviewResponse.salary_transfer:type_name -> hyapp.wallet.v1.RechargeBillSalaryTransferStats + 130, // 61: hyapp.wallet.v1.RefreshGooglePaymentPricesResponse.prices:type_name -> hyapp.wallet.v1.GooglePaymentPrice + 8, // 62: hyapp.wallet.v1.GetWalletOverviewResponse.balances:type_name -> hyapp.wallet.v1.AssetBalance + 132, // 63: hyapp.wallet.v1.GetWalletOverviewResponse.feature_flags:type_name -> hyapp.wallet.v1.WalletFeatureFlags + 135, // 64: hyapp.wallet.v1.GetWalletValueSummaryResponse.summary:type_name -> hyapp.wallet.v1.WalletValueSummary + 48, // 65: hyapp.wallet.v1.GiftWallItem.resource:type_name -> hyapp.wallet.v1.Resource + 138, // 66: hyapp.wallet.v1.GetUserGiftWallResponse.items:type_name -> hyapp.wallet.v1.GiftWallItem + 141, // 67: hyapp.wallet.v1.ListRechargeProductsResponse.products:type_name -> hyapp.wallet.v1.RechargeProduct + 8, // 68: hyapp.wallet.v1.ConfirmGooglePaymentResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance + 141, // 69: hyapp.wallet.v1.ListAdminRechargeProductsResponse.products:type_name -> hyapp.wallet.v1.RechargeProduct + 141, // 70: hyapp.wallet.v1.RechargeProductResponse.product:type_name -> hyapp.wallet.v1.RechargeProduct + 153, // 71: hyapp.wallet.v1.ThirdPartyPaymentChannel.methods:type_name -> hyapp.wallet.v1.ThirdPartyPaymentMethod + 154, // 72: hyapp.wallet.v1.ListThirdPartyPaymentChannelsResponse.channels:type_name -> hyapp.wallet.v1.ThirdPartyPaymentChannel + 153, // 73: hyapp.wallet.v1.ThirdPartyPaymentMethodResponse.method:type_name -> hyapp.wallet.v1.ThirdPartyPaymentMethod + 160, // 74: hyapp.wallet.v1.SyncThirdPartyPaymentMethodsResponse.issues:type_name -> hyapp.wallet.v1.ThirdPartyPaymentMethodSyncIssue + 141, // 75: hyapp.wallet.v1.H5RechargeOptionsResponse.products:type_name -> hyapp.wallet.v1.RechargeProduct + 153, // 76: hyapp.wallet.v1.H5RechargeOptionsResponse.payment_methods:type_name -> hyapp.wallet.v1.ThirdPartyPaymentMethod + 165, // 77: hyapp.wallet.v1.ListTemporaryRechargeOrdersResponse.orders:type_name -> hyapp.wallet.v1.ExternalRechargeOrder + 165, // 78: hyapp.wallet.v1.H5RechargeOrderResponse.order:type_name -> hyapp.wallet.v1.ExternalRechargeOrder + 291, // 79: hyapp.wallet.v1.HandleV5PayNotifyRequest.fields:type_name -> hyapp.wallet.v1.HandleV5PayNotifyRequest.FieldsEntry + 180, // 80: hyapp.wallet.v1.GetDiamondExchangeConfigResponse.rules:type_name -> hyapp.wallet.v1.DiamondExchangeRule + 183, // 81: hyapp.wallet.v1.ListWalletTransactionsResponse.transactions:type_name -> hyapp.wallet.v1.WalletTransaction + 186, // 82: hyapp.wallet.v1.VipLevel.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem + 188, // 83: hyapp.wallet.v1.VipLevel.benefits:type_name -> hyapp.wallet.v1.VipBenefit + 190, // 84: hyapp.wallet.v1.VipState.paid_vip:type_name -> hyapp.wallet.v1.UserVip + 191, // 85: hyapp.wallet.v1.VipState.equipped_trial_card:type_name -> hyapp.wallet.v1.VipTrialCard + 190, // 86: hyapp.wallet.v1.VipState.effective_vip:type_name -> hyapp.wallet.v1.UserVip + 188, // 87: hyapp.wallet.v1.VipState.effective_benefits:type_name -> hyapp.wallet.v1.VipBenefit + 187, // 88: hyapp.wallet.v1.VipState.program_config:type_name -> hyapp.wallet.v1.VipProgramConfig + 192, // 89: hyapp.wallet.v1.VipState.user_settings:type_name -> hyapp.wallet.v1.VipUserSettings + 190, // 90: hyapp.wallet.v1.ListVipPackagesResponse.current_vip:type_name -> hyapp.wallet.v1.UserVip + 189, // 91: hyapp.wallet.v1.ListVipPackagesResponse.packages:type_name -> hyapp.wallet.v1.VipLevel + 193, // 92: hyapp.wallet.v1.ListVipPackagesResponse.state:type_name -> hyapp.wallet.v1.VipState + 187, // 93: hyapp.wallet.v1.ListVipPackagesResponse.program_config:type_name -> hyapp.wallet.v1.VipProgramConfig + 190, // 94: hyapp.wallet.v1.GetMyVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip + 193, // 95: hyapp.wallet.v1.GetMyVipResponse.state:type_name -> hyapp.wallet.v1.VipState + 190, // 96: hyapp.wallet.v1.PurchaseVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip + 186, // 97: hyapp.wallet.v1.PurchaseVipResponse.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem + 193, // 98: hyapp.wallet.v1.PurchaseVipResponse.state:type_name -> hyapp.wallet.v1.VipState + 8, // 99: hyapp.wallet.v1.DebitCPBreakupFeeResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance + 8, // 100: hyapp.wallet.v1.DebitWheelDrawResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance + 190, // 101: hyapp.wallet.v1.GrantVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip + 186, // 102: hyapp.wallet.v1.GrantVipResponse.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem + 193, // 103: hyapp.wallet.v1.GrantVipResponse.state:type_name -> hyapp.wallet.v1.VipState + 187, // 104: hyapp.wallet.v1.GetVipProgramConfigResponse.config:type_name -> hyapp.wallet.v1.VipProgramConfig + 188, // 105: hyapp.wallet.v1.GetVipProgramConfigResponse.benefits:type_name -> hyapp.wallet.v1.VipBenefit + 187, // 106: hyapp.wallet.v1.UpdateAdminVipProgramConfigRequest.config:type_name -> hyapp.wallet.v1.VipProgramConfig + 187, // 107: hyapp.wallet.v1.UpdateAdminVipProgramConfigResponse.config:type_name -> hyapp.wallet.v1.VipProgramConfig + 192, // 108: hyapp.wallet.v1.UpdateMyVipSettingsResponse.settings:type_name -> hyapp.wallet.v1.VipUserSettings + 212, // 109: hyapp.wallet.v1.ProcessVipDailyCoinRebateBatchResponse.run:type_name -> hyapp.wallet.v1.VipDailyCoinRebateRun + 213, // 110: hyapp.wallet.v1.GetMyCurrentVipDailyCoinRebateResponse.rebate:type_name -> hyapp.wallet.v1.VipDailyCoinRebate + 213, // 111: hyapp.wallet.v1.ListMyVipDailyCoinRebateStatusesResponse.rebates:type_name -> hyapp.wallet.v1.VipDailyCoinRebate + 213, // 112: hyapp.wallet.v1.ClaimVipDailyCoinRebateResponse.rebate:type_name -> hyapp.wallet.v1.VipDailyCoinRebate + 8, // 113: hyapp.wallet.v1.ClaimVipDailyCoinRebateResponse.coin_balance:type_name -> hyapp.wallet.v1.AssetBalance + 191, // 114: hyapp.wallet.v1.GrantVipTrialCardResponse.trial_card:type_name -> hyapp.wallet.v1.VipTrialCard + 193, // 115: hyapp.wallet.v1.GrantVipTrialCardResponse.state:type_name -> hyapp.wallet.v1.VipState + 191, // 116: hyapp.wallet.v1.EquipVipTrialCardResponse.trial_card:type_name -> hyapp.wallet.v1.VipTrialCard + 193, // 117: hyapp.wallet.v1.EquipVipTrialCardResponse.state:type_name -> hyapp.wallet.v1.VipState + 193, // 118: hyapp.wallet.v1.UnequipVipTrialCardResponse.state:type_name -> hyapp.wallet.v1.VipState + 188, // 119: hyapp.wallet.v1.CheckVipBenefitResponse.benefit:type_name -> hyapp.wallet.v1.VipBenefit + 193, // 120: hyapp.wallet.v1.CheckVipBenefitResponse.state:type_name -> hyapp.wallet.v1.VipState + 188, // 121: hyapp.wallet.v1.AdminVipLevelInput.benefits:type_name -> hyapp.wallet.v1.VipBenefit + 189, // 122: hyapp.wallet.v1.ListAdminVipLevelsResponse.levels:type_name -> hyapp.wallet.v1.VipLevel + 187, // 123: hyapp.wallet.v1.ListAdminVipLevelsResponse.program_config:type_name -> hyapp.wallet.v1.VipProgramConfig + 230, // 124: hyapp.wallet.v1.UpdateAdminVipLevelsRequest.levels:type_name -> hyapp.wallet.v1.AdminVipLevelInput + 189, // 125: hyapp.wallet.v1.UpdateAdminVipLevelsResponse.levels:type_name -> hyapp.wallet.v1.VipLevel + 187, // 126: hyapp.wallet.v1.UpdateAdminVipLevelsResponse.program_config:type_name -> hyapp.wallet.v1.VipProgramConfig + 8, // 127: hyapp.wallet.v1.CreditTaskRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance + 8, // 128: hyapp.wallet.v1.CreditLuckyGiftRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance + 8, // 129: hyapp.wallet.v1.CreditWheelRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance + 8, // 130: hyapp.wallet.v1.CreditRoomTurnoverRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance + 8, // 131: hyapp.wallet.v1.CreditInviteActivityRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance + 8, // 132: hyapp.wallet.v1.CreditAgencyOpeningRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance + 250, // 133: hyapp.wallet.v1.RedPacket.claims:type_name -> hyapp.wallet.v1.RedPacketClaim + 249, // 134: hyapp.wallet.v1.GetRedPacketConfigResponse.config:type_name -> hyapp.wallet.v1.RedPacketConfig + 249, // 135: hyapp.wallet.v1.UpdateRedPacketConfigResponse.config:type_name -> hyapp.wallet.v1.RedPacketConfig + 251, // 136: hyapp.wallet.v1.CreateRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket + 250, // 137: hyapp.wallet.v1.ClaimRedPacketResponse.claim:type_name -> hyapp.wallet.v1.RedPacketClaim + 251, // 138: hyapp.wallet.v1.ClaimRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket + 251, // 139: hyapp.wallet.v1.ListRedPacketsResponse.packets:type_name -> hyapp.wallet.v1.RedPacket + 251, // 140: hyapp.wallet.v1.GetRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket + 251, // 141: hyapp.wallet.v1.RetryRedPacketRefundResponse.packet:type_name -> hyapp.wallet.v1.RedPacket + 48, // 142: hyapp.wallet.v1.ResourceShopPurchaseOrder.resource:type_name -> hyapp.wallet.v1.Resource + 270, // 143: hyapp.wallet.v1.ListResourceShopPurchaseOrdersResponse.orders:type_name -> hyapp.wallet.v1.ResourceShopPurchaseOrder + 275, // 144: hyapp.wallet.v1.GetHostRevenueStatsResponse.stats:type_name -> hyapp.wallet.v1.HostRevenueStats + 278, // 145: hyapp.wallet.v1.GetAgencyPointShareStatsResponse.stats:type_name -> hyapp.wallet.v1.AgencyPointShareStats + 281, // 146: hyapp.wallet.v1.GetAgencyHostGiftStatsResponse.stats:type_name -> hyapp.wallet.v1.AgencyHostGiftStats + 284, // 147: hyapp.wallet.v1.ListPointWithdrawalCoinSellersResponse.sellers:type_name -> hyapp.wallet.v1.PointWithdrawalCoinSellerConfig + 268, // 148: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryDailySettlementBatch:input_type -> hyapp.wallet.v1.CronBatchRequest + 268, // 149: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryHalfMonthSettlementBatch:input_type -> hyapp.wallet.v1.CronBatchRequest + 268, // 150: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryMonthEndBatch:input_type -> hyapp.wallet.v1.CronBatchRequest + 214, // 151: hyapp.wallet.v1.WalletCronService.ProcessVipDailyCoinRebateBatch:input_type -> hyapp.wallet.v1.ProcessVipDailyCoinRebateBatchRequest + 0, // 152: hyapp.wallet.v1.WalletService.DebitGift:input_type -> hyapp.wallet.v1.DebitGiftRequest + 1, // 153: hyapp.wallet.v1.WalletService.DebitDirectGift:input_type -> hyapp.wallet.v1.DebitDirectGiftRequest + 4, // 154: hyapp.wallet.v1.WalletService.BatchDebitGift:input_type -> hyapp.wallet.v1.BatchDebitGiftRequest + 7, // 155: hyapp.wallet.v1.WalletService.DebitRobotGift:input_type -> hyapp.wallet.v1.DebitRobotGiftRequest + 9, // 156: hyapp.wallet.v1.WalletService.GetBalances:input_type -> hyapp.wallet.v1.GetBalancesRequest + 13, // 157: hyapp.wallet.v1.WalletService.GetActiveHostSalaryPolicy:input_type -> hyapp.wallet.v1.GetActiveHostSalaryPolicyRequest + 16, // 158: hyapp.wallet.v1.WalletService.GetHostSalaryProgress:input_type -> hyapp.wallet.v1.GetHostSalaryProgressRequest + 276, // 159: hyapp.wallet.v1.WalletService.GetHostRevenueStats:input_type -> hyapp.wallet.v1.GetHostRevenueStatsRequest + 279, // 160: hyapp.wallet.v1.WalletService.GetAgencyPointShareStats:input_type -> hyapp.wallet.v1.GetAgencyPointShareStatsRequest + 282, // 161: hyapp.wallet.v1.WalletService.GetAgencyHostGiftStats:input_type -> hyapp.wallet.v1.GetAgencyHostGiftStatsRequest + 19, // 162: hyapp.wallet.v1.WalletService.GetTeamHostSalaryStats:input_type -> hyapp.wallet.v1.GetTeamHostSalaryStatsRequest + 21, // 163: hyapp.wallet.v1.WalletService.AdminCreditAsset:input_type -> hyapp.wallet.v1.AdminCreditAssetRequest + 23, // 164: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:input_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockRequest + 25, // 165: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:input_type -> hyapp.wallet.v1.TransferCoinFromSellerRequest + 27, // 166: hyapp.wallet.v1.WalletService.TransferCoinSellerStockToChild:input_type -> hyapp.wallet.v1.TransferCoinSellerStockToChildRequest + 30, // 167: hyapp.wallet.v1.WalletService.ListCoinSellerSalaryExchangeRateTiers:input_type -> hyapp.wallet.v1.ListCoinSellerSalaryExchangeRateTiersRequest + 32, // 168: hyapp.wallet.v1.WalletService.ExchangeSalaryToCoin:input_type -> hyapp.wallet.v1.ExchangeSalaryToCoinRequest + 34, // 169: hyapp.wallet.v1.WalletService.TransferSalaryToCoinSeller:input_type -> hyapp.wallet.v1.TransferSalaryToCoinSellerRequest + 285, // 170: hyapp.wallet.v1.WalletService.ListPointWithdrawalCoinSellers:input_type -> hyapp.wallet.v1.ListPointWithdrawalCoinSellersRequest + 287, // 171: hyapp.wallet.v1.WalletService.TransferPointToCoinSeller:input_type -> hyapp.wallet.v1.TransferPointToCoinSellerRequest + 289, // 172: hyapp.wallet.v1.WalletService.GetPointWithdrawalConfig:input_type -> hyapp.wallet.v1.GetPointWithdrawalConfigRequest + 36, // 173: hyapp.wallet.v1.WalletService.FreezeSalaryWithdrawal:input_type -> hyapp.wallet.v1.FreezeSalaryWithdrawalRequest + 38, // 174: hyapp.wallet.v1.WalletService.SettleSalaryWithdrawal:input_type -> hyapp.wallet.v1.SettleSalaryWithdrawalRequest + 40, // 175: hyapp.wallet.v1.WalletService.ReleaseSalaryWithdrawal:input_type -> hyapp.wallet.v1.ReleaseSalaryWithdrawalRequest + 42, // 176: hyapp.wallet.v1.WalletService.FreezePointWithdrawal:input_type -> hyapp.wallet.v1.FreezePointWithdrawalRequest + 44, // 177: hyapp.wallet.v1.WalletService.SettlePointWithdrawal:input_type -> hyapp.wallet.v1.SettlePointWithdrawalRequest + 46, // 178: hyapp.wallet.v1.WalletService.ReleasePointWithdrawal:input_type -> hyapp.wallet.v1.ReleasePointWithdrawalRequest + 58, // 179: hyapp.wallet.v1.WalletService.ListResources:input_type -> hyapp.wallet.v1.ListResourcesRequest + 60, // 180: hyapp.wallet.v1.WalletService.GetResource:input_type -> hyapp.wallet.v1.GetResourceRequest + 62, // 181: hyapp.wallet.v1.WalletService.CreateResource:input_type -> hyapp.wallet.v1.CreateResourceRequest + 63, // 182: hyapp.wallet.v1.WalletService.UpdateResource:input_type -> hyapp.wallet.v1.UpdateResourceRequest + 64, // 183: hyapp.wallet.v1.WalletService.SetResourceStatus:input_type -> hyapp.wallet.v1.SetResourceStatusRequest + 65, // 184: hyapp.wallet.v1.WalletService.DeleteResource:input_type -> hyapp.wallet.v1.DeleteResourceRequest + 66, // 185: hyapp.wallet.v1.WalletService.BatchDeleteResources:input_type -> hyapp.wallet.v1.BatchDeleteResourcesRequest + 69, // 186: hyapp.wallet.v1.WalletService.ListResourceGroups:input_type -> hyapp.wallet.v1.ListResourceGroupsRequest + 71, // 187: hyapp.wallet.v1.WalletService.GetResourceGroup:input_type -> hyapp.wallet.v1.GetResourceGroupRequest + 73, // 188: hyapp.wallet.v1.WalletService.CreateResourceGroup:input_type -> hyapp.wallet.v1.CreateResourceGroupRequest + 74, // 189: hyapp.wallet.v1.WalletService.UpdateResourceGroup:input_type -> hyapp.wallet.v1.UpdateResourceGroupRequest + 75, // 190: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:input_type -> hyapp.wallet.v1.SetResourceGroupStatusRequest + 77, // 191: hyapp.wallet.v1.WalletService.ListGiftConfigs:input_type -> hyapp.wallet.v1.ListGiftConfigsRequest + 79, // 192: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:input_type -> hyapp.wallet.v1.ListGiftTypeConfigsRequest + 273, // 193: hyapp.wallet.v1.WalletService.GetGiftCatalogVersion:input_type -> hyapp.wallet.v1.GetGiftCatalogVersionRequest + 83, // 194: hyapp.wallet.v1.WalletService.CreateGiftConfig:input_type -> hyapp.wallet.v1.CreateGiftConfigRequest + 84, // 195: hyapp.wallet.v1.WalletService.BatchCreateGiftConfigs:input_type -> hyapp.wallet.v1.BatchCreateGiftConfigsRequest + 86, // 196: hyapp.wallet.v1.WalletService.UpdateGiftConfig:input_type -> hyapp.wallet.v1.UpdateGiftConfigRequest + 87, // 197: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:input_type -> hyapp.wallet.v1.SetGiftConfigStatusRequest + 88, // 198: hyapp.wallet.v1.WalletService.DeleteGiftConfig:input_type -> hyapp.wallet.v1.DeleteGiftConfigRequest + 81, // 199: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:input_type -> hyapp.wallet.v1.UpsertGiftTypeConfigRequest + 90, // 200: hyapp.wallet.v1.WalletService.GrantResource:input_type -> hyapp.wallet.v1.GrantResourceRequest + 91, // 201: hyapp.wallet.v1.WalletService.GrantResourceGroup:input_type -> hyapp.wallet.v1.GrantResourceGroupRequest + 92, // 202: hyapp.wallet.v1.WalletService.RevokeResourceGrant:input_type -> hyapp.wallet.v1.RevokeResourceGrantRequest + 94, // 203: hyapp.wallet.v1.WalletService.ListUserResources:input_type -> hyapp.wallet.v1.ListUserResourcesRequest + 96, // 204: hyapp.wallet.v1.WalletService.EquipUserResource:input_type -> hyapp.wallet.v1.EquipUserResourceRequest + 98, // 205: hyapp.wallet.v1.WalletService.UnequipUserResource:input_type -> hyapp.wallet.v1.UnequipUserResourceRequest + 100, // 206: hyapp.wallet.v1.WalletService.BatchGetUserEquippedResources:input_type -> hyapp.wallet.v1.BatchGetUserEquippedResourcesRequest + 103, // 207: hyapp.wallet.v1.WalletService.ListResourceGrants:input_type -> hyapp.wallet.v1.ListResourceGrantsRequest + 106, // 208: hyapp.wallet.v1.WalletService.ListResourceShopItems:input_type -> hyapp.wallet.v1.ListResourceShopItemsRequest + 108, // 209: hyapp.wallet.v1.WalletService.UpsertResourceShopItems:input_type -> hyapp.wallet.v1.UpsertResourceShopItemsRequest + 110, // 210: hyapp.wallet.v1.WalletService.SetResourceShopItemStatus:input_type -> hyapp.wallet.v1.SetResourceShopItemStatusRequest + 271, // 211: hyapp.wallet.v1.WalletService.ListResourceShopPurchaseOrders:input_type -> hyapp.wallet.v1.ListResourceShopPurchaseOrdersRequest + 112, // 212: hyapp.wallet.v1.WalletService.PurchaseResourceShopItem:input_type -> hyapp.wallet.v1.PurchaseResourceShopItemRequest + 115, // 213: hyapp.wallet.v1.WalletService.ListRechargeBills:input_type -> hyapp.wallet.v1.ListRechargeBillsRequest + 117, // 214: hyapp.wallet.v1.WalletService.GetRechargeBillSummary:input_type -> hyapp.wallet.v1.GetRechargeBillSummaryRequest + 121, // 215: hyapp.wallet.v1.WalletService.BatchGetUserRechargeStats:input_type -> hyapp.wallet.v1.BatchGetUserRechargeStatsRequest + 123, // 216: hyapp.wallet.v1.WalletService.GetRechargeBillOverview:input_type -> hyapp.wallet.v1.GetRechargeBillOverviewRequest + 129, // 217: hyapp.wallet.v1.WalletService.RefreshGooglePaymentPrices:input_type -> hyapp.wallet.v1.RefreshGooglePaymentPricesRequest + 133, // 218: hyapp.wallet.v1.WalletService.GetWalletOverview:input_type -> hyapp.wallet.v1.GetWalletOverviewRequest + 136, // 219: hyapp.wallet.v1.WalletService.GetWalletValueSummary:input_type -> hyapp.wallet.v1.GetWalletValueSummaryRequest + 139, // 220: hyapp.wallet.v1.WalletService.GetUserGiftWall:input_type -> hyapp.wallet.v1.GetUserGiftWallRequest + 142, // 221: hyapp.wallet.v1.WalletService.ListRechargeProducts:input_type -> hyapp.wallet.v1.ListRechargeProductsRequest + 144, // 222: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:input_type -> hyapp.wallet.v1.ConfirmGooglePaymentRequest + 146, // 223: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:input_type -> hyapp.wallet.v1.ListAdminRechargeProductsRequest + 148, // 224: hyapp.wallet.v1.WalletService.CreateRechargeProduct:input_type -> hyapp.wallet.v1.CreateRechargeProductRequest + 149, // 225: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:input_type -> hyapp.wallet.v1.UpdateRechargeProductRequest + 150, // 226: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:input_type -> hyapp.wallet.v1.DeleteRechargeProductRequest + 155, // 227: hyapp.wallet.v1.WalletService.ListThirdPartyPaymentChannels:input_type -> hyapp.wallet.v1.ListThirdPartyPaymentChannelsRequest + 157, // 228: hyapp.wallet.v1.WalletService.SetThirdPartyPaymentMethodStatus:input_type -> hyapp.wallet.v1.SetThirdPartyPaymentMethodStatusRequest + 159, // 229: hyapp.wallet.v1.WalletService.UpdateThirdPartyPaymentRate:input_type -> hyapp.wallet.v1.UpdateThirdPartyPaymentRateRequest + 161, // 230: hyapp.wallet.v1.WalletService.SyncThirdPartyPaymentMethods:input_type -> hyapp.wallet.v1.SyncThirdPartyPaymentMethodsRequest + 163, // 231: hyapp.wallet.v1.WalletService.ListH5RechargeOptions:input_type -> hyapp.wallet.v1.H5RechargeOptionsRequest + 166, // 232: hyapp.wallet.v1.WalletService.CreateH5RechargeOrder:input_type -> hyapp.wallet.v1.CreateH5RechargeOrderRequest + 167, // 233: hyapp.wallet.v1.WalletService.CreateTemporaryRechargeOrder:input_type -> hyapp.wallet.v1.CreateTemporaryRechargeOrderRequest + 168, // 234: hyapp.wallet.v1.WalletService.GetTemporaryRechargeOrder:input_type -> hyapp.wallet.v1.GetTemporaryRechargeOrderRequest + 169, // 235: hyapp.wallet.v1.WalletService.ListTemporaryRechargeOrders:input_type -> hyapp.wallet.v1.ListTemporaryRechargeOrdersRequest + 171, // 236: hyapp.wallet.v1.WalletService.SubmitH5RechargeTx:input_type -> hyapp.wallet.v1.SubmitH5RechargeTxRequest + 172, // 237: hyapp.wallet.v1.WalletService.GetH5RechargeOrder:input_type -> hyapp.wallet.v1.GetH5RechargeOrderRequest + 174, // 238: hyapp.wallet.v1.WalletService.VerifyCoinSellerRechargeReceipt:input_type -> hyapp.wallet.v1.VerifyCoinSellerRechargeReceiptRequest + 176, // 239: hyapp.wallet.v1.WalletService.HandleMifapayNotify:input_type -> hyapp.wallet.v1.HandleMifapayNotifyRequest + 178, // 240: hyapp.wallet.v1.WalletService.HandleV5PayNotify:input_type -> hyapp.wallet.v1.HandleV5PayNotifyRequest + 181, // 241: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:input_type -> hyapp.wallet.v1.GetDiamondExchangeConfigRequest + 184, // 242: hyapp.wallet.v1.WalletService.ListWalletTransactions:input_type -> hyapp.wallet.v1.ListWalletTransactionsRequest + 194, // 243: hyapp.wallet.v1.WalletService.ListVipPackages:input_type -> hyapp.wallet.v1.ListVipPackagesRequest + 206, // 244: hyapp.wallet.v1.WalletService.GetVipProgramConfig:input_type -> hyapp.wallet.v1.GetVipProgramConfigRequest + 196, // 245: hyapp.wallet.v1.WalletService.GetMyVip:input_type -> hyapp.wallet.v1.GetMyVipRequest + 228, // 246: hyapp.wallet.v1.WalletService.CheckVipBenefit:input_type -> hyapp.wallet.v1.CheckVipBenefitRequest + 198, // 247: hyapp.wallet.v1.WalletService.PurchaseVip:input_type -> hyapp.wallet.v1.PurchaseVipRequest + 200, // 248: hyapp.wallet.v1.WalletService.DebitCPBreakupFee:input_type -> hyapp.wallet.v1.DebitCPBreakupFeeRequest + 202, // 249: hyapp.wallet.v1.WalletService.DebitWheelDraw:input_type -> hyapp.wallet.v1.DebitWheelDrawRequest + 204, // 250: hyapp.wallet.v1.WalletService.GrantVip:input_type -> hyapp.wallet.v1.GrantVipRequest + 222, // 251: hyapp.wallet.v1.WalletService.GrantVipTrialCard:input_type -> hyapp.wallet.v1.GrantVipTrialCardRequest + 224, // 252: hyapp.wallet.v1.WalletService.EquipVipTrialCard:input_type -> hyapp.wallet.v1.EquipVipTrialCardRequest + 226, // 253: hyapp.wallet.v1.WalletService.UnequipVipTrialCard:input_type -> hyapp.wallet.v1.UnequipVipTrialCardRequest + 231, // 254: hyapp.wallet.v1.WalletService.ListAdminVipLevels:input_type -> hyapp.wallet.v1.ListAdminVipLevelsRequest + 233, // 255: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:input_type -> hyapp.wallet.v1.UpdateAdminVipLevelsRequest + 208, // 256: hyapp.wallet.v1.WalletService.UpdateAdminVipProgramConfig:input_type -> hyapp.wallet.v1.UpdateAdminVipProgramConfigRequest + 210, // 257: hyapp.wallet.v1.WalletService.UpdateMyVipSettings:input_type -> hyapp.wallet.v1.UpdateMyVipSettingsRequest + 216, // 258: hyapp.wallet.v1.WalletService.GetMyCurrentVipDailyCoinRebate:input_type -> hyapp.wallet.v1.GetMyCurrentVipDailyCoinRebateRequest + 218, // 259: hyapp.wallet.v1.WalletService.ListMyVipDailyCoinRebateStatuses:input_type -> hyapp.wallet.v1.ListMyVipDailyCoinRebateStatusesRequest + 220, // 260: hyapp.wallet.v1.WalletService.ClaimVipDailyCoinRebate:input_type -> hyapp.wallet.v1.ClaimVipDailyCoinRebateRequest + 235, // 261: hyapp.wallet.v1.WalletService.CreditTaskReward:input_type -> hyapp.wallet.v1.CreditTaskRewardRequest + 237, // 262: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:input_type -> hyapp.wallet.v1.CreditLuckyGiftRewardRequest + 239, // 263: hyapp.wallet.v1.WalletService.CreditWheelReward:input_type -> hyapp.wallet.v1.CreditWheelRewardRequest + 241, // 264: hyapp.wallet.v1.WalletService.CreditRoomTurnoverReward:input_type -> hyapp.wallet.v1.CreditRoomTurnoverRewardRequest + 243, // 265: hyapp.wallet.v1.WalletService.CreditInviteActivityReward:input_type -> hyapp.wallet.v1.CreditInviteActivityRewardRequest + 245, // 266: hyapp.wallet.v1.WalletService.CreditAgencyOpeningReward:input_type -> hyapp.wallet.v1.CreditAgencyOpeningRewardRequest + 247, // 267: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:input_type -> hyapp.wallet.v1.ApplyGameCoinChangeRequest + 252, // 268: hyapp.wallet.v1.WalletService.GetRedPacketConfig:input_type -> hyapp.wallet.v1.GetRedPacketConfigRequest + 254, // 269: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:input_type -> hyapp.wallet.v1.UpdateRedPacketConfigRequest + 256, // 270: hyapp.wallet.v1.WalletService.CreateRedPacket:input_type -> hyapp.wallet.v1.CreateRedPacketRequest + 258, // 271: hyapp.wallet.v1.WalletService.ClaimRedPacket:input_type -> hyapp.wallet.v1.ClaimRedPacketRequest + 260, // 272: hyapp.wallet.v1.WalletService.ListRedPackets:input_type -> hyapp.wallet.v1.ListRedPacketsRequest + 262, // 273: hyapp.wallet.v1.WalletService.GetRedPacket:input_type -> hyapp.wallet.v1.GetRedPacketRequest + 264, // 274: hyapp.wallet.v1.WalletService.ExpireRedPackets:input_type -> hyapp.wallet.v1.ExpireRedPacketsRequest + 266, // 275: hyapp.wallet.v1.WalletService.RetryRedPacketRefund:input_type -> hyapp.wallet.v1.RetryRedPacketRefundRequest + 269, // 276: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryDailySettlementBatch:output_type -> hyapp.wallet.v1.CronBatchResponse + 269, // 277: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryHalfMonthSettlementBatch:output_type -> hyapp.wallet.v1.CronBatchResponse + 269, // 278: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryMonthEndBatch:output_type -> hyapp.wallet.v1.CronBatchResponse + 215, // 279: hyapp.wallet.v1.WalletCronService.ProcessVipDailyCoinRebateBatch:output_type -> hyapp.wallet.v1.ProcessVipDailyCoinRebateBatchResponse + 2, // 280: hyapp.wallet.v1.WalletService.DebitGift:output_type -> hyapp.wallet.v1.DebitGiftResponse + 2, // 281: hyapp.wallet.v1.WalletService.DebitDirectGift:output_type -> hyapp.wallet.v1.DebitGiftResponse + 6, // 282: hyapp.wallet.v1.WalletService.BatchDebitGift:output_type -> hyapp.wallet.v1.BatchDebitGiftResponse + 2, // 283: hyapp.wallet.v1.WalletService.DebitRobotGift:output_type -> hyapp.wallet.v1.DebitGiftResponse + 10, // 284: hyapp.wallet.v1.WalletService.GetBalances:output_type -> hyapp.wallet.v1.GetBalancesResponse + 14, // 285: hyapp.wallet.v1.WalletService.GetActiveHostSalaryPolicy:output_type -> hyapp.wallet.v1.GetActiveHostSalaryPolicyResponse + 17, // 286: hyapp.wallet.v1.WalletService.GetHostSalaryProgress:output_type -> hyapp.wallet.v1.GetHostSalaryProgressResponse + 277, // 287: hyapp.wallet.v1.WalletService.GetHostRevenueStats:output_type -> hyapp.wallet.v1.GetHostRevenueStatsResponse + 280, // 288: hyapp.wallet.v1.WalletService.GetAgencyPointShareStats:output_type -> hyapp.wallet.v1.GetAgencyPointShareStatsResponse + 283, // 289: hyapp.wallet.v1.WalletService.GetAgencyHostGiftStats:output_type -> hyapp.wallet.v1.GetAgencyHostGiftStatsResponse + 20, // 290: hyapp.wallet.v1.WalletService.GetTeamHostSalaryStats:output_type -> hyapp.wallet.v1.GetTeamHostSalaryStatsResponse + 22, // 291: hyapp.wallet.v1.WalletService.AdminCreditAsset:output_type -> hyapp.wallet.v1.AdminCreditAssetResponse + 24, // 292: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:output_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockResponse + 26, // 293: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:output_type -> hyapp.wallet.v1.TransferCoinFromSellerResponse + 28, // 294: hyapp.wallet.v1.WalletService.TransferCoinSellerStockToChild:output_type -> hyapp.wallet.v1.TransferCoinSellerStockToChildResponse + 31, // 295: hyapp.wallet.v1.WalletService.ListCoinSellerSalaryExchangeRateTiers:output_type -> hyapp.wallet.v1.ListCoinSellerSalaryExchangeRateTiersResponse + 33, // 296: hyapp.wallet.v1.WalletService.ExchangeSalaryToCoin:output_type -> hyapp.wallet.v1.ExchangeSalaryToCoinResponse + 35, // 297: hyapp.wallet.v1.WalletService.TransferSalaryToCoinSeller:output_type -> hyapp.wallet.v1.TransferSalaryToCoinSellerResponse + 286, // 298: hyapp.wallet.v1.WalletService.ListPointWithdrawalCoinSellers:output_type -> hyapp.wallet.v1.ListPointWithdrawalCoinSellersResponse + 288, // 299: hyapp.wallet.v1.WalletService.TransferPointToCoinSeller:output_type -> hyapp.wallet.v1.TransferPointToCoinSellerResponse + 290, // 300: hyapp.wallet.v1.WalletService.GetPointWithdrawalConfig:output_type -> hyapp.wallet.v1.GetPointWithdrawalConfigResponse + 37, // 301: hyapp.wallet.v1.WalletService.FreezeSalaryWithdrawal:output_type -> hyapp.wallet.v1.FreezeSalaryWithdrawalResponse + 39, // 302: hyapp.wallet.v1.WalletService.SettleSalaryWithdrawal:output_type -> hyapp.wallet.v1.SettleSalaryWithdrawalResponse + 41, // 303: hyapp.wallet.v1.WalletService.ReleaseSalaryWithdrawal:output_type -> hyapp.wallet.v1.ReleaseSalaryWithdrawalResponse + 43, // 304: hyapp.wallet.v1.WalletService.FreezePointWithdrawal:output_type -> hyapp.wallet.v1.FreezePointWithdrawalResponse + 45, // 305: hyapp.wallet.v1.WalletService.SettlePointWithdrawal:output_type -> hyapp.wallet.v1.SettlePointWithdrawalResponse + 47, // 306: hyapp.wallet.v1.WalletService.ReleasePointWithdrawal:output_type -> hyapp.wallet.v1.ReleasePointWithdrawalResponse + 59, // 307: hyapp.wallet.v1.WalletService.ListResources:output_type -> hyapp.wallet.v1.ListResourcesResponse + 61, // 308: hyapp.wallet.v1.WalletService.GetResource:output_type -> hyapp.wallet.v1.GetResourceResponse + 68, // 309: hyapp.wallet.v1.WalletService.CreateResource:output_type -> hyapp.wallet.v1.ResourceResponse + 68, // 310: hyapp.wallet.v1.WalletService.UpdateResource:output_type -> hyapp.wallet.v1.ResourceResponse + 68, // 311: hyapp.wallet.v1.WalletService.SetResourceStatus:output_type -> hyapp.wallet.v1.ResourceResponse + 68, // 312: hyapp.wallet.v1.WalletService.DeleteResource:output_type -> hyapp.wallet.v1.ResourceResponse + 67, // 313: hyapp.wallet.v1.WalletService.BatchDeleteResources:output_type -> hyapp.wallet.v1.BatchDeleteResourcesResponse + 70, // 314: hyapp.wallet.v1.WalletService.ListResourceGroups:output_type -> hyapp.wallet.v1.ListResourceGroupsResponse + 72, // 315: hyapp.wallet.v1.WalletService.GetResourceGroup:output_type -> hyapp.wallet.v1.GetResourceGroupResponse + 76, // 316: hyapp.wallet.v1.WalletService.CreateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 76, // 317: hyapp.wallet.v1.WalletService.UpdateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 76, // 318: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 78, // 319: hyapp.wallet.v1.WalletService.ListGiftConfigs:output_type -> hyapp.wallet.v1.ListGiftConfigsResponse + 80, // 320: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:output_type -> hyapp.wallet.v1.ListGiftTypeConfigsResponse + 274, // 321: hyapp.wallet.v1.WalletService.GetGiftCatalogVersion:output_type -> hyapp.wallet.v1.GetGiftCatalogVersionResponse + 89, // 322: hyapp.wallet.v1.WalletService.CreateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse + 85, // 323: hyapp.wallet.v1.WalletService.BatchCreateGiftConfigs:output_type -> hyapp.wallet.v1.BatchCreateGiftConfigsResponse + 89, // 324: hyapp.wallet.v1.WalletService.UpdateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse + 89, // 325: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:output_type -> hyapp.wallet.v1.GiftConfigResponse + 89, // 326: hyapp.wallet.v1.WalletService.DeleteGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse + 82, // 327: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:output_type -> hyapp.wallet.v1.GiftTypeConfigResponse + 93, // 328: hyapp.wallet.v1.WalletService.GrantResource:output_type -> hyapp.wallet.v1.ResourceGrantResponse + 93, // 329: hyapp.wallet.v1.WalletService.GrantResourceGroup:output_type -> hyapp.wallet.v1.ResourceGrantResponse + 93, // 330: hyapp.wallet.v1.WalletService.RevokeResourceGrant:output_type -> hyapp.wallet.v1.ResourceGrantResponse + 95, // 331: hyapp.wallet.v1.WalletService.ListUserResources:output_type -> hyapp.wallet.v1.ListUserResourcesResponse + 97, // 332: hyapp.wallet.v1.WalletService.EquipUserResource:output_type -> hyapp.wallet.v1.EquipUserResourceResponse + 99, // 333: hyapp.wallet.v1.WalletService.UnequipUserResource:output_type -> hyapp.wallet.v1.UnequipUserResourceResponse + 102, // 334: hyapp.wallet.v1.WalletService.BatchGetUserEquippedResources:output_type -> hyapp.wallet.v1.BatchGetUserEquippedResourcesResponse + 104, // 335: hyapp.wallet.v1.WalletService.ListResourceGrants:output_type -> hyapp.wallet.v1.ListResourceGrantsResponse + 107, // 336: hyapp.wallet.v1.WalletService.ListResourceShopItems:output_type -> hyapp.wallet.v1.ListResourceShopItemsResponse + 109, // 337: hyapp.wallet.v1.WalletService.UpsertResourceShopItems:output_type -> hyapp.wallet.v1.UpsertResourceShopItemsResponse + 111, // 338: hyapp.wallet.v1.WalletService.SetResourceShopItemStatus:output_type -> hyapp.wallet.v1.ResourceShopItemResponse + 272, // 339: hyapp.wallet.v1.WalletService.ListResourceShopPurchaseOrders:output_type -> hyapp.wallet.v1.ListResourceShopPurchaseOrdersResponse + 113, // 340: hyapp.wallet.v1.WalletService.PurchaseResourceShopItem:output_type -> hyapp.wallet.v1.PurchaseResourceShopItemResponse + 116, // 341: hyapp.wallet.v1.WalletService.ListRechargeBills:output_type -> hyapp.wallet.v1.ListRechargeBillsResponse + 119, // 342: hyapp.wallet.v1.WalletService.GetRechargeBillSummary:output_type -> hyapp.wallet.v1.GetRechargeBillSummaryResponse + 122, // 343: hyapp.wallet.v1.WalletService.BatchGetUserRechargeStats:output_type -> hyapp.wallet.v1.BatchGetUserRechargeStatsResponse + 128, // 344: hyapp.wallet.v1.WalletService.GetRechargeBillOverview:output_type -> hyapp.wallet.v1.GetRechargeBillOverviewResponse + 131, // 345: hyapp.wallet.v1.WalletService.RefreshGooglePaymentPrices:output_type -> hyapp.wallet.v1.RefreshGooglePaymentPricesResponse + 134, // 346: hyapp.wallet.v1.WalletService.GetWalletOverview:output_type -> hyapp.wallet.v1.GetWalletOverviewResponse + 137, // 347: hyapp.wallet.v1.WalletService.GetWalletValueSummary:output_type -> hyapp.wallet.v1.GetWalletValueSummaryResponse + 140, // 348: hyapp.wallet.v1.WalletService.GetUserGiftWall:output_type -> hyapp.wallet.v1.GetUserGiftWallResponse + 143, // 349: hyapp.wallet.v1.WalletService.ListRechargeProducts:output_type -> hyapp.wallet.v1.ListRechargeProductsResponse + 145, // 350: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:output_type -> hyapp.wallet.v1.ConfirmGooglePaymentResponse + 147, // 351: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:output_type -> hyapp.wallet.v1.ListAdminRechargeProductsResponse + 151, // 352: hyapp.wallet.v1.WalletService.CreateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse + 151, // 353: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse + 152, // 354: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:output_type -> hyapp.wallet.v1.DeleteRechargeProductResponse + 156, // 355: hyapp.wallet.v1.WalletService.ListThirdPartyPaymentChannels:output_type -> hyapp.wallet.v1.ListThirdPartyPaymentChannelsResponse + 158, // 356: hyapp.wallet.v1.WalletService.SetThirdPartyPaymentMethodStatus:output_type -> hyapp.wallet.v1.ThirdPartyPaymentMethodResponse + 158, // 357: hyapp.wallet.v1.WalletService.UpdateThirdPartyPaymentRate:output_type -> hyapp.wallet.v1.ThirdPartyPaymentMethodResponse + 162, // 358: hyapp.wallet.v1.WalletService.SyncThirdPartyPaymentMethods:output_type -> hyapp.wallet.v1.SyncThirdPartyPaymentMethodsResponse + 164, // 359: hyapp.wallet.v1.WalletService.ListH5RechargeOptions:output_type -> hyapp.wallet.v1.H5RechargeOptionsResponse + 173, // 360: hyapp.wallet.v1.WalletService.CreateH5RechargeOrder:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse + 173, // 361: hyapp.wallet.v1.WalletService.CreateTemporaryRechargeOrder:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse + 173, // 362: hyapp.wallet.v1.WalletService.GetTemporaryRechargeOrder:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse + 170, // 363: hyapp.wallet.v1.WalletService.ListTemporaryRechargeOrders:output_type -> hyapp.wallet.v1.ListTemporaryRechargeOrdersResponse + 173, // 364: hyapp.wallet.v1.WalletService.SubmitH5RechargeTx:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse + 173, // 365: hyapp.wallet.v1.WalletService.GetH5RechargeOrder:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse + 175, // 366: hyapp.wallet.v1.WalletService.VerifyCoinSellerRechargeReceipt:output_type -> hyapp.wallet.v1.VerifyCoinSellerRechargeReceiptResponse + 177, // 367: hyapp.wallet.v1.WalletService.HandleMifapayNotify:output_type -> hyapp.wallet.v1.HandleMifapayNotifyResponse + 179, // 368: hyapp.wallet.v1.WalletService.HandleV5PayNotify:output_type -> hyapp.wallet.v1.HandleV5PayNotifyResponse + 182, // 369: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:output_type -> hyapp.wallet.v1.GetDiamondExchangeConfigResponse + 185, // 370: hyapp.wallet.v1.WalletService.ListWalletTransactions:output_type -> hyapp.wallet.v1.ListWalletTransactionsResponse + 195, // 371: hyapp.wallet.v1.WalletService.ListVipPackages:output_type -> hyapp.wallet.v1.ListVipPackagesResponse + 207, // 372: hyapp.wallet.v1.WalletService.GetVipProgramConfig:output_type -> hyapp.wallet.v1.GetVipProgramConfigResponse + 197, // 373: hyapp.wallet.v1.WalletService.GetMyVip:output_type -> hyapp.wallet.v1.GetMyVipResponse + 229, // 374: hyapp.wallet.v1.WalletService.CheckVipBenefit:output_type -> hyapp.wallet.v1.CheckVipBenefitResponse + 199, // 375: hyapp.wallet.v1.WalletService.PurchaseVip:output_type -> hyapp.wallet.v1.PurchaseVipResponse + 201, // 376: hyapp.wallet.v1.WalletService.DebitCPBreakupFee:output_type -> hyapp.wallet.v1.DebitCPBreakupFeeResponse + 203, // 377: hyapp.wallet.v1.WalletService.DebitWheelDraw:output_type -> hyapp.wallet.v1.DebitWheelDrawResponse + 205, // 378: hyapp.wallet.v1.WalletService.GrantVip:output_type -> hyapp.wallet.v1.GrantVipResponse + 223, // 379: hyapp.wallet.v1.WalletService.GrantVipTrialCard:output_type -> hyapp.wallet.v1.GrantVipTrialCardResponse + 225, // 380: hyapp.wallet.v1.WalletService.EquipVipTrialCard:output_type -> hyapp.wallet.v1.EquipVipTrialCardResponse + 227, // 381: hyapp.wallet.v1.WalletService.UnequipVipTrialCard:output_type -> hyapp.wallet.v1.UnequipVipTrialCardResponse + 232, // 382: hyapp.wallet.v1.WalletService.ListAdminVipLevels:output_type -> hyapp.wallet.v1.ListAdminVipLevelsResponse + 234, // 383: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:output_type -> hyapp.wallet.v1.UpdateAdminVipLevelsResponse + 209, // 384: hyapp.wallet.v1.WalletService.UpdateAdminVipProgramConfig:output_type -> hyapp.wallet.v1.UpdateAdminVipProgramConfigResponse + 211, // 385: hyapp.wallet.v1.WalletService.UpdateMyVipSettings:output_type -> hyapp.wallet.v1.UpdateMyVipSettingsResponse + 217, // 386: hyapp.wallet.v1.WalletService.GetMyCurrentVipDailyCoinRebate:output_type -> hyapp.wallet.v1.GetMyCurrentVipDailyCoinRebateResponse + 219, // 387: hyapp.wallet.v1.WalletService.ListMyVipDailyCoinRebateStatuses:output_type -> hyapp.wallet.v1.ListMyVipDailyCoinRebateStatusesResponse + 221, // 388: hyapp.wallet.v1.WalletService.ClaimVipDailyCoinRebate:output_type -> hyapp.wallet.v1.ClaimVipDailyCoinRebateResponse + 236, // 389: hyapp.wallet.v1.WalletService.CreditTaskReward:output_type -> hyapp.wallet.v1.CreditTaskRewardResponse + 238, // 390: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:output_type -> hyapp.wallet.v1.CreditLuckyGiftRewardResponse + 240, // 391: hyapp.wallet.v1.WalletService.CreditWheelReward:output_type -> hyapp.wallet.v1.CreditWheelRewardResponse + 242, // 392: hyapp.wallet.v1.WalletService.CreditRoomTurnoverReward:output_type -> hyapp.wallet.v1.CreditRoomTurnoverRewardResponse + 244, // 393: hyapp.wallet.v1.WalletService.CreditInviteActivityReward:output_type -> hyapp.wallet.v1.CreditInviteActivityRewardResponse + 246, // 394: hyapp.wallet.v1.WalletService.CreditAgencyOpeningReward:output_type -> hyapp.wallet.v1.CreditAgencyOpeningRewardResponse + 248, // 395: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:output_type -> hyapp.wallet.v1.ApplyGameCoinChangeResponse + 253, // 396: hyapp.wallet.v1.WalletService.GetRedPacketConfig:output_type -> hyapp.wallet.v1.GetRedPacketConfigResponse + 255, // 397: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:output_type -> hyapp.wallet.v1.UpdateRedPacketConfigResponse + 257, // 398: hyapp.wallet.v1.WalletService.CreateRedPacket:output_type -> hyapp.wallet.v1.CreateRedPacketResponse + 259, // 399: hyapp.wallet.v1.WalletService.ClaimRedPacket:output_type -> hyapp.wallet.v1.ClaimRedPacketResponse + 261, // 400: hyapp.wallet.v1.WalletService.ListRedPackets:output_type -> hyapp.wallet.v1.ListRedPacketsResponse + 263, // 401: hyapp.wallet.v1.WalletService.GetRedPacket:output_type -> hyapp.wallet.v1.GetRedPacketResponse + 265, // 402: hyapp.wallet.v1.WalletService.ExpireRedPackets:output_type -> hyapp.wallet.v1.ExpireRedPacketsResponse + 267, // 403: hyapp.wallet.v1.WalletService.RetryRedPacketRefund:output_type -> hyapp.wallet.v1.RetryRedPacketRefundResponse + 276, // [276:404] is the sub-list for method output_type + 148, // [148:276] is the sub-list for method input_type + 148, // [148:148] is the sub-list for extension type_name + 148, // [148:148] is the sub-list for extension extendee + 0, // [0:148] is the sub-list for field type_name } func init() { file_proto_wallet_v1_wallet_proto_init() } @@ -27235,15 +32057,17 @@ func file_proto_wallet_v1_wallet_proto_init() { } file_proto_wallet_v1_wallet_proto_msgTypes[62].OneofWrappers = []any{} file_proto_wallet_v1_wallet_proto_msgTypes[63].OneofWrappers = []any{} - file_proto_wallet_v1_wallet_proto_msgTypes[145].OneofWrappers = []any{} - file_proto_wallet_v1_wallet_proto_msgTypes[146].OneofWrappers = []any{} + file_proto_wallet_v1_wallet_proto_msgTypes[148].OneofWrappers = []any{} + file_proto_wallet_v1_wallet_proto_msgTypes[149].OneofWrappers = []any{} + file_proto_wallet_v1_wallet_proto_msgTypes[210].OneofWrappers = []any{} + file_proto_wallet_v1_wallet_proto_msgTypes[222].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_wallet_v1_wallet_proto_rawDesc, NumEnums: 0, - NumMessages: 244, + NumMessages: 292, NumExtensions: 0, NumServices: 2, }, diff --git a/api/proto/wallet/v1/wallet.proto b/api/proto/wallet/v1/wallet.proto index 8043aa6e..a7721222 100644 --- a/api/proto/wallet/v1/wallet.proto +++ b/api/proto/wallet/v1/wallet.proto @@ -90,6 +90,8 @@ message DebitGiftResponse { string host_point_policy_instance_code = 25; string host_point_template_code = 26; string host_point_template_version = 27; + // balance_version 是 sender 被扣费资产在本次账务完成后的版本;同 command_id 重放返回首次版本。 + int64 balance_version = 28; } // DebitGiftTarget 是一笔批量送礼中的单个接收方账务快照。 @@ -1267,6 +1269,26 @@ message GetRechargeBillSummaryResponse { RechargeBillSummaryBucket coin_seller = 4; } +// UserRechargeStats 是 wallet-service 已维护的用户累计充值聚合,不允许后台回扫充值流水临时计算。 +message UserRechargeStats { + int64 user_id = 1; + int64 recharge_count = 2; + int64 total_coin_amount = 3; + int64 total_usd_minor_amount = 4; + int64 first_recharged_at_ms = 5; + int64 updated_at_ms = 6; +} + +message BatchGetUserRechargeStatsRequest { + string request_id = 1; + string app_code = 2; + repeated int64 user_ids = 3; +} + +message BatchGetUserRechargeStatsResponse { + repeated UserRechargeStats items = 1; +} + message GetRechargeBillOverviewRequest { string request_id = 1; string app_code = 2; @@ -1887,6 +1909,54 @@ message VipRewardItem { string animation_url = 9; } +// VipProgramConfig 是按 App 隔离的 VIP 规则开关。业务服务只能依据这些显式策略执行, +// 禁止通过 app_code 分支推断升级有效期、权益继承或后台发放语义。 +message VipProgramConfig { + string app_code = 1; + // program_type: legacy_timed/tiered_privilege_v1。 + string program_type = 2; + int32 level_count = 3; + // same_level_expiry_policy: extend_remaining;同级购买从当前有效截止时间续期。 + string same_level_expiry_policy = 4; + // upgrade_expiry_policy: extend_remaining/replace_from_now。 + string upgrade_expiry_policy = 5; + // downgrade_purchase_policy 首版固定 reject,保留字段用于后台完整展示当前规则。 + string downgrade_purchase_policy = 6; + // benefit_inheritance_policy 当前固定 target_only;每级提交最终完整集合,不做自动继承。 + string benefit_inheritance_policy = 7; + // grant_mode: direct_membership/trial_card,决定后台“赠送 VIP”的真实账务对象。 + string grant_mode = 8; + bool trial_card_enabled = 9; + string status = 10; + int64 config_version = 11; + int64 updated_by_admin_id = 12; + int64 created_at_ms = 13; + int64 updated_at_ms = 14; +} + +// VipBenefit 是服务端可执行的权益事实。resource_id 为 0 表示纯功能特权,非 0 时 +// 指向钱包资源;trial_enabled=false 的权益不会进入体验卡产生的 effective_benefits。 +message VipBenefit { + string benefit_code = 1; + string name = 2; + // benefit_type: decoration/function。 + string benefit_type = 3; + // unlock_level 只作为 P1 默认矩阵的来源信息;后台仍按每个等级提交完整权益集合, + // 运行时不得据此自动补齐高等级权益。 + int32 unlock_level = 4; + string status = 5; + bool trial_enabled = 6; + int64 resource_id = 7; + string resource_type = 8; + // execution_scope 标识 wallet/room/user/notice 等实际权限执行方,不代表服务间直连。 + string execution_scope = 9; + bool auto_equip = 10; + int32 sort_order = 11; + string metadata_json = 12; + int64 created_at_ms = 13; + int64 updated_at_ms = 14; +} + message VipLevel { int32 level = 1; string name = 2; @@ -1903,6 +1973,9 @@ message VipLevel { string purchase_locked_reason = 13; int64 created_at_ms = 14; int64 updated_at_ms = 15; + // benefits 是后台为该等级显式保存的最终权益集合,运行时不按 unlock_level 自动继承。 + repeated VipBenefit benefits = 16; + int64 config_version = 17; } message UserVip { @@ -1913,6 +1986,54 @@ message UserVip { int64 started_at_ms = 5; int64 expires_at_ms = 6; int64 updated_at_ms = 7; + string program_type = 8; + int64 config_version = 9; +} + +// VipTrialCard 是体验卡背包权益的 VIP 投影。entitlement_id 复用通用背包佩戴事实, +// 切换卡片只改变装备记录,不修改任何卡片自身的 expires_at_ms。 +message VipTrialCard { + string trial_card_id = 1; + string entitlement_id = 2; + int64 resource_id = 3; + int64 user_id = 4; + int32 level = 5; + string name = 6; + string status = 7; + bool equipped = 8; + int64 duration_ms = 9; + int64 effective_at_ms = 10; + int64 expires_at_ms = 11; + int64 remaining_duration_ms = 12; + string grant_source = 13; + string source_grant_id = 14; + int64 created_at_ms = 15; + int64 updated_at_ms = 16; +} + +// VipUserSettings 是用户按 App 隔离的可关闭 VIP 功能偏好。没有持久记录时 wallet +// 仍返回两个默认开启值,updated_at_ms=0;设置本身不授予任何 VIP 权益。 +message VipUserSettings { + string app_code = 1; + int64 user_id = 2; + bool room_entry_notice_enabled = 3; + bool online_global_notice_enabled = 4; + int64 updated_at_ms = 5; +} + +// VipState 同时返回付费会员、当前佩戴体验卡和最终生效会员,避免调用方自行合并两层状态。 +// effective_benefits 是钱包按 paid/trial 来源、该等级显式矩阵和 trial_enabled 过滤后的资格事实; +// 对可关闭通知,执行方还必须结合 user_settings 或调用 CheckVipBenefit。 +message VipState { + UserVip paid_vip = 1; + VipTrialCard equipped_trial_card = 2; + UserVip effective_vip = 3; + // effective_source: paid/trial/none。 + string effective_source = 4; + repeated VipBenefit effective_benefits = 5; + int64 evaluated_at_ms = 6; + VipProgramConfig program_config = 7; + VipUserSettings user_settings = 8; } message ListVipPackagesRequest { @@ -1924,6 +2045,8 @@ message ListVipPackagesRequest { message ListVipPackagesResponse { UserVip current_vip = 1; repeated VipLevel packages = 2; + VipState state = 3; + VipProgramConfig program_config = 4; } message GetMyVipRequest { @@ -1934,6 +2057,7 @@ message GetMyVipRequest { message GetMyVipResponse { UserVip vip = 1; + VipState state = 2; } message PurchaseVipRequest { @@ -1950,6 +2074,7 @@ message PurchaseVipResponse { int64 coin_spent = 4; int64 coin_balance_after = 5; repeated VipRewardItem reward_items = 6; + VipState state = 7; } message DebitCPBreakupFeeRequest { @@ -1999,6 +2124,205 @@ message GrantVipResponse { UserVip vip = 2; repeated VipRewardItem reward_items = 3; int64 server_time_ms = 4; + VipState state = 5; +} + +message GetVipProgramConfigRequest { + string request_id = 1; + string app_code = 2; +} + +message GetVipProgramConfigResponse { + VipProgramConfig config = 1; + repeated VipBenefit benefits = 2; + int64 server_time_ms = 3; +} + +// UpdateAdminVipProgramConfigRequest 以完整配置替换单 App 当前规则;config_version 由服务端递增, +// 客户端提交的审计时间和操作者字段不会被信任。 +message UpdateAdminVipProgramConfigRequest { + string request_id = 1; + string app_code = 2; + VipProgramConfig config = 3; + int64 operator_user_id = 4; +} + +message UpdateAdminVipProgramConfigResponse { + VipProgramConfig config = 1; + int64 server_time_ms = 2; +} + +// UpdateMyVipSettingsRequest 使用 optional 保留 PATCH 语义;显式 false 不能被当成未提交。 +message UpdateMyVipSettingsRequest { + string request_id = 1; + string app_code = 2; + int64 user_id = 3; + optional bool room_entry_notice_enabled = 4; + optional bool online_global_notice_enabled = 5; +} + +message UpdateMyVipSettingsResponse { + VipUserSettings settings = 1; + int64 server_time_ms = 2; +} + +// VipDailyCoinRebateRun 固化某个 UTC 自然日的返现金额矩阵。批任务开始后即使后台 +// 修改等级权益,后续分页也只能复用本 run 的 config_version/level_amounts_json。 +message VipDailyCoinRebateRun { + string run_id = 1; + string task_day = 2; + int64 day_start_ms = 3; + int64 day_end_ms = 4; + int64 config_version = 5; + string level_amounts_json = 6; + string status = 7; + int64 last_user_id = 8; + int64 scanned_count = 9; + int64 created_count = 10; + int64 created_at_ms = 11; + int64 updated_at_ms = 12; + int64 completed_at_ms = 13; +} + +// VipDailyCoinRebate 是付费 VIP 在 UTC 日切时生成的可领取金币事实。体验卡不会生成; +// status 返回 claimable/claimed/expired,expired 可由查询时钟投影而不改写历史行。 +message VipDailyCoinRebate { + string rebate_id = 1; + int64 user_id = 2; + string task_day = 3; + int32 vip_level = 4; + string vip_name = 5; + int64 coin_amount = 6; + string status = 7; + int64 available_at_ms = 8; + int64 expires_at_ms = 9; + int64 config_version = 10; + int64 claimed_at_ms = 11; + string wallet_transaction_id = 12; + int64 created_at_ms = 13; + int64 updated_at_ms = 14; +} + +// ProcessVipDailyCoinRebateBatchRequest 由 WalletCronService 接收。task_day 为空时按 +// wallet-service 当前 UTC 日期处理;同 App+task_day 始终创建或复用同一个 run。 +message ProcessVipDailyCoinRebateBatchRequest { + string request_id = 1; + string app_code = 2; + string task_day = 3; + int32 batch_size = 4; +} + +message ProcessVipDailyCoinRebateBatchResponse { + VipDailyCoinRebateRun run = 1; + int32 scanned_count = 2; + int32 created_count = 3; + bool has_more = 4; + int64 server_time_ms = 5; +} + +message GetMyCurrentVipDailyCoinRebateRequest { + string request_id = 1; + string app_code = 2; + int64 user_id = 3; +} + +message GetMyCurrentVipDailyCoinRebateResponse { + bool found = 1; + VipDailyCoinRebate rebate = 2; + int64 server_time_ms = 3; +} + +message ListMyVipDailyCoinRebateStatusesRequest { + string request_id = 1; + string app_code = 2; + int64 user_id = 3; + int32 page = 4; + int32 page_size = 5; + // rebate_ids 非空时精确恢复指定通知的领取状态,最多 100 个;仍强制按 user_id 隔离。 + repeated string rebate_ids = 6; +} + +message ListMyVipDailyCoinRebateStatusesResponse { + repeated VipDailyCoinRebate rebates = 1; + int64 total = 2; + int64 server_time_ms = 3; +} + +message ClaimVipDailyCoinRebateRequest { + string command_id = 1; + string app_code = 2; + int64 user_id = 3; + string rebate_id = 4; +} + +message ClaimVipDailyCoinRebateResponse { + VipDailyCoinRebate rebate = 1; + string transaction_id = 2; + AssetBalance coin_balance = 3; + int64 server_time_ms = 4; +} + +// GrantVipTrialCardRequest 允许后台直接按 level+duration_ms 发卡;resource_id 为空时按 +// vip_trial_card_{level} 解析同 App 预置资源,非空时也必须匹配该等级。 +message GrantVipTrialCardRequest { + string command_id = 1; + string app_code = 2; + int64 target_user_id = 3; + int32 level = 4; + int64 duration_ms = 5; + optional int64 resource_id = 6; + string grant_source = 7; + int64 operator_user_id = 8; + string reason = 9; +} + +message GrantVipTrialCardResponse { + VipTrialCard trial_card = 1; + VipState state = 2; + int64 server_time_ms = 3; +} + +message EquipVipTrialCardRequest { + string request_id = 1; + string app_code = 2; + int64 user_id = 3; + // entitlement_id 是背包实例标识,保证同资源的多张卡也能被精确佩戴。 + string entitlement_id = 4; +} + +message EquipVipTrialCardResponse { + VipTrialCard trial_card = 1; + VipState state = 2; + int64 server_time_ms = 3; +} + +message UnequipVipTrialCardRequest { + string request_id = 1; + string app_code = 2; + int64 user_id = 3; +} + +message UnequipVipTrialCardResponse { + bool unequipped = 1; + VipState state = 2; + int64 server_time_ms = 3; +} + +// CheckVipBenefit 供 room/user 等权益执行方校验单个特权。调用方不能只比较 VIP level, +// 因为体验卡排除项和 App 配置会改变最终可执行权益集合。 +message CheckVipBenefitRequest { + string request_id = 1; + string app_code = 2; + int64 user_id = 3; + string benefit_code = 4; +} + +message CheckVipBenefitResponse { + bool allowed = 1; + VipBenefit benefit = 2; + VipState state = 3; + string denial_reason = 4; + int64 evaluated_at_ms = 5; } message AdminVipLevelInput { @@ -2010,6 +2334,8 @@ message AdminVipLevelInput { int64 reward_resource_group_id = 6; int32 sort_order = 7; int64 required_recharge_coin_amount = 8; + // benefits 是该等级完整、显式的权益集合;服务端不根据 unlock_level 自动继承。 + repeated VipBenefit benefits = 9; } message ListAdminVipLevelsRequest { @@ -2020,6 +2346,7 @@ message ListAdminVipLevelsRequest { message ListAdminVipLevelsResponse { repeated VipLevel levels = 1; int64 server_time_ms = 2; + VipProgramConfig program_config = 3; } message UpdateAdminVipLevelsRequest { @@ -2032,6 +2359,7 @@ message UpdateAdminVipLevelsRequest { message UpdateAdminVipLevelsResponse { repeated VipLevel levels = 1; int64 server_time_ms = 2; + VipProgramConfig program_config = 3; } // CreditTaskRewardRequest 是 activity-service 领取任务奖励时调用的钱包入账命令。 @@ -2435,11 +2763,128 @@ message GetGiftCatalogVersionResponse { int64 version = 1; } +// HostRevenueStats 聚合主播在自然日区间内的收益 POINT 和送礼用户;gateway 对外仍按产品文案展示为钻石。 +message HostRevenueStats { + int64 diamond_earnings = 1; + int64 diamond_exchanged = 2; + int64 gift_senders = 3; +} + +message GetHostRevenueStatsRequest { + string request_id = 1; + string app_code = 2; + int64 host_user_id = 3; + int64 start_at_ms = 4; + int64 end_at_ms = 5; +} + +message GetHostRevenueStatsResponse { + HostRevenueStats stats = 1; +} + +// AgencyPointShareStats 只统计独立 Agency 分成流水,不把 owner 自己的主播礼物收益重复算入分成。 +message AgencyPointShareStats { + int64 share_income = 1; + int64 gifted_host_count = 2; +} + +message GetAgencyPointShareStatsRequest { + string request_id = 1; + string app_code = 2; + int64 agency_owner_user_id = 3; + int64 start_at_ms = 4; + int64 end_at_ms = 5; +} + +message GetAgencyPointShareStatsResponse { + AgencyPointShareStats stats = 1; +} + +message AgencyHostGiftStats { + int64 gift_income = 1; + int64 gifted_host_count = 2; +} + +message GetAgencyHostGiftStatsRequest { + string request_id = 1; + string app_code = 2; + repeated int64 host_user_ids = 3; + int64 start_at_ms = 4; + int64 end_at_ms = 5; +} + +message GetAgencyHostGiftStatsResponse { + AgencyHostGiftStats stats = 1; +} + +// PointWithdrawalCoinSellerConfig 是按 App 隔离的 POINT 提现币商白名单与兑换比例。 +// 比例用整数分子/分母保存,避免浮点金额在 H5、gateway 和账本间产生舍入分歧。 +message PointWithdrawalCoinSellerConfig { + string app_code = 1; + int64 seller_user_id = 2; + int32 sort_order = 3; + int64 point_amount = 4; + int64 seller_coin_amount = 5; + repeated string service_country_codes = 6; + string status = 7; + int64 created_at_ms = 8; + int64 updated_at_ms = 9; +} + +message ListPointWithdrawalCoinSellersRequest { + string request_id = 1; + string app_code = 2; + // country_code 由 gateway 从登录用户资料注入;为空只用于 Admin/内部查询。 + string country_code = 3; + bool include_disabled = 4; +} + +message ListPointWithdrawalCoinSellersResponse { + repeated PointWithdrawalCoinSellerConfig sellers = 1; +} + +message TransferPointToCoinSellerRequest { + string command_id = 1; + string app_code = 2; + int64 source_user_id = 3; + int64 seller_user_id = 4; + int64 point_amount = 5; + // source_country_code 由 gateway 从用户资料读取,wallet 会再次校验币商服务国家。 + string source_country_code = 6; + string reason = 7; +} + +message TransferPointToCoinSellerResponse { + string transaction_id = 1; + int64 source_point_balance_after = 2; + int64 seller_balance_after = 3; + int64 point_amount = 4; + int64 seller_coin_amount = 5; + int64 ratio_point_amount = 6; + int64 ratio_seller_coin_amount = 7; +} + +message GetPointWithdrawalConfigRequest { + string request_id = 1; + string app_code = 2; + int64 region_id = 3; + int64 now_ms = 4; +} + +message GetPointWithdrawalConfigResponse { + bool found = 1; + int64 points_per_usd = 2; + int32 fee_bps = 3; + int64 minimum_points = 4; + string policy_instance_code = 5; +} + // WalletCronService 只给 cron-service 调用;账务状态仍由 wallet-service owner 修改。 service WalletCronService { rpc ProcessHostSalaryDailySettlementBatch(CronBatchRequest) returns (CronBatchResponse); rpc ProcessHostSalaryHalfMonthSettlementBatch(CronBatchRequest) returns (CronBatchResponse); rpc ProcessHostSalaryMonthEndBatch(CronBatchRequest) returns (CronBatchResponse); + rpc ProcessVipDailyCoinRebateBatch(ProcessVipDailyCoinRebateBatchRequest) returns (ProcessVipDailyCoinRebateBatchResponse); } // WalletService 是内部账务 gRPC 边界,外部 HTTP 入口仍由 gateway-service 承载。 @@ -2451,6 +2896,9 @@ service WalletService { rpc GetBalances(GetBalancesRequest) returns (GetBalancesResponse); rpc GetActiveHostSalaryPolicy(GetActiveHostSalaryPolicyRequest) returns (GetActiveHostSalaryPolicyResponse); rpc GetHostSalaryProgress(GetHostSalaryProgressRequest) returns (GetHostSalaryProgressResponse); + rpc GetHostRevenueStats(GetHostRevenueStatsRequest) returns (GetHostRevenueStatsResponse); + rpc GetAgencyPointShareStats(GetAgencyPointShareStatsRequest) returns (GetAgencyPointShareStatsResponse); + rpc GetAgencyHostGiftStats(GetAgencyHostGiftStatsRequest) returns (GetAgencyHostGiftStatsResponse); // GetTeamHostSalaryStats 按 Agency 收款人集合聚合主播预收入工资;经理中心团队工资卡片使用。 rpc GetTeamHostSalaryStats(GetTeamHostSalaryStatsRequest) returns (GetTeamHostSalaryStatsResponse); rpc AdminCreditAsset(AdminCreditAssetRequest) returns (AdminCreditAssetResponse); @@ -2460,6 +2908,9 @@ service WalletService { rpc ListCoinSellerSalaryExchangeRateTiers(ListCoinSellerSalaryExchangeRateTiersRequest) returns (ListCoinSellerSalaryExchangeRateTiersResponse); rpc ExchangeSalaryToCoin(ExchangeSalaryToCoinRequest) returns (ExchangeSalaryToCoinResponse); rpc TransferSalaryToCoinSeller(TransferSalaryToCoinSellerRequest) returns (TransferSalaryToCoinSellerResponse); + rpc ListPointWithdrawalCoinSellers(ListPointWithdrawalCoinSellersRequest) returns (ListPointWithdrawalCoinSellersResponse); + rpc TransferPointToCoinSeller(TransferPointToCoinSellerRequest) returns (TransferPointToCoinSellerResponse); + rpc GetPointWithdrawalConfig(GetPointWithdrawalConfigRequest) returns (GetPointWithdrawalConfigResponse); rpc FreezeSalaryWithdrawal(FreezeSalaryWithdrawalRequest) returns (FreezeSalaryWithdrawalResponse); rpc SettleSalaryWithdrawal(SettleSalaryWithdrawalRequest) returns (SettleSalaryWithdrawalResponse); rpc ReleaseSalaryWithdrawal(ReleaseSalaryWithdrawalRequest) returns (ReleaseSalaryWithdrawalResponse); @@ -2502,6 +2953,7 @@ service WalletService { rpc PurchaseResourceShopItem(PurchaseResourceShopItemRequest) returns (PurchaseResourceShopItemResponse); rpc ListRechargeBills(ListRechargeBillsRequest) returns (ListRechargeBillsResponse); rpc GetRechargeBillSummary(GetRechargeBillSummaryRequest) returns (GetRechargeBillSummaryResponse); + rpc BatchGetUserRechargeStats(BatchGetUserRechargeStatsRequest) returns (BatchGetUserRechargeStatsResponse); rpc GetRechargeBillOverview(GetRechargeBillOverviewRequest) returns (GetRechargeBillOverviewResponse); rpc RefreshGooglePaymentPrices(RefreshGooglePaymentPricesRequest) returns (RefreshGooglePaymentPricesResponse); rpc GetWalletOverview(GetWalletOverviewRequest) returns (GetWalletOverviewResponse); @@ -2530,13 +2982,23 @@ service WalletService { rpc GetDiamondExchangeConfig(GetDiamondExchangeConfigRequest) returns (GetDiamondExchangeConfigResponse); rpc ListWalletTransactions(ListWalletTransactionsRequest) returns (ListWalletTransactionsResponse); rpc ListVipPackages(ListVipPackagesRequest) returns (ListVipPackagesResponse); + rpc GetVipProgramConfig(GetVipProgramConfigRequest) returns (GetVipProgramConfigResponse); rpc GetMyVip(GetMyVipRequest) returns (GetMyVipResponse); + rpc CheckVipBenefit(CheckVipBenefitRequest) returns (CheckVipBenefitResponse); rpc PurchaseVip(PurchaseVipRequest) returns (PurchaseVipResponse); rpc DebitCPBreakupFee(DebitCPBreakupFeeRequest) returns (DebitCPBreakupFeeResponse); rpc DebitWheelDraw(DebitWheelDrawRequest) returns (DebitWheelDrawResponse); rpc GrantVip(GrantVipRequest) returns (GrantVipResponse); + rpc GrantVipTrialCard(GrantVipTrialCardRequest) returns (GrantVipTrialCardResponse); + rpc EquipVipTrialCard(EquipVipTrialCardRequest) returns (EquipVipTrialCardResponse); + rpc UnequipVipTrialCard(UnequipVipTrialCardRequest) returns (UnequipVipTrialCardResponse); rpc ListAdminVipLevels(ListAdminVipLevelsRequest) returns (ListAdminVipLevelsResponse); rpc UpdateAdminVipLevels(UpdateAdminVipLevelsRequest) returns (UpdateAdminVipLevelsResponse); + rpc UpdateAdminVipProgramConfig(UpdateAdminVipProgramConfigRequest) returns (UpdateAdminVipProgramConfigResponse); + rpc UpdateMyVipSettings(UpdateMyVipSettingsRequest) returns (UpdateMyVipSettingsResponse); + rpc GetMyCurrentVipDailyCoinRebate(GetMyCurrentVipDailyCoinRebateRequest) returns (GetMyCurrentVipDailyCoinRebateResponse); + rpc ListMyVipDailyCoinRebateStatuses(ListMyVipDailyCoinRebateStatusesRequest) returns (ListMyVipDailyCoinRebateStatusesResponse); + rpc ClaimVipDailyCoinRebate(ClaimVipDailyCoinRebateRequest) returns (ClaimVipDailyCoinRebateResponse); rpc CreditTaskReward(CreditTaskRewardRequest) returns (CreditTaskRewardResponse); rpc CreditLuckyGiftReward(CreditLuckyGiftRewardRequest) returns (CreditLuckyGiftRewardResponse); rpc CreditWheelReward(CreditWheelRewardRequest) returns (CreditWheelRewardResponse); diff --git a/api/proto/wallet/v1/wallet_grpc.pb.go b/api/proto/wallet/v1/wallet_grpc.pb.go index 47c93648..a7653246 100644 --- a/api/proto/wallet/v1/wallet_grpc.pb.go +++ b/api/proto/wallet/v1/wallet_grpc.pb.go @@ -22,6 +22,7 @@ const ( WalletCronService_ProcessHostSalaryDailySettlementBatch_FullMethodName = "/hyapp.wallet.v1.WalletCronService/ProcessHostSalaryDailySettlementBatch" WalletCronService_ProcessHostSalaryHalfMonthSettlementBatch_FullMethodName = "/hyapp.wallet.v1.WalletCronService/ProcessHostSalaryHalfMonthSettlementBatch" WalletCronService_ProcessHostSalaryMonthEndBatch_FullMethodName = "/hyapp.wallet.v1.WalletCronService/ProcessHostSalaryMonthEndBatch" + WalletCronService_ProcessVipDailyCoinRebateBatch_FullMethodName = "/hyapp.wallet.v1.WalletCronService/ProcessVipDailyCoinRebateBatch" ) // WalletCronServiceClient is the client API for WalletCronService service. @@ -33,6 +34,7 @@ type WalletCronServiceClient interface { ProcessHostSalaryDailySettlementBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) ProcessHostSalaryHalfMonthSettlementBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) ProcessHostSalaryMonthEndBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) + ProcessVipDailyCoinRebateBatch(ctx context.Context, in *ProcessVipDailyCoinRebateBatchRequest, opts ...grpc.CallOption) (*ProcessVipDailyCoinRebateBatchResponse, error) } type walletCronServiceClient struct { @@ -73,6 +75,16 @@ func (c *walletCronServiceClient) ProcessHostSalaryMonthEndBatch(ctx context.Con return out, nil } +func (c *walletCronServiceClient) ProcessVipDailyCoinRebateBatch(ctx context.Context, in *ProcessVipDailyCoinRebateBatchRequest, opts ...grpc.CallOption) (*ProcessVipDailyCoinRebateBatchResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ProcessVipDailyCoinRebateBatchResponse) + err := c.cc.Invoke(ctx, WalletCronService_ProcessVipDailyCoinRebateBatch_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // WalletCronServiceServer is the server API for WalletCronService service. // All implementations must embed UnimplementedWalletCronServiceServer // for forward compatibility. @@ -82,6 +94,7 @@ type WalletCronServiceServer interface { ProcessHostSalaryDailySettlementBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) ProcessHostSalaryHalfMonthSettlementBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) ProcessHostSalaryMonthEndBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) + ProcessVipDailyCoinRebateBatch(context.Context, *ProcessVipDailyCoinRebateBatchRequest) (*ProcessVipDailyCoinRebateBatchResponse, error) mustEmbedUnimplementedWalletCronServiceServer() } @@ -101,6 +114,9 @@ func (UnimplementedWalletCronServiceServer) ProcessHostSalaryHalfMonthSettlement func (UnimplementedWalletCronServiceServer) ProcessHostSalaryMonthEndBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ProcessHostSalaryMonthEndBatch not implemented") } +func (UnimplementedWalletCronServiceServer) ProcessVipDailyCoinRebateBatch(context.Context, *ProcessVipDailyCoinRebateBatchRequest) (*ProcessVipDailyCoinRebateBatchResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ProcessVipDailyCoinRebateBatch not implemented") +} func (UnimplementedWalletCronServiceServer) mustEmbedUnimplementedWalletCronServiceServer() {} func (UnimplementedWalletCronServiceServer) testEmbeddedByValue() {} @@ -176,6 +192,24 @@ func _WalletCronService_ProcessHostSalaryMonthEndBatch_Handler(srv interface{}, return interceptor(ctx, in, info, handler) } +func _WalletCronService_ProcessVipDailyCoinRebateBatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ProcessVipDailyCoinRebateBatchRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletCronServiceServer).ProcessVipDailyCoinRebateBatch(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletCronService_ProcessVipDailyCoinRebateBatch_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletCronServiceServer).ProcessVipDailyCoinRebateBatch(ctx, req.(*ProcessVipDailyCoinRebateBatchRequest)) + } + return interceptor(ctx, in, info, handler) +} + // WalletCronService_ServiceDesc is the grpc.ServiceDesc for WalletCronService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -195,6 +229,10 @@ var WalletCronService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ProcessHostSalaryMonthEndBatch", Handler: _WalletCronService_ProcessHostSalaryMonthEndBatch_Handler, }, + { + MethodName: "ProcessVipDailyCoinRebateBatch", + Handler: _WalletCronService_ProcessVipDailyCoinRebateBatch_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/wallet/v1/wallet.proto", @@ -208,6 +246,9 @@ const ( WalletService_GetBalances_FullMethodName = "/hyapp.wallet.v1.WalletService/GetBalances" WalletService_GetActiveHostSalaryPolicy_FullMethodName = "/hyapp.wallet.v1.WalletService/GetActiveHostSalaryPolicy" WalletService_GetHostSalaryProgress_FullMethodName = "/hyapp.wallet.v1.WalletService/GetHostSalaryProgress" + WalletService_GetHostRevenueStats_FullMethodName = "/hyapp.wallet.v1.WalletService/GetHostRevenueStats" + WalletService_GetAgencyPointShareStats_FullMethodName = "/hyapp.wallet.v1.WalletService/GetAgencyPointShareStats" + WalletService_GetAgencyHostGiftStats_FullMethodName = "/hyapp.wallet.v1.WalletService/GetAgencyHostGiftStats" WalletService_GetTeamHostSalaryStats_FullMethodName = "/hyapp.wallet.v1.WalletService/GetTeamHostSalaryStats" WalletService_AdminCreditAsset_FullMethodName = "/hyapp.wallet.v1.WalletService/AdminCreditAsset" WalletService_AdminCreditCoinSellerStock_FullMethodName = "/hyapp.wallet.v1.WalletService/AdminCreditCoinSellerStock" @@ -216,6 +257,9 @@ const ( WalletService_ListCoinSellerSalaryExchangeRateTiers_FullMethodName = "/hyapp.wallet.v1.WalletService/ListCoinSellerSalaryExchangeRateTiers" WalletService_ExchangeSalaryToCoin_FullMethodName = "/hyapp.wallet.v1.WalletService/ExchangeSalaryToCoin" WalletService_TransferSalaryToCoinSeller_FullMethodName = "/hyapp.wallet.v1.WalletService/TransferSalaryToCoinSeller" + WalletService_ListPointWithdrawalCoinSellers_FullMethodName = "/hyapp.wallet.v1.WalletService/ListPointWithdrawalCoinSellers" + WalletService_TransferPointToCoinSeller_FullMethodName = "/hyapp.wallet.v1.WalletService/TransferPointToCoinSeller" + WalletService_GetPointWithdrawalConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/GetPointWithdrawalConfig" WalletService_FreezeSalaryWithdrawal_FullMethodName = "/hyapp.wallet.v1.WalletService/FreezeSalaryWithdrawal" WalletService_SettleSalaryWithdrawal_FullMethodName = "/hyapp.wallet.v1.WalletService/SettleSalaryWithdrawal" WalletService_ReleaseSalaryWithdrawal_FullMethodName = "/hyapp.wallet.v1.WalletService/ReleaseSalaryWithdrawal" @@ -258,6 +302,7 @@ const ( WalletService_PurchaseResourceShopItem_FullMethodName = "/hyapp.wallet.v1.WalletService/PurchaseResourceShopItem" WalletService_ListRechargeBills_FullMethodName = "/hyapp.wallet.v1.WalletService/ListRechargeBills" WalletService_GetRechargeBillSummary_FullMethodName = "/hyapp.wallet.v1.WalletService/GetRechargeBillSummary" + WalletService_BatchGetUserRechargeStats_FullMethodName = "/hyapp.wallet.v1.WalletService/BatchGetUserRechargeStats" WalletService_GetRechargeBillOverview_FullMethodName = "/hyapp.wallet.v1.WalletService/GetRechargeBillOverview" WalletService_RefreshGooglePaymentPrices_FullMethodName = "/hyapp.wallet.v1.WalletService/RefreshGooglePaymentPrices" WalletService_GetWalletOverview_FullMethodName = "/hyapp.wallet.v1.WalletService/GetWalletOverview" @@ -286,13 +331,23 @@ const ( WalletService_GetDiamondExchangeConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/GetDiamondExchangeConfig" WalletService_ListWalletTransactions_FullMethodName = "/hyapp.wallet.v1.WalletService/ListWalletTransactions" WalletService_ListVipPackages_FullMethodName = "/hyapp.wallet.v1.WalletService/ListVipPackages" + WalletService_GetVipProgramConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/GetVipProgramConfig" WalletService_GetMyVip_FullMethodName = "/hyapp.wallet.v1.WalletService/GetMyVip" + WalletService_CheckVipBenefit_FullMethodName = "/hyapp.wallet.v1.WalletService/CheckVipBenefit" WalletService_PurchaseVip_FullMethodName = "/hyapp.wallet.v1.WalletService/PurchaseVip" WalletService_DebitCPBreakupFee_FullMethodName = "/hyapp.wallet.v1.WalletService/DebitCPBreakupFee" WalletService_DebitWheelDraw_FullMethodName = "/hyapp.wallet.v1.WalletService/DebitWheelDraw" WalletService_GrantVip_FullMethodName = "/hyapp.wallet.v1.WalletService/GrantVip" + WalletService_GrantVipTrialCard_FullMethodName = "/hyapp.wallet.v1.WalletService/GrantVipTrialCard" + WalletService_EquipVipTrialCard_FullMethodName = "/hyapp.wallet.v1.WalletService/EquipVipTrialCard" + WalletService_UnequipVipTrialCard_FullMethodName = "/hyapp.wallet.v1.WalletService/UnequipVipTrialCard" WalletService_ListAdminVipLevels_FullMethodName = "/hyapp.wallet.v1.WalletService/ListAdminVipLevels" WalletService_UpdateAdminVipLevels_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateAdminVipLevels" + WalletService_UpdateAdminVipProgramConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateAdminVipProgramConfig" + WalletService_UpdateMyVipSettings_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateMyVipSettings" + WalletService_GetMyCurrentVipDailyCoinRebate_FullMethodName = "/hyapp.wallet.v1.WalletService/GetMyCurrentVipDailyCoinRebate" + WalletService_ListMyVipDailyCoinRebateStatuses_FullMethodName = "/hyapp.wallet.v1.WalletService/ListMyVipDailyCoinRebateStatuses" + WalletService_ClaimVipDailyCoinRebate_FullMethodName = "/hyapp.wallet.v1.WalletService/ClaimVipDailyCoinRebate" WalletService_CreditTaskReward_FullMethodName = "/hyapp.wallet.v1.WalletService/CreditTaskReward" WalletService_CreditLuckyGiftReward_FullMethodName = "/hyapp.wallet.v1.WalletService/CreditLuckyGiftReward" WalletService_CreditWheelReward_FullMethodName = "/hyapp.wallet.v1.WalletService/CreditWheelReward" @@ -323,6 +378,9 @@ type WalletServiceClient interface { GetBalances(ctx context.Context, in *GetBalancesRequest, opts ...grpc.CallOption) (*GetBalancesResponse, error) GetActiveHostSalaryPolicy(ctx context.Context, in *GetActiveHostSalaryPolicyRequest, opts ...grpc.CallOption) (*GetActiveHostSalaryPolicyResponse, error) GetHostSalaryProgress(ctx context.Context, in *GetHostSalaryProgressRequest, opts ...grpc.CallOption) (*GetHostSalaryProgressResponse, error) + GetHostRevenueStats(ctx context.Context, in *GetHostRevenueStatsRequest, opts ...grpc.CallOption) (*GetHostRevenueStatsResponse, error) + GetAgencyPointShareStats(ctx context.Context, in *GetAgencyPointShareStatsRequest, opts ...grpc.CallOption) (*GetAgencyPointShareStatsResponse, error) + GetAgencyHostGiftStats(ctx context.Context, in *GetAgencyHostGiftStatsRequest, opts ...grpc.CallOption) (*GetAgencyHostGiftStatsResponse, error) // GetTeamHostSalaryStats 按 Agency 收款人集合聚合主播预收入工资;经理中心团队工资卡片使用。 GetTeamHostSalaryStats(ctx context.Context, in *GetTeamHostSalaryStatsRequest, opts ...grpc.CallOption) (*GetTeamHostSalaryStatsResponse, error) AdminCreditAsset(ctx context.Context, in *AdminCreditAssetRequest, opts ...grpc.CallOption) (*AdminCreditAssetResponse, error) @@ -332,6 +390,9 @@ type WalletServiceClient interface { ListCoinSellerSalaryExchangeRateTiers(ctx context.Context, in *ListCoinSellerSalaryExchangeRateTiersRequest, opts ...grpc.CallOption) (*ListCoinSellerSalaryExchangeRateTiersResponse, error) ExchangeSalaryToCoin(ctx context.Context, in *ExchangeSalaryToCoinRequest, opts ...grpc.CallOption) (*ExchangeSalaryToCoinResponse, error) TransferSalaryToCoinSeller(ctx context.Context, in *TransferSalaryToCoinSellerRequest, opts ...grpc.CallOption) (*TransferSalaryToCoinSellerResponse, error) + ListPointWithdrawalCoinSellers(ctx context.Context, in *ListPointWithdrawalCoinSellersRequest, opts ...grpc.CallOption) (*ListPointWithdrawalCoinSellersResponse, error) + TransferPointToCoinSeller(ctx context.Context, in *TransferPointToCoinSellerRequest, opts ...grpc.CallOption) (*TransferPointToCoinSellerResponse, error) + GetPointWithdrawalConfig(ctx context.Context, in *GetPointWithdrawalConfigRequest, opts ...grpc.CallOption) (*GetPointWithdrawalConfigResponse, error) FreezeSalaryWithdrawal(ctx context.Context, in *FreezeSalaryWithdrawalRequest, opts ...grpc.CallOption) (*FreezeSalaryWithdrawalResponse, error) SettleSalaryWithdrawal(ctx context.Context, in *SettleSalaryWithdrawalRequest, opts ...grpc.CallOption) (*SettleSalaryWithdrawalResponse, error) ReleaseSalaryWithdrawal(ctx context.Context, in *ReleaseSalaryWithdrawalRequest, opts ...grpc.CallOption) (*ReleaseSalaryWithdrawalResponse, error) @@ -374,6 +435,7 @@ type WalletServiceClient interface { PurchaseResourceShopItem(ctx context.Context, in *PurchaseResourceShopItemRequest, opts ...grpc.CallOption) (*PurchaseResourceShopItemResponse, error) ListRechargeBills(ctx context.Context, in *ListRechargeBillsRequest, opts ...grpc.CallOption) (*ListRechargeBillsResponse, error) GetRechargeBillSummary(ctx context.Context, in *GetRechargeBillSummaryRequest, opts ...grpc.CallOption) (*GetRechargeBillSummaryResponse, error) + BatchGetUserRechargeStats(ctx context.Context, in *BatchGetUserRechargeStatsRequest, opts ...grpc.CallOption) (*BatchGetUserRechargeStatsResponse, error) GetRechargeBillOverview(ctx context.Context, in *GetRechargeBillOverviewRequest, opts ...grpc.CallOption) (*GetRechargeBillOverviewResponse, error) RefreshGooglePaymentPrices(ctx context.Context, in *RefreshGooglePaymentPricesRequest, opts ...grpc.CallOption) (*RefreshGooglePaymentPricesResponse, error) GetWalletOverview(ctx context.Context, in *GetWalletOverviewRequest, opts ...grpc.CallOption) (*GetWalletOverviewResponse, error) @@ -402,13 +464,23 @@ type WalletServiceClient interface { GetDiamondExchangeConfig(ctx context.Context, in *GetDiamondExchangeConfigRequest, opts ...grpc.CallOption) (*GetDiamondExchangeConfigResponse, error) ListWalletTransactions(ctx context.Context, in *ListWalletTransactionsRequest, opts ...grpc.CallOption) (*ListWalletTransactionsResponse, error) ListVipPackages(ctx context.Context, in *ListVipPackagesRequest, opts ...grpc.CallOption) (*ListVipPackagesResponse, error) + GetVipProgramConfig(ctx context.Context, in *GetVipProgramConfigRequest, opts ...grpc.CallOption) (*GetVipProgramConfigResponse, error) GetMyVip(ctx context.Context, in *GetMyVipRequest, opts ...grpc.CallOption) (*GetMyVipResponse, error) + CheckVipBenefit(ctx context.Context, in *CheckVipBenefitRequest, opts ...grpc.CallOption) (*CheckVipBenefitResponse, error) PurchaseVip(ctx context.Context, in *PurchaseVipRequest, opts ...grpc.CallOption) (*PurchaseVipResponse, error) DebitCPBreakupFee(ctx context.Context, in *DebitCPBreakupFeeRequest, opts ...grpc.CallOption) (*DebitCPBreakupFeeResponse, error) DebitWheelDraw(ctx context.Context, in *DebitWheelDrawRequest, opts ...grpc.CallOption) (*DebitWheelDrawResponse, error) GrantVip(ctx context.Context, in *GrantVipRequest, opts ...grpc.CallOption) (*GrantVipResponse, error) + GrantVipTrialCard(ctx context.Context, in *GrantVipTrialCardRequest, opts ...grpc.CallOption) (*GrantVipTrialCardResponse, error) + EquipVipTrialCard(ctx context.Context, in *EquipVipTrialCardRequest, opts ...grpc.CallOption) (*EquipVipTrialCardResponse, error) + UnequipVipTrialCard(ctx context.Context, in *UnequipVipTrialCardRequest, opts ...grpc.CallOption) (*UnequipVipTrialCardResponse, error) ListAdminVipLevels(ctx context.Context, in *ListAdminVipLevelsRequest, opts ...grpc.CallOption) (*ListAdminVipLevelsResponse, error) UpdateAdminVipLevels(ctx context.Context, in *UpdateAdminVipLevelsRequest, opts ...grpc.CallOption) (*UpdateAdminVipLevelsResponse, error) + UpdateAdminVipProgramConfig(ctx context.Context, in *UpdateAdminVipProgramConfigRequest, opts ...grpc.CallOption) (*UpdateAdminVipProgramConfigResponse, error) + UpdateMyVipSettings(ctx context.Context, in *UpdateMyVipSettingsRequest, opts ...grpc.CallOption) (*UpdateMyVipSettingsResponse, error) + GetMyCurrentVipDailyCoinRebate(ctx context.Context, in *GetMyCurrentVipDailyCoinRebateRequest, opts ...grpc.CallOption) (*GetMyCurrentVipDailyCoinRebateResponse, error) + ListMyVipDailyCoinRebateStatuses(ctx context.Context, in *ListMyVipDailyCoinRebateStatusesRequest, opts ...grpc.CallOption) (*ListMyVipDailyCoinRebateStatusesResponse, error) + ClaimVipDailyCoinRebate(ctx context.Context, in *ClaimVipDailyCoinRebateRequest, opts ...grpc.CallOption) (*ClaimVipDailyCoinRebateResponse, error) CreditTaskReward(ctx context.Context, in *CreditTaskRewardRequest, opts ...grpc.CallOption) (*CreditTaskRewardResponse, error) CreditLuckyGiftReward(ctx context.Context, in *CreditLuckyGiftRewardRequest, opts ...grpc.CallOption) (*CreditLuckyGiftRewardResponse, error) CreditWheelReward(ctx context.Context, in *CreditWheelRewardRequest, opts ...grpc.CallOption) (*CreditWheelRewardResponse, error) @@ -504,6 +576,36 @@ func (c *walletServiceClient) GetHostSalaryProgress(ctx context.Context, in *Get return out, nil } +func (c *walletServiceClient) GetHostRevenueStats(ctx context.Context, in *GetHostRevenueStatsRequest, opts ...grpc.CallOption) (*GetHostRevenueStatsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetHostRevenueStatsResponse) + err := c.cc.Invoke(ctx, WalletService_GetHostRevenueStats_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) GetAgencyPointShareStats(ctx context.Context, in *GetAgencyPointShareStatsRequest, opts ...grpc.CallOption) (*GetAgencyPointShareStatsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetAgencyPointShareStatsResponse) + err := c.cc.Invoke(ctx, WalletService_GetAgencyPointShareStats_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) GetAgencyHostGiftStats(ctx context.Context, in *GetAgencyHostGiftStatsRequest, opts ...grpc.CallOption) (*GetAgencyHostGiftStatsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetAgencyHostGiftStatsResponse) + err := c.cc.Invoke(ctx, WalletService_GetAgencyHostGiftStats_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *walletServiceClient) GetTeamHostSalaryStats(ctx context.Context, in *GetTeamHostSalaryStatsRequest, opts ...grpc.CallOption) (*GetTeamHostSalaryStatsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetTeamHostSalaryStatsResponse) @@ -584,6 +686,36 @@ func (c *walletServiceClient) TransferSalaryToCoinSeller(ctx context.Context, in return out, nil } +func (c *walletServiceClient) ListPointWithdrawalCoinSellers(ctx context.Context, in *ListPointWithdrawalCoinSellersRequest, opts ...grpc.CallOption) (*ListPointWithdrawalCoinSellersResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListPointWithdrawalCoinSellersResponse) + err := c.cc.Invoke(ctx, WalletService_ListPointWithdrawalCoinSellers_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) TransferPointToCoinSeller(ctx context.Context, in *TransferPointToCoinSellerRequest, opts ...grpc.CallOption) (*TransferPointToCoinSellerResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TransferPointToCoinSellerResponse) + err := c.cc.Invoke(ctx, WalletService_TransferPointToCoinSeller_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) GetPointWithdrawalConfig(ctx context.Context, in *GetPointWithdrawalConfigRequest, opts ...grpc.CallOption) (*GetPointWithdrawalConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetPointWithdrawalConfigResponse) + err := c.cc.Invoke(ctx, WalletService_GetPointWithdrawalConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *walletServiceClient) FreezeSalaryWithdrawal(ctx context.Context, in *FreezeSalaryWithdrawalRequest, opts ...grpc.CallOption) (*FreezeSalaryWithdrawalResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(FreezeSalaryWithdrawalResponse) @@ -1004,6 +1136,16 @@ func (c *walletServiceClient) GetRechargeBillSummary(ctx context.Context, in *Ge return out, nil } +func (c *walletServiceClient) BatchGetUserRechargeStats(ctx context.Context, in *BatchGetUserRechargeStatsRequest, opts ...grpc.CallOption) (*BatchGetUserRechargeStatsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(BatchGetUserRechargeStatsResponse) + err := c.cc.Invoke(ctx, WalletService_BatchGetUserRechargeStats_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *walletServiceClient) GetRechargeBillOverview(ctx context.Context, in *GetRechargeBillOverviewRequest, opts ...grpc.CallOption) (*GetRechargeBillOverviewResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetRechargeBillOverviewResponse) @@ -1284,6 +1426,16 @@ func (c *walletServiceClient) ListVipPackages(ctx context.Context, in *ListVipPa return out, nil } +func (c *walletServiceClient) GetVipProgramConfig(ctx context.Context, in *GetVipProgramConfigRequest, opts ...grpc.CallOption) (*GetVipProgramConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetVipProgramConfigResponse) + err := c.cc.Invoke(ctx, WalletService_GetVipProgramConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *walletServiceClient) GetMyVip(ctx context.Context, in *GetMyVipRequest, opts ...grpc.CallOption) (*GetMyVipResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetMyVipResponse) @@ -1294,6 +1446,16 @@ func (c *walletServiceClient) GetMyVip(ctx context.Context, in *GetMyVipRequest, return out, nil } +func (c *walletServiceClient) CheckVipBenefit(ctx context.Context, in *CheckVipBenefitRequest, opts ...grpc.CallOption) (*CheckVipBenefitResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CheckVipBenefitResponse) + err := c.cc.Invoke(ctx, WalletService_CheckVipBenefit_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *walletServiceClient) PurchaseVip(ctx context.Context, in *PurchaseVipRequest, opts ...grpc.CallOption) (*PurchaseVipResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(PurchaseVipResponse) @@ -1334,6 +1496,36 @@ func (c *walletServiceClient) GrantVip(ctx context.Context, in *GrantVipRequest, return out, nil } +func (c *walletServiceClient) GrantVipTrialCard(ctx context.Context, in *GrantVipTrialCardRequest, opts ...grpc.CallOption) (*GrantVipTrialCardResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GrantVipTrialCardResponse) + err := c.cc.Invoke(ctx, WalletService_GrantVipTrialCard_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) EquipVipTrialCard(ctx context.Context, in *EquipVipTrialCardRequest, opts ...grpc.CallOption) (*EquipVipTrialCardResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(EquipVipTrialCardResponse) + err := c.cc.Invoke(ctx, WalletService_EquipVipTrialCard_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) UnequipVipTrialCard(ctx context.Context, in *UnequipVipTrialCardRequest, opts ...grpc.CallOption) (*UnequipVipTrialCardResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UnequipVipTrialCardResponse) + err := c.cc.Invoke(ctx, WalletService_UnequipVipTrialCard_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *walletServiceClient) ListAdminVipLevels(ctx context.Context, in *ListAdminVipLevelsRequest, opts ...grpc.CallOption) (*ListAdminVipLevelsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListAdminVipLevelsResponse) @@ -1354,6 +1546,56 @@ func (c *walletServiceClient) UpdateAdminVipLevels(ctx context.Context, in *Upda return out, nil } +func (c *walletServiceClient) UpdateAdminVipProgramConfig(ctx context.Context, in *UpdateAdminVipProgramConfigRequest, opts ...grpc.CallOption) (*UpdateAdminVipProgramConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateAdminVipProgramConfigResponse) + err := c.cc.Invoke(ctx, WalletService_UpdateAdminVipProgramConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) UpdateMyVipSettings(ctx context.Context, in *UpdateMyVipSettingsRequest, opts ...grpc.CallOption) (*UpdateMyVipSettingsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateMyVipSettingsResponse) + err := c.cc.Invoke(ctx, WalletService_UpdateMyVipSettings_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) GetMyCurrentVipDailyCoinRebate(ctx context.Context, in *GetMyCurrentVipDailyCoinRebateRequest, opts ...grpc.CallOption) (*GetMyCurrentVipDailyCoinRebateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetMyCurrentVipDailyCoinRebateResponse) + err := c.cc.Invoke(ctx, WalletService_GetMyCurrentVipDailyCoinRebate_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) ListMyVipDailyCoinRebateStatuses(ctx context.Context, in *ListMyVipDailyCoinRebateStatusesRequest, opts ...grpc.CallOption) (*ListMyVipDailyCoinRebateStatusesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListMyVipDailyCoinRebateStatusesResponse) + err := c.cc.Invoke(ctx, WalletService_ListMyVipDailyCoinRebateStatuses_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) ClaimVipDailyCoinRebate(ctx context.Context, in *ClaimVipDailyCoinRebateRequest, opts ...grpc.CallOption) (*ClaimVipDailyCoinRebateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ClaimVipDailyCoinRebateResponse) + err := c.cc.Invoke(ctx, WalletService_ClaimVipDailyCoinRebate_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *walletServiceClient) CreditTaskReward(ctx context.Context, in *CreditTaskRewardRequest, opts ...grpc.CallOption) (*CreditTaskRewardResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(CreditTaskRewardResponse) @@ -1517,6 +1759,9 @@ type WalletServiceServer interface { GetBalances(context.Context, *GetBalancesRequest) (*GetBalancesResponse, error) GetActiveHostSalaryPolicy(context.Context, *GetActiveHostSalaryPolicyRequest) (*GetActiveHostSalaryPolicyResponse, error) GetHostSalaryProgress(context.Context, *GetHostSalaryProgressRequest) (*GetHostSalaryProgressResponse, error) + GetHostRevenueStats(context.Context, *GetHostRevenueStatsRequest) (*GetHostRevenueStatsResponse, error) + GetAgencyPointShareStats(context.Context, *GetAgencyPointShareStatsRequest) (*GetAgencyPointShareStatsResponse, error) + GetAgencyHostGiftStats(context.Context, *GetAgencyHostGiftStatsRequest) (*GetAgencyHostGiftStatsResponse, error) // GetTeamHostSalaryStats 按 Agency 收款人集合聚合主播预收入工资;经理中心团队工资卡片使用。 GetTeamHostSalaryStats(context.Context, *GetTeamHostSalaryStatsRequest) (*GetTeamHostSalaryStatsResponse, error) AdminCreditAsset(context.Context, *AdminCreditAssetRequest) (*AdminCreditAssetResponse, error) @@ -1526,6 +1771,9 @@ type WalletServiceServer interface { ListCoinSellerSalaryExchangeRateTiers(context.Context, *ListCoinSellerSalaryExchangeRateTiersRequest) (*ListCoinSellerSalaryExchangeRateTiersResponse, error) ExchangeSalaryToCoin(context.Context, *ExchangeSalaryToCoinRequest) (*ExchangeSalaryToCoinResponse, error) TransferSalaryToCoinSeller(context.Context, *TransferSalaryToCoinSellerRequest) (*TransferSalaryToCoinSellerResponse, error) + ListPointWithdrawalCoinSellers(context.Context, *ListPointWithdrawalCoinSellersRequest) (*ListPointWithdrawalCoinSellersResponse, error) + TransferPointToCoinSeller(context.Context, *TransferPointToCoinSellerRequest) (*TransferPointToCoinSellerResponse, error) + GetPointWithdrawalConfig(context.Context, *GetPointWithdrawalConfigRequest) (*GetPointWithdrawalConfigResponse, error) FreezeSalaryWithdrawal(context.Context, *FreezeSalaryWithdrawalRequest) (*FreezeSalaryWithdrawalResponse, error) SettleSalaryWithdrawal(context.Context, *SettleSalaryWithdrawalRequest) (*SettleSalaryWithdrawalResponse, error) ReleaseSalaryWithdrawal(context.Context, *ReleaseSalaryWithdrawalRequest) (*ReleaseSalaryWithdrawalResponse, error) @@ -1568,6 +1816,7 @@ type WalletServiceServer interface { PurchaseResourceShopItem(context.Context, *PurchaseResourceShopItemRequest) (*PurchaseResourceShopItemResponse, error) ListRechargeBills(context.Context, *ListRechargeBillsRequest) (*ListRechargeBillsResponse, error) GetRechargeBillSummary(context.Context, *GetRechargeBillSummaryRequest) (*GetRechargeBillSummaryResponse, error) + BatchGetUserRechargeStats(context.Context, *BatchGetUserRechargeStatsRequest) (*BatchGetUserRechargeStatsResponse, error) GetRechargeBillOverview(context.Context, *GetRechargeBillOverviewRequest) (*GetRechargeBillOverviewResponse, error) RefreshGooglePaymentPrices(context.Context, *RefreshGooglePaymentPricesRequest) (*RefreshGooglePaymentPricesResponse, error) GetWalletOverview(context.Context, *GetWalletOverviewRequest) (*GetWalletOverviewResponse, error) @@ -1596,13 +1845,23 @@ type WalletServiceServer interface { GetDiamondExchangeConfig(context.Context, *GetDiamondExchangeConfigRequest) (*GetDiamondExchangeConfigResponse, error) ListWalletTransactions(context.Context, *ListWalletTransactionsRequest) (*ListWalletTransactionsResponse, error) ListVipPackages(context.Context, *ListVipPackagesRequest) (*ListVipPackagesResponse, error) + GetVipProgramConfig(context.Context, *GetVipProgramConfigRequest) (*GetVipProgramConfigResponse, error) GetMyVip(context.Context, *GetMyVipRequest) (*GetMyVipResponse, error) + CheckVipBenefit(context.Context, *CheckVipBenefitRequest) (*CheckVipBenefitResponse, error) PurchaseVip(context.Context, *PurchaseVipRequest) (*PurchaseVipResponse, error) DebitCPBreakupFee(context.Context, *DebitCPBreakupFeeRequest) (*DebitCPBreakupFeeResponse, error) DebitWheelDraw(context.Context, *DebitWheelDrawRequest) (*DebitWheelDrawResponse, error) GrantVip(context.Context, *GrantVipRequest) (*GrantVipResponse, error) + GrantVipTrialCard(context.Context, *GrantVipTrialCardRequest) (*GrantVipTrialCardResponse, error) + EquipVipTrialCard(context.Context, *EquipVipTrialCardRequest) (*EquipVipTrialCardResponse, error) + UnequipVipTrialCard(context.Context, *UnequipVipTrialCardRequest) (*UnequipVipTrialCardResponse, error) ListAdminVipLevels(context.Context, *ListAdminVipLevelsRequest) (*ListAdminVipLevelsResponse, error) UpdateAdminVipLevels(context.Context, *UpdateAdminVipLevelsRequest) (*UpdateAdminVipLevelsResponse, error) + UpdateAdminVipProgramConfig(context.Context, *UpdateAdminVipProgramConfigRequest) (*UpdateAdminVipProgramConfigResponse, error) + UpdateMyVipSettings(context.Context, *UpdateMyVipSettingsRequest) (*UpdateMyVipSettingsResponse, error) + GetMyCurrentVipDailyCoinRebate(context.Context, *GetMyCurrentVipDailyCoinRebateRequest) (*GetMyCurrentVipDailyCoinRebateResponse, error) + ListMyVipDailyCoinRebateStatuses(context.Context, *ListMyVipDailyCoinRebateStatusesRequest) (*ListMyVipDailyCoinRebateStatusesResponse, error) + ClaimVipDailyCoinRebate(context.Context, *ClaimVipDailyCoinRebateRequest) (*ClaimVipDailyCoinRebateResponse, error) CreditTaskReward(context.Context, *CreditTaskRewardRequest) (*CreditTaskRewardResponse, error) CreditLuckyGiftReward(context.Context, *CreditLuckyGiftRewardRequest) (*CreditLuckyGiftRewardResponse, error) CreditWheelReward(context.Context, *CreditWheelRewardRequest) (*CreditWheelRewardResponse, error) @@ -1649,6 +1908,15 @@ func (UnimplementedWalletServiceServer) GetActiveHostSalaryPolicy(context.Contex func (UnimplementedWalletServiceServer) GetHostSalaryProgress(context.Context, *GetHostSalaryProgressRequest) (*GetHostSalaryProgressResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetHostSalaryProgress not implemented") } +func (UnimplementedWalletServiceServer) GetHostRevenueStats(context.Context, *GetHostRevenueStatsRequest) (*GetHostRevenueStatsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetHostRevenueStats not implemented") +} +func (UnimplementedWalletServiceServer) GetAgencyPointShareStats(context.Context, *GetAgencyPointShareStatsRequest) (*GetAgencyPointShareStatsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetAgencyPointShareStats not implemented") +} +func (UnimplementedWalletServiceServer) GetAgencyHostGiftStats(context.Context, *GetAgencyHostGiftStatsRequest) (*GetAgencyHostGiftStatsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetAgencyHostGiftStats not implemented") +} func (UnimplementedWalletServiceServer) GetTeamHostSalaryStats(context.Context, *GetTeamHostSalaryStatsRequest) (*GetTeamHostSalaryStatsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetTeamHostSalaryStats not implemented") } @@ -1673,6 +1941,15 @@ func (UnimplementedWalletServiceServer) ExchangeSalaryToCoin(context.Context, *E func (UnimplementedWalletServiceServer) TransferSalaryToCoinSeller(context.Context, *TransferSalaryToCoinSellerRequest) (*TransferSalaryToCoinSellerResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method TransferSalaryToCoinSeller not implemented") } +func (UnimplementedWalletServiceServer) ListPointWithdrawalCoinSellers(context.Context, *ListPointWithdrawalCoinSellersRequest) (*ListPointWithdrawalCoinSellersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListPointWithdrawalCoinSellers not implemented") +} +func (UnimplementedWalletServiceServer) TransferPointToCoinSeller(context.Context, *TransferPointToCoinSellerRequest) (*TransferPointToCoinSellerResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method TransferPointToCoinSeller not implemented") +} +func (UnimplementedWalletServiceServer) GetPointWithdrawalConfig(context.Context, *GetPointWithdrawalConfigRequest) (*GetPointWithdrawalConfigResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetPointWithdrawalConfig not implemented") +} func (UnimplementedWalletServiceServer) FreezeSalaryWithdrawal(context.Context, *FreezeSalaryWithdrawalRequest) (*FreezeSalaryWithdrawalResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method FreezeSalaryWithdrawal not implemented") } @@ -1799,6 +2076,9 @@ func (UnimplementedWalletServiceServer) ListRechargeBills(context.Context, *List func (UnimplementedWalletServiceServer) GetRechargeBillSummary(context.Context, *GetRechargeBillSummaryRequest) (*GetRechargeBillSummaryResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetRechargeBillSummary not implemented") } +func (UnimplementedWalletServiceServer) BatchGetUserRechargeStats(context.Context, *BatchGetUserRechargeStatsRequest) (*BatchGetUserRechargeStatsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BatchGetUserRechargeStats not implemented") +} func (UnimplementedWalletServiceServer) GetRechargeBillOverview(context.Context, *GetRechargeBillOverviewRequest) (*GetRechargeBillOverviewResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetRechargeBillOverview not implemented") } @@ -1883,9 +2163,15 @@ func (UnimplementedWalletServiceServer) ListWalletTransactions(context.Context, func (UnimplementedWalletServiceServer) ListVipPackages(context.Context, *ListVipPackagesRequest) (*ListVipPackagesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListVipPackages not implemented") } +func (UnimplementedWalletServiceServer) GetVipProgramConfig(context.Context, *GetVipProgramConfigRequest) (*GetVipProgramConfigResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetVipProgramConfig not implemented") +} func (UnimplementedWalletServiceServer) GetMyVip(context.Context, *GetMyVipRequest) (*GetMyVipResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetMyVip not implemented") } +func (UnimplementedWalletServiceServer) CheckVipBenefit(context.Context, *CheckVipBenefitRequest) (*CheckVipBenefitResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CheckVipBenefit not implemented") +} func (UnimplementedWalletServiceServer) PurchaseVip(context.Context, *PurchaseVipRequest) (*PurchaseVipResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method PurchaseVip not implemented") } @@ -1898,12 +2184,36 @@ func (UnimplementedWalletServiceServer) DebitWheelDraw(context.Context, *DebitWh func (UnimplementedWalletServiceServer) GrantVip(context.Context, *GrantVipRequest) (*GrantVipResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GrantVip not implemented") } +func (UnimplementedWalletServiceServer) GrantVipTrialCard(context.Context, *GrantVipTrialCardRequest) (*GrantVipTrialCardResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GrantVipTrialCard not implemented") +} +func (UnimplementedWalletServiceServer) EquipVipTrialCard(context.Context, *EquipVipTrialCardRequest) (*EquipVipTrialCardResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method EquipVipTrialCard not implemented") +} +func (UnimplementedWalletServiceServer) UnequipVipTrialCard(context.Context, *UnequipVipTrialCardRequest) (*UnequipVipTrialCardResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UnequipVipTrialCard not implemented") +} func (UnimplementedWalletServiceServer) ListAdminVipLevels(context.Context, *ListAdminVipLevelsRequest) (*ListAdminVipLevelsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListAdminVipLevels not implemented") } func (UnimplementedWalletServiceServer) UpdateAdminVipLevels(context.Context, *UpdateAdminVipLevelsRequest) (*UpdateAdminVipLevelsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateAdminVipLevels not implemented") } +func (UnimplementedWalletServiceServer) UpdateAdminVipProgramConfig(context.Context, *UpdateAdminVipProgramConfigRequest) (*UpdateAdminVipProgramConfigResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateAdminVipProgramConfig not implemented") +} +func (UnimplementedWalletServiceServer) UpdateMyVipSettings(context.Context, *UpdateMyVipSettingsRequest) (*UpdateMyVipSettingsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateMyVipSettings not implemented") +} +func (UnimplementedWalletServiceServer) GetMyCurrentVipDailyCoinRebate(context.Context, *GetMyCurrentVipDailyCoinRebateRequest) (*GetMyCurrentVipDailyCoinRebateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetMyCurrentVipDailyCoinRebate not implemented") +} +func (UnimplementedWalletServiceServer) ListMyVipDailyCoinRebateStatuses(context.Context, *ListMyVipDailyCoinRebateStatusesRequest) (*ListMyVipDailyCoinRebateStatusesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListMyVipDailyCoinRebateStatuses not implemented") +} +func (UnimplementedWalletServiceServer) ClaimVipDailyCoinRebate(context.Context, *ClaimVipDailyCoinRebateRequest) (*ClaimVipDailyCoinRebateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClaimVipDailyCoinRebate not implemented") +} func (UnimplementedWalletServiceServer) CreditTaskReward(context.Context, *CreditTaskRewardRequest) (*CreditTaskRewardResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CreditTaskReward not implemented") } @@ -2096,6 +2406,60 @@ func _WalletService_GetHostSalaryProgress_Handler(srv interface{}, ctx context.C return interceptor(ctx, in, info, handler) } +func _WalletService_GetHostRevenueStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetHostRevenueStatsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).GetHostRevenueStats(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_GetHostRevenueStats_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).GetHostRevenueStats(ctx, req.(*GetHostRevenueStatsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_GetAgencyPointShareStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAgencyPointShareStatsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).GetAgencyPointShareStats(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_GetAgencyPointShareStats_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).GetAgencyPointShareStats(ctx, req.(*GetAgencyPointShareStatsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_GetAgencyHostGiftStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAgencyHostGiftStatsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).GetAgencyHostGiftStats(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_GetAgencyHostGiftStats_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).GetAgencyHostGiftStats(ctx, req.(*GetAgencyHostGiftStatsRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _WalletService_GetTeamHostSalaryStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetTeamHostSalaryStatsRequest) if err := dec(in); err != nil { @@ -2240,6 +2604,60 @@ func _WalletService_TransferSalaryToCoinSeller_Handler(srv interface{}, ctx cont return interceptor(ctx, in, info, handler) } +func _WalletService_ListPointWithdrawalCoinSellers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListPointWithdrawalCoinSellersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).ListPointWithdrawalCoinSellers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_ListPointWithdrawalCoinSellers_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).ListPointWithdrawalCoinSellers(ctx, req.(*ListPointWithdrawalCoinSellersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_TransferPointToCoinSeller_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TransferPointToCoinSellerRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).TransferPointToCoinSeller(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_TransferPointToCoinSeller_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).TransferPointToCoinSeller(ctx, req.(*TransferPointToCoinSellerRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_GetPointWithdrawalConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetPointWithdrawalConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).GetPointWithdrawalConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_GetPointWithdrawalConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).GetPointWithdrawalConfig(ctx, req.(*GetPointWithdrawalConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _WalletService_FreezeSalaryWithdrawal_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(FreezeSalaryWithdrawalRequest) if err := dec(in); err != nil { @@ -2996,6 +3414,24 @@ func _WalletService_GetRechargeBillSummary_Handler(srv interface{}, ctx context. return interceptor(ctx, in, info, handler) } +func _WalletService_BatchGetUserRechargeStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BatchGetUserRechargeStatsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).BatchGetUserRechargeStats(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_BatchGetUserRechargeStats_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).BatchGetUserRechargeStats(ctx, req.(*BatchGetUserRechargeStatsRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _WalletService_GetRechargeBillOverview_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetRechargeBillOverviewRequest) if err := dec(in); err != nil { @@ -3500,6 +3936,24 @@ func _WalletService_ListVipPackages_Handler(srv interface{}, ctx context.Context return interceptor(ctx, in, info, handler) } +func _WalletService_GetVipProgramConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetVipProgramConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).GetVipProgramConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_GetVipProgramConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).GetVipProgramConfig(ctx, req.(*GetVipProgramConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _WalletService_GetMyVip_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetMyVipRequest) if err := dec(in); err != nil { @@ -3518,6 +3972,24 @@ func _WalletService_GetMyVip_Handler(srv interface{}, ctx context.Context, dec f return interceptor(ctx, in, info, handler) } +func _WalletService_CheckVipBenefit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CheckVipBenefitRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).CheckVipBenefit(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_CheckVipBenefit_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).CheckVipBenefit(ctx, req.(*CheckVipBenefitRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _WalletService_PurchaseVip_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(PurchaseVipRequest) if err := dec(in); err != nil { @@ -3590,6 +4062,60 @@ func _WalletService_GrantVip_Handler(srv interface{}, ctx context.Context, dec f return interceptor(ctx, in, info, handler) } +func _WalletService_GrantVipTrialCard_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GrantVipTrialCardRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).GrantVipTrialCard(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_GrantVipTrialCard_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).GrantVipTrialCard(ctx, req.(*GrantVipTrialCardRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_EquipVipTrialCard_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EquipVipTrialCardRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).EquipVipTrialCard(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_EquipVipTrialCard_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).EquipVipTrialCard(ctx, req.(*EquipVipTrialCardRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_UnequipVipTrialCard_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UnequipVipTrialCardRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).UnequipVipTrialCard(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_UnequipVipTrialCard_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).UnequipVipTrialCard(ctx, req.(*UnequipVipTrialCardRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _WalletService_ListAdminVipLevels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListAdminVipLevelsRequest) if err := dec(in); err != nil { @@ -3626,6 +4152,96 @@ func _WalletService_UpdateAdminVipLevels_Handler(srv interface{}, ctx context.Co return interceptor(ctx, in, info, handler) } +func _WalletService_UpdateAdminVipProgramConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateAdminVipProgramConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).UpdateAdminVipProgramConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_UpdateAdminVipProgramConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).UpdateAdminVipProgramConfig(ctx, req.(*UpdateAdminVipProgramConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_UpdateMyVipSettings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateMyVipSettingsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).UpdateMyVipSettings(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_UpdateMyVipSettings_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).UpdateMyVipSettings(ctx, req.(*UpdateMyVipSettingsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_GetMyCurrentVipDailyCoinRebate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetMyCurrentVipDailyCoinRebateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).GetMyCurrentVipDailyCoinRebate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_GetMyCurrentVipDailyCoinRebate_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).GetMyCurrentVipDailyCoinRebate(ctx, req.(*GetMyCurrentVipDailyCoinRebateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_ListMyVipDailyCoinRebateStatuses_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListMyVipDailyCoinRebateStatusesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).ListMyVipDailyCoinRebateStatuses(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_ListMyVipDailyCoinRebateStatuses_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).ListMyVipDailyCoinRebateStatuses(ctx, req.(*ListMyVipDailyCoinRebateStatusesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_ClaimVipDailyCoinRebate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClaimVipDailyCoinRebateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).ClaimVipDailyCoinRebate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_ClaimVipDailyCoinRebate_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).ClaimVipDailyCoinRebate(ctx, req.(*ClaimVipDailyCoinRebateRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _WalletService_CreditTaskReward_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CreditTaskRewardRequest) if err := dec(in); err != nil { @@ -3931,6 +4547,18 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetHostSalaryProgress", Handler: _WalletService_GetHostSalaryProgress_Handler, }, + { + MethodName: "GetHostRevenueStats", + Handler: _WalletService_GetHostRevenueStats_Handler, + }, + { + MethodName: "GetAgencyPointShareStats", + Handler: _WalletService_GetAgencyPointShareStats_Handler, + }, + { + MethodName: "GetAgencyHostGiftStats", + Handler: _WalletService_GetAgencyHostGiftStats_Handler, + }, { MethodName: "GetTeamHostSalaryStats", Handler: _WalletService_GetTeamHostSalaryStats_Handler, @@ -3963,6 +4591,18 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{ MethodName: "TransferSalaryToCoinSeller", Handler: _WalletService_TransferSalaryToCoinSeller_Handler, }, + { + MethodName: "ListPointWithdrawalCoinSellers", + Handler: _WalletService_ListPointWithdrawalCoinSellers_Handler, + }, + { + MethodName: "TransferPointToCoinSeller", + Handler: _WalletService_TransferPointToCoinSeller_Handler, + }, + { + MethodName: "GetPointWithdrawalConfig", + Handler: _WalletService_GetPointWithdrawalConfig_Handler, + }, { MethodName: "FreezeSalaryWithdrawal", Handler: _WalletService_FreezeSalaryWithdrawal_Handler, @@ -4131,6 +4771,10 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetRechargeBillSummary", Handler: _WalletService_GetRechargeBillSummary_Handler, }, + { + MethodName: "BatchGetUserRechargeStats", + Handler: _WalletService_BatchGetUserRechargeStats_Handler, + }, { MethodName: "GetRechargeBillOverview", Handler: _WalletService_GetRechargeBillOverview_Handler, @@ -4243,10 +4887,18 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ListVipPackages", Handler: _WalletService_ListVipPackages_Handler, }, + { + MethodName: "GetVipProgramConfig", + Handler: _WalletService_GetVipProgramConfig_Handler, + }, { MethodName: "GetMyVip", Handler: _WalletService_GetMyVip_Handler, }, + { + MethodName: "CheckVipBenefit", + Handler: _WalletService_CheckVipBenefit_Handler, + }, { MethodName: "PurchaseVip", Handler: _WalletService_PurchaseVip_Handler, @@ -4263,6 +4915,18 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GrantVip", Handler: _WalletService_GrantVip_Handler, }, + { + MethodName: "GrantVipTrialCard", + Handler: _WalletService_GrantVipTrialCard_Handler, + }, + { + MethodName: "EquipVipTrialCard", + Handler: _WalletService_EquipVipTrialCard_Handler, + }, + { + MethodName: "UnequipVipTrialCard", + Handler: _WalletService_UnequipVipTrialCard_Handler, + }, { MethodName: "ListAdminVipLevels", Handler: _WalletService_ListAdminVipLevels_Handler, @@ -4271,6 +4935,26 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{ MethodName: "UpdateAdminVipLevels", Handler: _WalletService_UpdateAdminVipLevels_Handler, }, + { + MethodName: "UpdateAdminVipProgramConfig", + Handler: _WalletService_UpdateAdminVipProgramConfig_Handler, + }, + { + MethodName: "UpdateMyVipSettings", + Handler: _WalletService_UpdateMyVipSettings_Handler, + }, + { + MethodName: "GetMyCurrentVipDailyCoinRebate", + Handler: _WalletService_GetMyCurrentVipDailyCoinRebate_Handler, + }, + { + MethodName: "ListMyVipDailyCoinRebateStatuses", + Handler: _WalletService_ListMyVipDailyCoinRebateStatuses_Handler, + }, + { + MethodName: "ClaimVipDailyCoinRebate", + Handler: _WalletService_ClaimVipDailyCoinRebate_Handler, + }, { MethodName: "CreditTaskReward", Handler: _WalletService_CreditTaskReward_Handler, diff --git a/docker-compose.yml b/docker-compose.yml index ded62f2e..4386a9ef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -80,10 +80,10 @@ services: condition: service_healthy wallet-service: condition: service_healthy - lucky-gift-service: - condition: service_healthy rocketmq-broker: condition: service_started + lucky-gift-service: + condition: service_healthy healthcheck: test: ["CMD-SHELL", "wget -q -O - http://127.0.0.1:13101/healthz/ready >/dev/null"] interval: 5s @@ -186,6 +186,8 @@ services: condition: service_healthy wallet-service: condition: service_healthy + rocketmq-broker: + condition: service_started healthcheck: test: ["CMD-SHELL", "wget -q -O - http://127.0.0.1:13113/healthz/ready >/dev/null"] interval: 5s diff --git a/docs/App消息Tab架构.md b/docs/App消息Tab架构.md index 0eda01af..45773602 100644 --- a/docs/App消息Tab架构.md +++ b/docs/App消息Tab架构.md @@ -516,7 +516,7 @@ tasks: timeout: 10s lock_ttl: 30s batch_size: 500 - app_codes: ["lalu"] + app_codes: ["lalu","fami","huwaa"] ``` 如果消息 owner 后续拆成独立 `message-service`,这些配置迁移到新服务;gateway 只需要更新 message gRPC 地址。 diff --git a/docs/Fami_VIP_Flutter对接方案.md b/docs/Fami_VIP_Flutter对接方案.md new file mode 100644 index 00000000..47492970 --- /dev/null +++ b/docs/Fami_VIP_Flutter对接方案.md @@ -0,0 +1,1109 @@ +# Fami VIP Flutter 对接方案 + +本文只定义 Flutter 与已落地后端的对接契约,不要在客户端重复实现 VIP 状态机。界面和文案统一使用“VIP”,不再使用“SVIP”。 + +## 1. 通用约定 + +- 地址前缀:`/api/v1`,实际 host 按环境配置。 +- 所有接口需携带 `Authorization: Bearer `。`app_code` 和 `user_id` 从登录态解析,Flutter 不在 body 中传。 +- 所有成功响应使用统一外壳: + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_xxx", + "data": {} +} +``` + +- `request_id` 由后端生成,只用于追踪,不是幂等键。购买幂等键是 `command_id`。 +- 所有 `*_at_ms` / `duration_ms` 都是 Unix epoch milliseconds。业务切日使用 UTC,Flutter 只在展示层转本地时间。 +- 客户端展示当前身份只读 `state.effective_vip`,权益资格只读 `state.effective_benefits`。不要根据等级、`unlock_level` 或 App 名自行继承权益;进房/上线通知还要结合 `state.user_settings`。 + +## 2. VIP 套餐 + +### 接口地址 + +`GET /api/v1/vip/packages` + +### 参数 + +无 query/body 参数。 + +### 返回值 + +以下 JSON 仅保留对接所需的代表字段;后端会按本节字段表返回完整对象。 + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_01", + "data": { + "current_vip": { + "level": 3, + "name": "VIP3", + "active": true, + "started_at_ms": 1780000000000, + "expires_at_ms": 1782592000000, + "program_type": "tiered_privilege_v1", + "config_version": 7 + }, + "packages": [ + { + "level": 4, + "name": "VIP4", + "status": "active", + "price_coin": 4000, + "duration_ms": 2592000000, + "reward_resource_group_id": 0, + "reward_items": [], + "can_purchase": true, + "sort_order": 40, + "recharge_gate_required": false, + "required_recharge_coin_amount": 0, + "user_recharge_coin_amount": 0, + "purchase_locked_reason": "", + "benefits": [ + { + "benefit_code": "custom_room_background", + "name": "自定义房间背景", + "benefit_type": "function", + "unlock_level": 3, + "status": "active", + "trial_enabled": true, + "resource_id": 0, + "resource_type": "", + "execution_scope": "room", + "auto_equip": false, + "sort_order": 110, + "metadata_json": "", + "created_at_ms": 0, + "updated_at_ms": 0 + } + ], + "config_version": 7 + } + ], + "program_config": { + "app_code": "fami", + "program_type": "tiered_privilege_v1", + "level_count": 9, + "same_level_expiry_policy": "extend_remaining", + "upgrade_expiry_policy": "replace_from_now", + "downgrade_purchase_policy": "reject", + "benefit_inheritance_policy": "target_only", + "grant_mode": "trial_card", + "trial_card_enabled": true, + "status": "active", + "config_version": 7 + }, + "state": { + "paid_vip": { + "level": 3, + "name": "VIP3", + "active": true, + "expires_at_ms": 1782592000000 + }, + "equipped_trial_card": null, + "effective_vip": { + "level": 3, + "name": "VIP3", + "active": true, + "expires_at_ms": 1782592000000 + }, + "effective_source": "paid", + "effective_benefits": [ + { + "benefit_code": "custom_room_background", + "status": "active" + } + ], + "evaluated_at_ms": 1780000000000, + "program_config": { + "app_code": "fami", + "program_type": "tiered_privilege_v1", + "config_version": 7 + }, + "user_settings": { + "app_code": "fami", + "user_id": 10001, + "room_entry_notice_enabled": true, + "online_global_notice_enabled": true, + "updated_at_ms": 0 + } + } + } +} +``` + +`packages` 只返回后台已启用的等级。Flutter 必须动态渲染,不得硬编码 9 个套餐。Fami 初始 VIP1–VIP9 均为 `disabled`,运营配置价格并启用前,`packages` 为空是正常结果。 + +### 相关 IM + +无。 + +### 场景处理 + +- 购买按钮以 `can_purchase` 为准;`false` 时可用 `purchase_locked_reason` 决定禁用态。 +- `benefits` 已是该等级的完整集合。`unlock_level` 仅是配置来源信息,不参与 Flutter 权益计算。 +- `current_vip` 是兼容字段,新代码优先使用 `state.effective_vip`。 + +## 3. 当前 VIP 状态 + +### 接口地址 + +`GET /api/v1/vip/me` + +### 参数 + +无 query/body 参数。 + +### 返回值 + +以下为结构示例,`state` 和 `program_config` 实际返回完整字段。 + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_02", + "data": { + "level": 8, + "name": "VIP8", + "active": true, + "started_at_ms": 1780100000000, + "expires_at_ms": 1782692000000, + "program_type": "tiered_privilege_v1", + "config_version": 7, + "program_config": { + "app_code": "fami", + "program_type": "tiered_privilege_v1", + "level_count": 9, + "same_level_expiry_policy": "extend_remaining", + "upgrade_expiry_policy": "replace_from_now", + "downgrade_purchase_policy": "reject", + "benefit_inheritance_policy": "target_only", + "grant_mode": "trial_card", + "trial_card_enabled": true, + "status": "active", + "config_version": 7 + }, + "state": { + "paid_vip": { + "level": 3, + "name": "VIP3", + "active": true, + "started_at_ms": 1780000000000, + "expires_at_ms": 1782592000000, + "program_type": "tiered_privilege_v1", + "config_version": 7 + }, + "equipped_trial_card": { + "trial_card_id": "vip_trial_card_xxx", + "entitlement_id": "ent_xxx", + "resource_id": 108, + "user_id": 10001, + "level": 8, + "name": "VIP8", + "status": "active", + "equipped": true, + "duration_ms": 2592000000, + "effective_at_ms": 1780100000000, + "expires_at_ms": 1782692000000, + "remaining_duration_ms": 2591000000, + "grant_source": "admin_grant", + "source_grant_id": "grant_xxx", + "created_at_ms": 1780100000000, + "updated_at_ms": 1780100000000 + }, + "effective_vip": { + "level": 8, + "name": "VIP8", + "active": true, + "started_at_ms": 1780100000000, + "expires_at_ms": 1782692000000, + "program_type": "tiered_privilege_v1", + "config_version": 7 + }, + "effective_source": "trial", + "effective_benefits": [ + { + "benefit_code": "room_entry_notice", + "name": "进房高亮通知", + "benefit_type": "function", + "unlock_level": 8, + "status": "active", + "trial_enabled": true, + "resource_id": 0, + "resource_type": "", + "execution_scope": "room", + "auto_equip": false, + "sort_order": 290, + "metadata_json": "" + } + ], + "evaluated_at_ms": 1780101000000, + "program_config": { + "app_code": "fami", + "program_type": "tiered_privilege_v1", + "config_version": 7 + }, + "user_settings": { + "app_code": "fami", + "user_id": 10001, + "room_entry_notice_enabled": true, + "online_global_notice_enabled": true, + "updated_at_ms": 0 + } + } + } +} +``` + +`data.level/name/active/...` 是兼容旧版的最终生效 VIP 扁平投影,新 Flutter 代码应读 `data.state`。 + +### 相关 IM + +本接口自身不发 IM。收到购买/发放的 `vip_notice` 后,应调用本接口刷新最终状态。 + +### 场景处理 + +- `effective_source` 取值为 `paid` / `trial` / `none`。 +- `paid_vip` 是付费会员事实;`equipped_trial_card` 是当前佩戴卡;`effective_vip` 是页面和房间展示的唯一真值。 +- 后台关闭 `trial_card_enabled` 时,`equipped_trial_card` 原始背包事实仍返回,但 `effective_source` 会立即回落为 `paid`;Flutter 不得继续沿用旧体验卡权限缓存。 +- 当体验卡被佩戴时,不限制卡等级高低,它会覆盖付费 VIP 成为 `effective_vip`;卸下或过期后自然回落到仍有效的 `paid_vip`。 +- 体验卡状态下,`daily_coin_rebate` 不会进入 `effective_benefits`。Flutter 不要只看卡等级猜测权益。 +- 无 VIP 时,`effective_source="none"`、`effective_vip.active=false`、`effective_benefits=[]`。 +- `user_settings.updated_at_ms=0` 表示用户尚未修改,两个开关使用默认开启值;设置在无 VIP 时也保留,但不会单独授权。 + +## 4. 购买、续期和升级 + +### 接口地址 + +`POST /api/v1/vip/purchase` + +### 参数 + +```json +{ + "command_id": "vip_purchase_10001_4_1780100000000", + "level": 4 +} +``` + +| 字段 | 说明 | +|---|---| +| `command_id` | 必填,同一次购买的网络重试必须复用同一值;新的购买动作必须生成新值。 | +| `level` | 必填,目标 VIP 等级,必须来自 `packages`。 | + +### 返回值 + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_03", + "data": { + "order_id": "vip_order_xxx", + "transaction_id": "tx_xxx", + "vip": { + "level": 4, + "name": "VIP4", + "active": true, + "started_at_ms": 1780100000000, + "expires_at_ms": 1782692000000, + "program_type": "tiered_privilege_v1", + "config_version": 7 + }, + "coin_spent": 4000, + "coin_balance_after": 6000, + "reward_items": [], + "program_config": { + "app_code": "fami", + "program_type": "tiered_privilege_v1", + "upgrade_expiry_policy": "replace_from_now", + "config_version": 7 + }, + "state": { + "effective_source": "paid", + "paid_vip": { + "level": 4, + "name": "VIP4", + "active": true, + "expires_at_ms": 1782692000000 + }, + "equipped_trial_card": null, + "effective_vip": { + "level": 4, + "name": "VIP4", + "active": true, + "expires_at_ms": 1782692000000 + }, + "effective_benefits": [ + { + "benefit_code": "custom_room_background", + "status": "active" + } + ], + "evaluated_at_ms": 1780100000000, + "program_config": { + "app_code": "fami", + "program_type": "tiered_privilege_v1", + "config_version": 7 + } + } + } +} +``` + +### 相关 IM + +购买或后台直接激活成功后,notice-service 向该用户发送 C2C `TIMCustomElem`: + +```json +{ + "Ext": "vip_notice", + "Desc": "VipActivated", + "Data": "{\"event_type\":\"VipActivated\",\"event_id\":\"evt_xxx\",\"app_code\":\"fami\",\"transaction_id\":\"tx_xxx\",\"command_id\":\"vip_purchase_10001_4_1780100000000\",\"user_id\":\"10001\",\"asset_type\":\"VIP\",\"available_delta\":0,\"frozen_delta\":0,\"source\":\"vip_purchase\",\"action\":\"upgrade\",\"previous_level\":3,\"previous_expires_at_ms\":1781396000000,\"level\":4,\"vip_name\":\"VIP4\",\"started_at_ms\":1780100000000,\"expires_at_ms\":1782692000000,\"program_type\":\"tiered_privilege_v1\",\"config_version\":7,\"expiry_policy\":\"replace_from_now\",\"reward_resource_group_id\":0,\"resource_grant_id\":\"\",\"reward_items\":[],\"created_at_ms\":1780100000000,\"source_created_at_ms\":1780100000000}" +} +``` + +Flutter 收到后用 `event_id` 做会话内去重,再调 `GET /vip/me`。不要用 IM payload 自行拼出最终权益。 + +### 场景处理 + +- Fami 无有效会员时:新截止时间 = 购买时间 + `duration_ms`。 +- Fami 同级续期:新截止时间 = 旧 `expires_at_ms` + `duration_ms`,保留剩余时间。 +- Fami 高级升级:新截止时间 = 购买时间 + `duration_ms`,旧低等级剩余时间丢弃。例如 VIP3 剩 15 天购买 VIP4 30 天,结果是 VIP4 30 天。 +- 购买更低等级由后端拒绝,错误 `VIP_DOWNGRADE_NOT_ALLOWED`。 +- 余额不足返回 `INSUFFICIENT_BALANCE`;等级未启用返回 `VIP_LEVEL_DISABLED`。 +- 如果购买时仍佩戴体验卡,购买结果会写入 `paid_vip`,但当前展示仍可能是 `effective_source="trial"`。购买成功页也必须以返回的 `state.effective_vip` 为准。 + +## 5. 体验卡背包列表 + +### 接口地址 + +`GET /api/v1/users/me/resources?resource_type=vip_trial_card` + +### 参数 + +| 参数 | 位置 | 值 | +|---|---|---| +| `resource_type` | query | 固定 `vip_trial_card` | + +### 返回值 + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_04", + "data": { + "items": [ + { + "entitlement_id": "ent_card_a", + "resource_id": 101, + "resource": { + "resource_id": 101, + "resource_code": "vip_trial_card_3", + "resource_type": "vip_trial_card", + "name": "VIP3体验卡", + "status": "active", + "grantable": true, + "grant_strategy": "new_entitlement", + "usage_scopes": [], + "asset_url": "", + "preview_url": "", + "animation_url": "", + "metadata_json": "{\"vip_level\":3}", + "sort_order": 30 + }, + "status": "active", + "quantity": 1, + "remaining_quantity": 1, + "effective_at_ms": 1780100000000, + "expires_at_ms": 1781309600000, + "source_grant_id": "grant_a", + "created_at_ms": 1780100000000, + "updated_at_ms": 1780100000000, + "equipped": false + } + ], + "total": 1 + } +} +``` + +`metadata_json` 是 JSON 字符串,如需读取 `vip_level` 必须再解析一次;但当前生效等级仍以 `/vip/me` 为准。 + +### 相关 IM + +无。后台发卡目前不会自动佩戴,也没有已定义的体验卡 C2C 消息。 + +### 场景处理 + +- 体验卡从发放时的 `effective_at_ms` 开始绝对计时,不是首次佩戴时才开始。 +- 切换、卸下都不修改任何卡的 `expires_at_ms`,也不删除旧卡。 +- 列表仅返回当前有效的背包实例。卡过期后会自然从 active 列表消失。 +- 预置卡素材初始为空,Flutter 需提供无图兜底,不得拼接本地固定素材 URL。 + +## 6. 佩戴体验卡 + +### 接口地址 + +`POST /api/v1/vip/trial-cards/{entitlement_id}/equip` + +### 参数 + +- path `entitlement_id`:必须使用背包列表的实例 ID,不能使用 `resource_id`。 +- 无 body。 + +### 返回值 + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_05", + "data": { + "trial_card": { + "trial_card_id": "vip_trial_card_b", + "entitlement_id": "ent_card_b", + "resource_id": 102, + "user_id": 10001, + "level": 4, + "name": "VIP4", + "status": "active", + "equipped": true, + "duration_ms": 1728000000, + "effective_at_ms": 1780200000000, + "expires_at_ms": 1781928000000, + "remaining_duration_ms": 1700000000, + "grant_source": "admin_grant", + "source_grant_id": "grant_b", + "created_at_ms": 1780200000000, + "updated_at_ms": 1780200000000 + }, + "program_config": { + "app_code": "fami", + "program_type": "tiered_privilege_v1", + "trial_card_enabled": true + }, + "state": { + "effective_source": "trial", + "effective_vip": { + "level": 4, + "name": "VIP4", + "active": true, + "expires_at_ms": 1781928000000 + }, + "effective_benefits": [] + }, + "server_time_ms": 1780228000000 + } +} +``` + +### 相关 IM + +无。 + +### 场景处理 + +- 任意等级可自由切换,不比较新旧卡高低。 +- 切换 A 到 B 后,B 成为 `equipped=true`,A 仍在背包中,A/B 的绝对截止时间均不变。 +- 必须直接用返回的 `state` 更新全局 VIP,不要等待 IM。 +- 倒计时使用 `expires_at_ms - server_time_ms`,不要在每次切换时重置为 `duration_ms`。 +- Flutter 的 VIP 流程固定调用本接口,不调用通用 `/users/me/resources/{resource_id}/equip`。后端虽兼容通用背包装备并会内部转交同一 VIP 状态机,但通用响应没有完整 `state`;历史通用背包组件若触发成功,必须立即补拉 `GET /vip/me`。 + +## 7. 卸下体验卡 + +### 接口地址 + +`DELETE /api/v1/vip/trial-cards/equipped` + +### 参数 + +无 path/query/body 参数。 + +### 返回值 + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_06", + "data": { + "unequipped": true, + "program_config": { + "app_code": "fami", + "program_type": "tiered_privilege_v1" + }, + "state": { + "effective_source": "paid", + "paid_vip": { + "level": 3, + "name": "VIP3", + "active": true + }, + "equipped_trial_card": null, + "effective_vip": { + "level": 3, + "name": "VIP3", + "active": true + }, + "effective_benefits": [] + }, + "server_time_ms": 1780229000000 + } +} +``` + +### 相关 IM + +无。 + +### 场景处理 + +- Flutter 的 VIP 流程固定调用本接口,不调用通用 `/users/me/resources/vip_trial_card/unequip`。后端对历史通用背包调用同样会转交专用状态机,但通用响应不包含回落后的完整 `state`。 + +- `unequipped=true` 表示本次确实解除了佩戴;重复调用可返回 `false`,仍按成功处理。 +- 卸下只删除佩戴关系,不删卡、不暂停卡、不修改卡的截止时间。 +- 卸下后优先回落到有效 `paid_vip`;没有付费 VIP 则 `effective_source="none"`。 + +## 8. 经理中心发放 VIP + +经理中心页面如果复用 Flutter,仍调用原地址,不需要按 Fami/Lalu 写分支。gateway 会读取当前 App 的 `program_config.grant_mode`:Lalu 调直接会员发放,Fami 调体验卡专用发放。 + +### 接口地址 + +`POST /api/v1/manager-center/vip-grants` + +### 参数 + +```json +{ + "command_id": "mgr_vip_10001_1780300000000", + "target_user_id": "10001", + "level": 5, + "reason": "manager_center" +} +``` + +- `command_id` 必填且最长 128 字节;重试必须复用同一个值和同一业务参数。 +- 经理中心现有权限仍只允许 VIP1–VIP5,目标用户国家与经理权限校验保持不变。 + +### 返回值 + +Lalu(`direct_membership`)直接激活会员: + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_08a", + "data": { + "grant_mode": "direct_membership", + "transaction_id": "tx_xxx", + "level": 5, + "active": true, + "expires_at_ms": 1782892000000, + "server_time_ms": 1780300000000 + } +} +``` + +Fami(`trial_card`)发一张 30 天体验卡到背包,不自动佩戴: + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_08b", + "data": { + "grant_mode": "trial_card", + "transaction_id": "", + "trial_card_id": "vip_card_xxx", + "entitlement_id": "ent_xxx", + "source_grant_id": "grant_xxx", + "level": 5, + "active": false, + "card_status": "active", + "equipped": false, + "expires_at_ms": 1782892000000, + "server_time_ms": 1780300000000 + } +} +``` + +### 相关 IM + +无。Fami 发卡成功后,被发放用户下次进入背包或收到后续资源刷新信号时重新拉取资源列表;当前接口不会替用户佩戴。 + +### 场景处理 + +- 只根据响应 `grant_mode` 展示“VIP 已生效”或“体验卡已发到背包”,不能仅按 `level` 判断。 +- `trial_card` 返回的 `active=false` 表示没有直接激活会员;卡片本身状态看 `card_status`。 +- 配置恰好在 gateway 查询后被后台切换时,wallet 会拒绝旧模式写入;Flutter 展示接口错误并允许使用同一 `command_id` 重试。 + +## 9. 进房通知 IM + +### 接口地址 + +进房仍使用现有 `POST /api/v1/rooms/join`。VIP 展示不新增 Flutter HTTP 请求。 + +### 参数 + +沿用现有进房参数: + +```json +{ + "room_id": "room_1001", + "command_id": "join_10001_room_1001_1780300000000", + "role": "audience", + "password": "", + "response_mode": "full" +} +``` + +### 返回值 + +`response_mode=full` 和 `response_mode=lite` 均会在进房成功响应的 `data` 中返回当前用户的进房 VIP 快照: + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_join_xxx", + "data": { + "result": { + "applied": true, + "room_version": 18, + "server_time_ms": 1780300000000 + }, + "room": {}, + "viewer": {}, + "seats": [], + "profiles": [], + "im": { + "group_id": "room_1001", + "need_join_group": true + }, + "rtc": {}, + "effective_vip": { + "program_type": "tiered_privilege_v1", + "level": 8, + "name": "VIP8", + "room_entry_notice_enabled": true + }, + "server_time_ms": 1780300000000 + } +} +``` + +`effective_vip` 是当前进房用户在本次 Join 时的轻量快照,可用于本人首屏。完整会员状态和权益集仍以 `/vip/me.state` 为准。 + +### 相关 IM + +进房成功后只向**当前房间的腾讯 IM 群**发送 `TIMCustomElem`,不发全服群: + +```json +{ + "Ext": "room_system_message", + "Desc": "room_user_joined", + "Data": "{\"event_id\":\"evt_join_xxx\",\"room_id\":\"room_1001\",\"event_type\":\"room_user_joined\",\"actor_user_id\":10001,\"target_user_id\":10001,\"room_version\":18,\"role\":\"audience\",\"nickname\":\"Nina\",\"avatar\":\"https://cdn.example/avatar.png\",\"display_user_id\":\"100042\",\"pretty_display_user_id\":\"888042\",\"vip_program_type\":\"tiered_privilege_v1\",\"effective_vip_level\":\"8\",\"effective_vip_name\":\"VIP8\",\"room_entry_notice_enabled\":\"true\",\"attributes\":{\"role\":\"audience\",\"nickname\":\"Nina\",\"avatar\":\"https://cdn.example/avatar.png\",\"display_user_id\":\"100042\",\"pretty_display_user_id\":\"888042\",\"vip_program_type\":\"tiered_privilege_v1\",\"effective_vip_level\":\"8\",\"effective_vip_name\":\"VIP8\",\"room_entry_notice_enabled\":\"true\"}}" +} +``` + +`effective_vip_level` 和 `room_entry_notice_enabled` 在 Data 中是字符串,Flutter 需做兼容解析。 + +### 场景处理 + +- 当前用户进房成功后,可直接用 HTTP `data.effective_vip` 初始化本人进房效果;其他成员的进房展示仍以房间 IM 为准。 +- 仅当 `event_type == "room_user_joined"` 且 `room_entry_notice_enabled == "true"` 时显示 VIP 进房高亮效果。 +- 消息所在 IM GroupID 就是当前 `room_id`;不要转发到 App 全局播报群。 +- 用 `event_id` 去重。重连/重复 Join 仍可收到进房展示事件,不要以本地 presence 猜测是否展示。 +- 进房 VIP 查询失败时后端会降级为普通进房,不阻断进房主链路。 + +## 10. VIP 通知开关 + +### 接口地址 + +- `GET /api/v1/vip/settings` +- `PATCH /api/v1/vip/settings` + +`GET /api/v1/vip/me` 的 `data.state.user_settings` 与 GET 接口是同一份事实。VIP 页面已经拉取 `/vip/me` 时,不必再为展示开关多发一次请求。 + +### 参数 + +GET 无参数。PATCH 是局部更新,两个字段至少传一个;显式 `false` 不能被当成未传: + +```json +{ + "room_entry_notice_enabled": false +} +``` + +```json +{ + "online_global_notice_enabled": true +} +``` + +Flutter 不传 `app_code` 和 `user_id`,后端只从登录态解析。空对象 `{}` 返回 `INVALID_ARGUMENT`。 + +### 返回值 + +GET: + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_settings_get", + "data": { + "settings": { + "app_code": "fami", + "user_id": 10001, + "room_entry_notice_enabled": true, + "online_global_notice_enabled": false, + "updated_at_ms": 1780300000000 + }, + "evaluated_at_ms": 1780300001000 + } +} +``` + +PATCH: + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_settings_patch", + "data": { + "settings": { + "app_code": "fami", + "user_id": 10001, + "room_entry_notice_enabled": false, + "online_global_notice_enabled": false, + "updated_at_ms": 1780300010000 + }, + "server_time_ms": 1780300010000 + } +} +``` + +用户从未修改过时,两项默认为 `true`,`updated_at_ms=0`。PATCH 只改本次提交的字段,不会覆盖另一项。 + +### 相关 IM + +修改开关本身不发 IM。进房开关会在下一次 Join 时影响当前房间 `room_user_joined`;上线开关会在触发 `/vip/online-notice` 时由服务端再校验。 + +### 场景处理 + +- 开关是用户偏好,不是权益;无 VIP 时也可保存,但不会产生进房或上线播报。 +- 进房展示必须同时满足 `effective_benefits` 含 `room_entry_notice` 且 `room_entry_notice_enabled=true`。 +- 上线展示必须同时满足 `online_global_notice` 权益和开关;最终以触发接口的服务端判定为准。 +- 设置按 App 隔离,Fami 的修改不影响 Lalu。 + +## 11. 每日 VIP 金币返现 + +### 接口地址 + +- `GET /api/v1/vip/coin-rebates/current`:查当前 UTC 日资格。 +- `POST /api/v1/vip/coin-rebates/statuses`:批量恢复系统消息中的真实状态,或分页查历史。 +- `POST /api/v1/vip/coin-rebates/{rebate_id}/claim`:手动领取。 +- `GET /api/v1/messages?section=system&page_size=20&page_token=`:查“VIP金币返现”系统消息。 + +### 参数 + +current 无 query/body。statuses 有两种用法: + +```json +{ + "rebate_ids": ["vip_rebate_xxx", "vip_rebate_yyy"], + "page": 1, + "page_size": 20 +} +``` + +`rebate_ids` 最多 100 个,后端只返回当前登录用户拥有的记录。传空数组或不传时使用 `page/page_size` 查历史,`page_size` 最大 100。 + +claim: + +```json +{ + "command_id": "vip_rebate_claim_10001_20260710" +} +``` + +`rebate_id` 只来自 current/statuses/系统消息。Flutter 不传金额、VIP 等级、`app_code` 或 `user_id`。 + +### 返回值 + +current 已生成资格: + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_rebate_current", + "data": { + "found": true, + "rebate": { + "rebate_id": "vip_rebate_xxx", + "user_id": "10001", + "task_day": "2026-07-10", + "vip_level": 6, + "vip_name": "VIP6", + "coin_amount": 100, + "status": "claimable", + "available_at_ms": 1783641600000, + "expires_at_ms": 1783728000000, + "config_version": 8, + "claimed_at_ms": 0, + "wallet_transaction_id": "", + "created_at_ms": 1783641605000, + "updated_at_ms": 1783641605000 + }, + "server_time_ms": 1783641610000 + } +} +``` + +无资格或 cron 尚未生成时也是成功响应: + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_rebate_none", + "data": { + "found": false, + "rebate": null, + "server_time_ms": 1783641610000 + } +} +``` + +statuses: + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_rebate_statuses", + "data": { + "rebates": [ + { + "rebate_id": "vip_rebate_xxx", + "user_id": "10001", + "task_day": "2026-07-10", + "vip_level": 6, + "vip_name": "VIP6", + "coin_amount": 100, + "status": "claimable", + "available_at_ms": 1783641600000, + "expires_at_ms": 1783728000000, + "config_version": 8, + "claimed_at_ms": 0, + "wallet_transaction_id": "", + "created_at_ms": 1783641605000, + "updated_at_ms": 1783641605000 + } + ], + "total": 1, + "server_time_ms": 1783641610000 + } +} +``` + +claim: + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_rebate_claim", + "data": { + "rebate": { + "rebate_id": "vip_rebate_xxx", + "user_id": "10001", + "task_day": "2026-07-10", + "vip_level": 6, + "vip_name": "VIP6", + "coin_amount": 100, + "status": "claimed", + "available_at_ms": 1783641600000, + "expires_at_ms": 1783728000000, + "config_version": 8, + "claimed_at_ms": 1783641700000, + "wallet_transaction_id": "wallet_tx_xxx", + "created_at_ms": 1783641605000, + "updated_at_ms": 1783641700000 + }, + "transaction_id": "wallet_tx_xxx", + "coin_balance": { + "asset_type": "COIN", + "available_amount": 20100, + "frozen_amount": 0, + "version": 18 + }, + "server_time_ms": 1783641700000 + } +} +``` + +`status` 只处理 `claimable` / `claimed` / `expired`。`expired` 是 wallet 根据当前 UTC 时间的投影,不要仅凭手机时钟自行改状态。 + +### 相关 IM + +返现可领取通知进入后端 system inbox,不新增腾讯云 `TIMCustomElem`。`GET /messages?section=system` 的条目示例: + +```json +{ + "message_id": "msg_vip_rebate_xxx", + "title": "VIP金币返现", + "summary": "尊敬的VIP用户,平台今日返现100金币,请及时领取。", + "body": "尊敬的VIP用户,平台今日返现100金币,请及时领取。", + "icon_url": "", + "image_url": "", + "action_type": "vip_coin_rebate_claim", + "action_param": "{\"coin_amount\":100,\"expires_at_ms\":1783728000000,\"rebate_id\":\"vip_rebate_xxx\",\"task_day\":\"2026-07-10\"}", + "read": false, + "sent_at_ms": 1783641600000 +} +``` + +Flutter 先把 `action_param` 再解析为 JSON。点击前可将页面上所有 `rebate_id` 合并调 statuses,用后端状态刷新“去领取/已领取/已过期”。 + +### 场景处理 + +- 资格按 UTC 0 点时的有效付费 VIP 还原;0 点后才购买或升级的状态从次日参与,体验卡不参与。 +- 领取窗口是 `[UTC 当日0点,UTC 次日0点)`。到次日0点后服务端返回 `VIP_COIN_REBATE_EXPIRED`,不再入账。 +- 金额取当日首个成功 cron run 固化的后台配置;同一 run 不会因运营中途改价而分裂。 +- 领取按钮要防连点,网络重试复用同一 `command_id`。同一返现换新 `command_id` 重复提交也只返回原成功回执,不会重复加币。 +- 领取成功后用 `data.coin_balance` 替换 COIN 余额快照,不在 Flutter 本地做 `balance += coin_amount`。 +- 记录不存在或不属于当前用户时返回 `VIP_COIN_REBATE_NOT_FOUND`;复用一个 `command_id` 改投另一笔返现时返回 `IDEMPOTENCY_CONFLICT`。 + +## 12. 上线全服通知 + +### 接口地址 + +`POST /api/v1/vip/online-notice` + +调用前先完成 `GET /api/v1/im/usersig` 的 IM 登录,并按 `join_groups` 加入 `type=global_broadcast` 的全局播报群。Flutter 只请求后端发送,不向全局群自行发消息。 + +### 参数 + +```json +{ + "command_id": "vip_online_550e8400-e29b-41d4-a716-446655440000_2026-07-10" +} +``` + +`command_id` 最长 128,用“进程启动唯一 ID + UTC 日期”生成。同一进程当日网络重试必须复用同一值。 + +### 返回值 + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_online_notice", + "data": { + "event_id": "vip_online_notice:10001:sha256_digest", + "group_id": "hy_fami_bc_g_v2", + "status": "pending", + "created": true, + "evaluated_at_ms": 1783641610000 + } +} +``` + +`status=pending` 表示已持久化到 activity 播报 outbox,腾讯云 IM 由 worker 异步投递。相同 `command_id` 重试返回同一 `event_id`,`created=false` 是幂等命中,不是失败。 + +### 相关 IM + +全局播报群下发 `TIMCustomElem`: + +| 字段 | 值 | +|---|---| +| `Ext` | `im_broadcast` | +| `Desc` | `vip_online_notice` | +| `Data` | 下方 JSON 字符串 | +| `CloudCustomData` | 与 `Data` 相同的 JSON 字符串 | + +```json +{ + "event_id": "vip_online_notice:10001:sha256_digest", + "broadcast_type": "vip_online_notice", + "scope": "global", + "app_code": "fami", + "user_id": 10001, + "sender_user_id": 10001, + "nickname": "Nina", + "avatar": "https://cdn.example/avatar.png", + "avatar_frame_url": "https://cdn.example/frame.png", + "avatar_frame_resource_id": 108, + "avatar_frame_code": "frame_vip9", + "avatar_frame_asset_url": "https://cdn.example/frame.png", + "avatar_frame_preview_url": "https://cdn.example/frame-preview.png", + "avatar_frame_animation_url": "https://cdn.example/frame-animation.svga", + "display_user_id": "888042", + "raw_display_user_id": "100042", + "pretty_display_user_id": "888042", + "vip_program_type": "tiered_privilege_v1", + "vip_level": 9, + "vip_name": "VIP9", + "message": "欢迎进入Fami,祝你有美好的一天", + "action": { + "type": "none" + }, + "sent_at_ms": 1783641610500 +} +``` + +`display_user_id` 优先美号,无美号时回落 `raw_display_user_id`。`avatar_frame_url` 是兼容字段,固定等于 `avatar_frame_asset_url`;无当前佩戴头像框时 ID 为 `0`、URL/code 为空字符串。用户资料、VIP 等级和头像框都由服务端取当前事实,Flutter 不能用请求伪造。用 `event_id` 做会话内去重。 + +### 场景处理 + +- 在进程内保存 `process_boot_id`、当前 UTC 日和该日 `command_id`,不写 SharedPreferences/数据库/磁盘文件。 +- 同进程同 UTC 日首次进入 App,且 `/vip/me` 中具有 `online_global_notice` 资格、`online_global_notice_enabled=true` 时触发。成功后当日不再产生新 command。 +- 超时/5xx 可用同一 `command_id` 重试;不能在同进程同日换 command 规避幂等。 +- 杀掉进程后内存清空,新进程产生新 `process_boot_id` 和 `command_id`,同日可再播一次,这是已确认产品规则。 +- 权益过期、后台停用、体验卡不允许或用户关闭开关时,服务端返回 `PERMISSION_DENIED`;Flutter 不做本地飘屏,可重拉 `/vip/me`。 +- 不把“每日一次”放到服务端持久化去重,否则无法满足杀进程后同日可再展示。 + +## 13. 权益场景和完成边界 + +| 场景 | Flutter 处理 | 当前后端状态 | +|---|---|---| +| App 维度 VIP 体系 | 始终渲染 `program_config` 和接口返回的动态等级,不按 Fami/Lalu 写死业务分支 | 已强制。Fami 为 `tiered_privilege_v1`,Lalu 为 `legacy_timed` | +| Fami 高级升级 | 直接展示购买响应的新截止时间 | 已强制,旧低级剩余时间丢弃 | +| 同级续期 | 不在本地计算,以响应为准 | 已强制,从旧截止时间累加 | +| 体验卡自由切换 | 按 `entitlement_id` 佩戴,每次直接替换全局 `state` | 已强制,不限等级,不作废旧卡,不改绝对截止时间 | +| 体验卡权益 | 只渲染 `effective_benefits` 资格 | 已强制过滤 `trial_enabled=false`,金币返现不给体验卡 | +| VIP 通知开关 | 读 `state.user_settings`,用 PATCH 局部修改 | 已按 App 持久化,无记录默认两项开启,开关不单独授权 | +| 进房高亮通知 | 本人首屏读 Join HTTP `effective_vip`,其他成员消费当前房间 `room_user_joined` | 后端已同时校验权益与用户开关,并透传 Join HTTP 和当前房间 IM;不发全服 | +| 防踢 | 房管操作返回 `PERMISSION_DENIED` 时显示不可踢,不做本地预判 | 已在 room-service 强制;只阻止普通房主/管理员的 KickUser | +| 防禁言 | 房管操作返回 `PERMISSION_DENIED` 时显示不可禁言,解禁始终可发起 | 已在 room-service 强制;只阻止普通房主/管理员新增禁言 | +| 平台封禁/风控/超级管理 | Flutter 不作 VIP 免责提示 | 独立 `SystemEvictUser` 治理链路会绕过防踢,VIP 不能绕过平台治理 | +| 自定义房间背景 | 按现有房间背景页面调用 | `tiered_privilege_v1` 已由 room-service 强制校验 `custom_room_background` | +| 金币返现 | 消费 system inbox,批量恢复状态,手动领取后使用响应余额 | 已完成 UTC 日资格、异步系统消息、24 小时窗口和 wallet 原子入账;体验卡不参与 | +| 上线全服通知 | 按进程内 UTC 日规则请求后端,消费全局播报群 `vip_online_notice` | 已完成权益/开关校验、服务端资料与头像框、activity outbox 和腾讯云 IM;服务端不做日级去重 | +| 其他 VIP 装扮/功能 | 可根据 `effective_benefits` 展示锁定/解锁态;有素材时使用后端 URL | 权益矩阵已配置,但不等于各 owner 均已强制。Fami 初始 `resource_id=0`、素材为空,未绑定 owner/资源前不能宣称已实现 | + +## 14. Flutter 建议状态流 + +1. 进 VIP 页并发请求 `/vip/packages` 和 `/vip/me`,套餐用前者,用户生效状态用后者。 +2. 购买成功、佩戴成功、卸下成功时,立即用响应中的 `state` 替换全局 VIP store。 +3. App 冷启动、登录成功、回前台以及收到 `vip_notice` 后调 `/vip/me` 校准;同时更新 `user_settings`。 +4. 背包页用 `vip_trial_card` 列表展示每张独立卡;当前佩戴态以 `/vip/me.state.equipped_trial_card.entitlement_id` 为准。 +5. 所有房间、个人页和消息展示都使用 `effective_vip`,不要用 `paid_vip` 覆盖体验卡展示。 +6. system inbox 遇到 `action_type=vip_coin_rebate_claim` 时,批量用 statuses 恢复真实状态;领取成功后替换余额与返现状态。 +7. IM 登录并加入全局播报群后,按进程内 UTC 日规则触发 `/vip/online-notice`;收到 `vip_online_notice` 时用 `event_id` 去重。 +8. 修改通知开关成功后,用 PATCH 响应替换本地 `user_settings`,不修改 `effective_benefits`。 diff --git a/docs/Outbox统一治理说明.md b/docs/Outbox统一治理说明.md index 8fdd85cb..a3d88aac 100644 --- a/docs/Outbox统一治理说明.md +++ b/docs/Outbox统一治理说明.md @@ -1,6 +1,6 @@ # Outbox 统一治理说明 -更新时间:2026-06-01 +更新时间:2026-07-11 ## 背景 @@ -43,6 +43,7 @@ - owner service 继续负责自己的 outbox claim 和 MQ 发布: - `wallet-service` 发布 `hyapp_wallet_outbox` - `room-service` 发布 `hyapp_room_outbox` + - `lucky-gift-service` 在钱包返奖和本地 granted 收敛后发布 `hyapp_lucky_gift_outbox` - `activity-service` 发布 activity / broadcast 相关 MQ - `game-service` 发布 game 相关 MQ - `user-service` 发布 `hyapp_user_outbox` @@ -54,6 +55,63 @@ - `delivering/running expired lock` - claim 查询全部显式带 `app_code`,并匹配对应索引。 - delivered/done 历史 outbox 通过归档脚本搬到 archive 表。 +- wallet/room topic 使用可灰度的 typed Tag:consumer 先部署 + `legacy || relevant event_type`,producer 再从 `legacy` 切到 `event_type`。 +- 同步 MQ publisher 每次只 claim 一条 outbox;`batch_size` 表示单轮最多处理 + 条数,不再表示一次提前锁住的行数,避免批尾 lease 在真正发送前过期。 + +## Typed Tag 无损切换 + +Typed Tag 只降低无关 consumer group 的投递、反序列化和 handler 调用量,不会 +减少 Broker ingress 或存储。礼物消息总量必须在写入 owner outbox 前通过送礼微批 +收敛;多人礼物仍按 target 保留各自 `WalletGiftDebited`、`RoomGiftSent` 事实,且 +每条事实携带聚合后的 `gift_count`。 + +切换顺序必须严格执行: + +1. 先滚动发布所有 wallet consumer,确认订阅表达式包含 + `wallet_outbox_event || <相关 event_type>`,且同一 group 所有副本表达式一致。 +2. 确认旧 consumer 实例归零、消费错误率稳定,再仅把 wallet-service + `rocketmq.wallet_event_tag_mode` 从 `legacy` 改为 `event_type`。 +3. 保留 consumer 的 legacy 分支,直到切换前积压、retry 和计划内 replay 窗口全部 + 排空;不能在 producer 切换同一批发布中移除 legacy。 +4. wallet 稳定后,以同样顺序先发布 activity/statistics/notice/user 等 room + consumer,再把 room-service `rocketmq.room_event_tag_mode` 从 `legacy` 改为 + `event_type`。 +5. 如需回滚 producer,只改回 `legacy`;兼容 consumer 无需回滚。只有确认 Broker + 已无 legacy 消息且不再重放旧 outbox 后,才可以另行删除 legacy selector。 + +推荐验证: + +```text +Phase A: producer tag=legacy;每个 consumer group 仍能消费原有事实 +Phase B: gift typed tag 只进入需要 gift 的 group;首充/累充/邀请等 group lag 不再随礼物增长 +Rollback: producer 改回 legacy 后所有兼容 consumer 继续消费,无消息不可见窗口 +``` + +Topic 拆分不是降低消息总量的手段。完成微批和 typed Tag 后,只有在礼物流量仍 +持续挤压非礼物 SLO,或确实需要独立 retention/retry/ACL/顺序策略时才拆高频 Topic; +流量占比只能作为评审信号,不能把固定百分比当作行业标准。 + +### 历史礼物消费修复 + +新代码只阻止新增的 `WalletBalanceChanged(direct_gift_debit)` 与 `RoomGiftSent` +双计;已经落入 `stat_app_day_country.consumed_coin` 的历史窗口不能直接拍脑袋减值。 +上线后使用现有 `replay-stat-tz` 从四个 owner outbox 重建受影响窗口,并分别执行 +UTC 与 Asia/Shanghai 两套口径。先不带 `--apply` 核对 source_rows,再在停止该 +窗口实时写入或只选择已完成自然日时执行 apply: + +```bash +go run ./services/statistics-service/cmd/replay-stat-tz \ + --app lalu --timezone UTC --start-day 2026-07-01 --end-day 2026-07-10 + +go run ./services/statistics-service/cmd/replay-stat-tz \ + --app lalu --timezone Asia/Shanghai --start-day 2026-07-01 --end-day 2026-07-10 +``` + +确认四类 owner source_rows 非零且窗口正确后,为同一命令补充 `--apply` 和运行时 +DSN 环境变量。每个 App 独立执行;完成后校验 `consumed_coin` 中礼物部分等于 +`RoomGiftSent.coin_spent`,钱包礼物扣款只继续影响 `coin_total`。 ## 修复掉的问题 diff --git a/docs/VIP系统架构.md b/docs/VIP系统架构.md index ca920b48..5b82fe60 100644 --- a/docs/VIP系统架构.md +++ b/docs/VIP系统架构.md @@ -1,462 +1,182 @@ -# VIP 系统架构设计 - -## 目标 - -VIP 是用户可直接用金币购买的限时会员权益。每个 VIP 等级都可以单独购买,单次购买有效期固定 7 天,不同等级价格不同。购买成功后,系统根据后台配置的资源组发放头像框、座驾、气泡、徽章、礼物等权益。 - -首版只做当前业务真实需要的能力: - -- 用户可以购买任意有效 VIP 等级。 -- 用户可以从低等级升级到高等级。 -- 用户当前 VIP 未过期时,不能购买比当前等级低的 VIP。 -- 同等级重复购买按续期处理。 -- VIP 扣金币、会员状态变更、资源组发放必须在 `wallet-service` 内形成一致账务闭环。 -- 后台只配置 VIP 等级、金币价格、有效期和赠送资源组;客户端不能传价格或资源明细。 +# 可配置 VIP 系统 -## 服务边界 +## 目标与边界 -VIP 属于 `wallet-service` 的账务和权益域,不放在 `user-service`。 +VIP 是 `wallet-service` 按 `app_code` 隔离的会员与权益域。每个 App 通过一份 `vip_program_configs` 选择等级数量、购买有效期策略、后台发放模式和体验卡开关;gateway、room、admin 和客户端只能消费这份显式配置,不得通过 `app_code` 写第二套状态机。 -原因: +- `wallet-service`:会员、体验卡、用户功能开关、权益矩阵、购买扣费、每日返现、资源发放、历史和 outbox 的 owner。 +- `gateway-service`:App HTTP 鉴权、协议转换和钱包 gRPC 编排。 +- `room-service`:只执行房间权限并通过腾讯云 IM 投递当前房间事件,不能拥有会员状态。 +- `server/admin`:配置 program、等级和每级完整权益,发放真实 VIP 或体验卡。 +- Flutter:展示后端状态、操作背包佩戴并处理客户端生命周期效果;不参与购买规则计算。 -- 购买 VIP 必须先扣金币,扣费成功才允许生成 VIP 状态和权益。 -- 购买 VIP 会发放后台资源组,资源组、资源权益和钱包资产已经归 `wallet-service` 管。 -- 如果 VIP 状态放在 `user-service`,扣费、会员状态、资源发放会跨服务,需要分布式事务或补偿,复杂度和失败窗口都更大。 - -服务职责: +全链路业务时间使用 UTC epoch milliseconds。每日结算或刷新按 UTC 0 点切日;用户本地时间只用于展示。 -- `gateway-service`:提供 App HTTP API,做鉴权、参数校验、生成 `request_id`、透传客户端 `command_id`,调用 `wallet-service`。 -- `wallet-service`:拥有 VIP 等级配置、用户 VIP 状态、VIP 购买订单、金币扣费、资源组发放、VIP 事件 outbox。 -- `user-service`:只在用户资料聚合时读取 VIP 摘要,不拥有 VIP 状态。 -- `room-service`:需要展示 VIP 标识时读取用户资料/VIP 投影,不参与 VIP 购买主链路。 -- `server/admin`:后台配置 VIP 等级和资源组,具体后台接口单独设计。 +## 当前 App 策略 -## 核心模型 +| 配置 | Lalu | Fami | +| --- | --- | --- | +| `program_type` | `legacy_timed` | `tiered_privilege_v1` | +| `level_count` | 10 | 9 | +| 同级购买 | 从当前截止时间续期 | 从当前截止时间续期 | +| 高级升级 | 保留剩余时间后追加新时长 | 立即替换为高级,从购买时刻重新计时,旧等级剩余时间丢弃 | +| 低级购买 | 拒绝 | 拒绝 | +| 后台发放 | `direct_membership`,直接写真实 VIP | `trial_card`,发到背包后由用户佩戴 | +| 体验卡 | 关闭 | 开启 | +| 权益集合 | 每级显式保存 | 每级显式保存完整集合,不按 `unlock_level` 自动继承 | -### VIP 等级 +Fami 的 VIP1–VIP9 初始全部为 `disabled`。30 天只是不产生零时长脏配置的占位值;价格和资源组保持 0,运营完成配置并启用前不可购买或发卡。 -`vip_levels` +## 数据模型 -| 字段 | 说明 | -| -------------------------- | -------------------------------------------------- | -| `id` | 内部主键 | -| `level` | VIP 等级,数字越大等级越高 | -| `name` | 展示名称,例如 VIP1、VIP2 | -| `coin_price` | 购买该等级需要消耗的金币数 | -| `duration_ms` | 有效期,首版固定 `7 * 24 * 60 * 60 * 1000` | -| `reward_resource_group_id` | 购买成功后发放的资源组 | -| `status` | `draft` / `active` / `disabled` | -| `sort_order` | App 展示排序 | -| `display_config_json` | 展示配置快照,例如颜色、权益说明、角标,不参与结算 | -| `created_at_ms` | 创建时间 | -| `updated_at_ms` | 更新时间 | +### App 级 program -约束: +`vip_program_configs` 是行为规则的唯一来源: -- `level` 唯一。 -- `coin_price > 0`。 -- `duration_ms` 首版必须等于 7 天,不允许后台临时改成其它值。 -- `active` 状态必须绑定有效资源组。 +- `program_type` +- `level_count` +- `same_level_expiry_policy` +- `upgrade_expiry_policy` +- `downgrade_purchase_policy` +- `benefit_inheritance_policy`,当前固定 `target_only` +- `grant_mode` +- `trial_card_enabled` +- `status` +- `config_version` -### 用户 VIP 当前状态 +后台修改 program、等级或权益时递增 `config_version`。会员、订单和体验卡保留创建时的 program/version 快照,当前权限判断使用最新有效 program 和等级显式权益集合。 -`user_vip_memberships` +### 等级和权益 -| 字段 | 说明 | -| --------------- | ---------------------------- | -| `user_id` | 用户 ID,唯一 | -| `level` | 当前等级 | -| `status` | `active` / `expired` | -| `started_at_ms` | 当前等级周期开始时间 | -| `expires_at_ms` | 当前 VIP 到期时间 | -| `last_order_id` | 最近一次购买订单 | -| `version` | 乐观版本,配合行锁防并发覆盖 | -| `created_at_ms` | 创建时间 | -| `updated_at_ms` | 更新时间 | +- `vip_levels`:等级名称、状态、金币价格、时长、可选资源组和排序。 +- `vip_level_benefits`:以 `(app_code, level, benefit_code)` 保存该等级最终完整权益集合。 -读取时以 `expires_at_ms > now` 判断是否有效,`status` 只作为后台检索和事件处理的辅助状态。这样即使过期任务延迟,App 也不会把过期 VIP 当成有效权益。 +`unlock_level` 只记录 P1 初始化来源,运行时和后台保存都不能据此给高等级自动补权益。纯功能权益的 `resource_id=0`;装扮权益可以绑定同 App 的实际资源。 -### VIP 购买订单 +### 付费会员 -`vip_purchase_orders` +`user_vip_memberships` 保存用户付费会员事实。有效条件是 `level > 0 && expires_at_ms > now_ms`;不依赖异步过期任务才能失效。 -| 字段 | 说明 | -| ---------------------------- | --------------------------------- | -| `order_id` | VIP 订单号 | -| `command_id` | 客户端生成的幂等命令 ID | -| `user_id` | 购买用户 | -| `target_level` | 目标 VIP 等级 | -| `coin_price_snapshot` | 下单时价格快照 | -| `duration_ms_snapshot` | 下单时有效期快照 | -| `resource_group_id_snapshot` | 下单时资源组快照 | -| `previous_level` | 购买前有效等级,无有效 VIP 则为 0 | -| `previous_expires_at_ms` | 购买前到期时间 | -| `new_level` | 购买后等级 | -| `new_expires_at_ms` | 购买后到期时间 | -| `wallet_transaction_id` | 金币扣费交易 ID | -| `resource_grant_id` | 资源组发放记录 ID | -| `status` | `succeeded` / `failed` | -| `failure_reason` | 失败原因 | -| `request_hash` | 幂等请求摘要 | -| `created_at_ms` | 创建时间 | -| `updated_at_ms` | 更新时间 | - -约束: - -- `(user_id, command_id)` 唯一。 -- 相同 `command_id` + 相同 `request_hash` 返回原订单结果。 -- 相同 `command_id` + 不同 `request_hash` 返回幂等冲突。 - -### VIP 变更历史 - -`user_vip_history` - -记录每次购买、续期、升级、过期,供客服、后台账单、风控排查使用。历史表不参与 App 主链路强查询。 - -核心字段: - -- `history_id` -- `user_id` -- `event_type`: `purchase` / `renew` / `upgrade` / `expire` -- `from_level` -- `to_level` -- `from_expires_at_ms` -- `to_expires_at_ms` -- `order_id` -- `created_at_ms` - -## 购买和升级规则 - -定义: - -- 当前有效 VIP:`membership.expires_at_ms > now && membership.level > 0` -- 目标等级:用户本次购买的 `vip_level` - -规则: - -| 场景 | 处理 | -| ---------------------- | ------------------------------------------------------------------------ | -| 无有效 VIP | 可以购买任意 active 等级,`expires_at_ms = now_ms + 7d_ms` | -| 有效 VIP,购买同等级 | 续期,`expires_at_ms = current_expires_at_ms + 7d_ms` | -| 有效 VIP,购买更高等级 | 立即升级,`level = target_level`,`expires_at_ms = current_expires_at_ms + 7d_ms` | -| 有效 VIP,购买更低等级 | 拒绝,返回 `VIP_DOWNGRADE_NOT_ALLOWED` | -| VIP 已过期 | 按无有效 VIP 处理,可以购买任意 active 等级 | - -首版升级采用“保留剩余时间并升级”的策略:如果用户 VIP1 还剩 3 天,又购买 VIP3,则 VIP3 到期时间为当前 VIP1 到期时间再加 7 天。这个规则实现简单、用户感知清晰,也避免升级时损失已付费时长。 - -如果后续业务希望“补差价升级”或“升级只生效 7 天”,只需要扩展 VIP 等级的升级策略,不改变主链路表结构。 - -## 资源组发放规则 - -购买 VIP 成功后,`wallet-service` 根据 `vip_levels.reward_resource_group_id` 发放资源组。 - -资源组继续复用现有资源组能力: - -- 头像框:`avatar_frame` -- 资料卡:`profile_card` -- 座驾:`vehicle` -- 聊天气泡:`chat_bubble` -- 徽章:`badge` -- 飘屏:`floating_screen` -- 礼物:`gift` -- 金币等钱包资产:仅在明确配置时发放,默认不建议 VIP 购买返金币,避免账务绕圈。 - -VIP 资源组需要增加一个明确约束: - -- VIP 专属装扮类资源默认与 VIP 到期时间对齐。 -- 资源组 item 如果配置了独立 `duration_ms`,后台必须能看到它和 VIP 7 天周期的差异。 -- 首版推荐新增 `duration_policy = inherit_vip_expiry`,购买 VIP 时将资源权益到期时间直接设置为 `new_expires_at_ms`。 - -续期和升级时: - -- 同一资源重复发放时,使用资源权益的 `extend_expiry` 或 `align_to_expiry` 策略,避免重复插入导致用户出现多个同资源有效权益。 -- 升级后只发放目标等级配置的资源组;低等级已获得的资源不立即删除,到期后自然失效。 -- App 展示和自动佩戴时按用户当前有效资源、资源等级和佩戴状态决定,不从 VIP 等级反推资源。 - -## 主链路流程 - -```mermaid -sequenceDiagram - autonumber - participant App - participant Gateway as gateway-service - participant Wallet as wallet-service - participant DB as Wallet MySQL - participant Outbox as Wallet Outbox - - App->>Gateway: POST /api/v1/vip/purchase {level, command_id} - Gateway->>Wallet: PurchaseVip(user_id, level, command_id, request_meta) - Wallet->>DB: BEGIN - Wallet->>DB: 查询并锁定 vip_levels(level) - Wallet->>DB: 查询并锁定 user_vip_memberships(user_id) - Wallet->>DB: 校验不能降级、计算 new_expires_at_ms - Wallet->>DB: 锁定用户 COIN 钱包账户 - Wallet->>DB: 扣减金币,写 wallet_transaction / wallet_entries - Wallet->>DB: 发放 VIP 资源组,写 resource_entitlements - Wallet->>DB: upsert user_vip_memberships - Wallet->>DB: 写 vip_purchase_orders / user_vip_history - Wallet->>Outbox: 写 VipPurchased / VipUpgraded / VipRenewed - Wallet->>DB: COMMIT - Wallet-->>Gateway: 返回当前 VIP、扣费、权益摘要 - Gateway-->>App: {code,message,request_id,data} -``` - -关键点: - -- 金币扣费、VIP 状态、资源组发放必须在同一个 `wallet-service` 数据库事务中完成。 -- 客户端只传 `level` 和 `command_id`,价格和资源组完全由服务端配置决定。 -- 扣费成功但资源发放失败不能提交事务;否则用户会付费但拿不到权益。 -- 资源发放成功但 VIP 状态更新失败也不能提交事务;否则权益和会员状态会不一致。 - -## App 一级接口 - -### 查询 VIP 等级 - -`GET /api/v1/vip/levels` - -返回: - -```json -{ - "current_vip": { - "level": 2, - "name": "VIP2", - "expires_at_ms": 1760000000000, - "remaining_ms": 604800000 - }, - "levels": [ - { - "level": 1, - "name": "VIP1", - "coin_price": 1000, - "duration_ms": 604800000, - "can_purchase": true, - "purchase_block_reason": "", - "benefits": [ - { - "resource_type": "avatar_frame", - "resource_id": "vip1_frame", - "name": "VIP1 Avatar Frame", - "duration_policy": "inherit_vip_expiry" - } - ] - } - ] -} -``` - -说明: - -- `can_purchase=false` 通常表示当前有效 VIP 等级高于该等级。 -- App 可以展示所有等级,但购买按钮状态以服务端返回为准。 - -### 查询我的 VIP - -`GET /api/v1/vip/me` - -返回: - -```json -{ - "level": 2, - "name": "VIP2", - "status": "active", - "expires_at_ms": 1760000000000, - "remaining_ms": 604800000, - "renewable": true, - "upgradeable_levels": [3, 4, 5] -} -``` - -说明: - -- 底部“我的”页面只需要拿 VIP 摘要,不要拉完整资源列表。 -- 完整装扮资源仍从资源/背包接口查询。 - -### 购买 VIP - -`POST /api/v1/vip/purchase` - -请求: - -```json -{ - "level": 3, - "command_id": "client-generated-id" -} -``` - -返回: - -```json -{ - "order_id": "vip_order_123", - "event_type": "upgrade", - "coin_spent": 3000, - "vip": { - "level": 3, - "name": "VIP3", - "expires_at_ms": 1760604800000 - }, - "granted_resources": [ - { - "resource_type": "vehicle", - "resource_id": "vip3_vehicle", - "expires_at_ms": 1760604800000 - } - ] -} -``` +购买事务同时完成: -## gRPC 契约建议 - -新增 `wallet.v1.VipService`: - -```protobuf -service VipService { - rpc ListVipLevels(ListVipLevelsRequest) returns (ListVipLevelsResponse); - rpc GetMyVip(GetMyVipRequest) returns (GetMyVipResponse); - rpc PurchaseVip(PurchaseVipRequest) returns (PurchaseVipResponse); -} -``` - -核心字段: - -- `RequestMeta meta` -- `int64 user_id` -- `int32 level` -- `string command_id` -- `int64 now_ms` 不由客户端传,由服务端生成;测试可通过 clock 注入。 +1. 锁定 program、目标等级和当前会员; +2. 根据 program 计算有效期,拒绝降级; +3. 锁定 COIN 账户并扣费; +4. 发放目标资源组; +5. 更新会员,写订单、历史和 wallet outbox; +6. 一次事务提交。 -错误码建议纳入 `pkg/xerr` catalog: +`command_id` 是购买幂等键,`request_id` 仅用于链路追踪。 -- `VIP_LEVEL_NOT_FOUND` -- `VIP_LEVEL_DISABLED` -- `VIP_DOWNGRADE_NOT_ALLOWED` -- `VIP_PRICE_CHANGED`,仅在未来支持客户端价格确认时使用 -- `WALLET_INSUFFICIENT_BALANCE` -- `IDEMPOTENCY_CONFLICT` +`vip_command_locks` 在任何会员、账户或资源业务锁之前串行化 `(app_code, command_id)`。购买、直接会员赠送和体验卡发放共享该守卫空间;同命令并发重试会等待原事务提交后读取稳定回执,三种业务也不能交叉复用同一命令号。 -gRPC status 仍保持少量通用 code: +### 体验卡 -- 参数错误:`InvalidArgument` -- 余额不足、不能降级:`FailedPrecondition` -- 等级不存在或禁用:`NotFound` / `FailedPrecondition` -- 幂等冲突:`AlreadyExists` -- 内部事务失败:`Internal` +体验卡使用三层事实: -业务错误放在 `google.rpc.ErrorInfo.reason`。 +- `resources(resource_type=vip_trial_card)`:卡面与等级元数据; +- `user_resource_entitlements`:背包实例和绝对有效期; +- `user_resource_equipment`:当前单选佩戴指针; +- `user_vip_trial_cards`:等级、来源、时长和 program/version 快照。 -## 并发和幂等 +发卡时即写入 `effective_at_ms` 和 `expires_at_ms`,但不自动佩戴。切换卡片只替换 `user_resource_equipment` 指针,不修改、删除、暂停或延长任一卡片。 -同一用户可能快速点击多次购买或同时升级,必须按用户串行化处理。 +例如 A=14 天、B=20 天、C=30 天:佩戴 A 后立刻切 B,B 生效,A 仍保留原绝对到期时间;之后只要 A 未过期即可重新佩戴。体验卡等级不受当前付费 VIP 或上一张体验卡等级限制。 -事务内锁顺序固定: +### 用户功能开关 -1. 锁 `vip_levels(level)`,读取配置快照。 -2. 锁 `user_vip_memberships(user_id)`。 -3. 锁用户 `COIN` 钱包账户。 -4. 写订单、流水、权益、会员状态。 +`user_vip_settings` 按 `(app_code,user_id)` 保存两个用户偏好: -如果 `user_vip_memberships` 不存在,先插入一行占位再锁,或者用用户级 advisory lock。不要只依赖应用内 mutex,因为多实例部署时无效。 +- `room_entry_notice_enabled`:进房 VIP 高亮通知; +- `online_global_notice_enabled`:上线 VIP 全服飘屏。 -幂等规则: +无记录时两项默认为 `true`,首次修改才写行。偏好不授予权益:无 VIP 或对应等级没有权益时,即使开关为开也不会放行。用户可在无 VIP、VIP 过期或切换体验卡时保留偏好;Fami 与 Lalu 互不影响。 -- `command_id` 是购买命令幂等键,不使用 `request_id`。 -- 已成功订单重复请求直接返回原结果。 -- 已失败订单是否允许重试取决于失败类型:余额不足可以用新 `command_id` 重试;系统错误如果事务未提交,可以继续用原 `command_id` 重试。 -- 订单内保存价格、资源组、有效期快照,后台改配置不影响已成功订单解释。 +### 每日金币返现 -## VIP 过期 +`daily_coin_rebate` 的 `metadata_json.coin_amount` 是该等级每日可领金币正整数。只有启用的付费 VIP 参与,体验卡即使历史配置误开 `trial_enabled` 也会被 wallet 二次排除。 -VIP 是否有效以 `expires_at_ms` 为准。过期处理分两层: +每个 App 每个 UTC 日期只有一个 `vip_daily_coin_rebate_runs`: -- App 查询时实时判断,过期立即返回 `status=expired`。 -- 后台定时任务扫描过期会员,写 `user_vip_history(expire)` 和 `VipExpired` outbox,用于消息通知、缓存清理和搜索投影更新。 +1. 首个成功批次固化当时的 `config_version` 和等级金额矩阵;后续分页不重读新配置。 +2. wallet 根据会员历史还原 UTC 0 点时的付费 VIP;0 点后才购买或升级的状态从次日起参与。 +3. 生成 `vip_daily_coin_rebates` 资格,领取窗口固定为 `[UTC 当日0点,UTC 次日0点)`。 +4. 同事务写 `VipDailyCoinRebateAvailable` outbox;activity-service 幂等投影为“VIP金币返现”系统消息。 +5. 用户手动领取时,返现行锁、COIN 余额、交易、分录和 `WalletBalanceChanged` outbox 在一个事务内提交。 -资源权益也以自己的 `expires_at_ms` 判断有效性。如果使用 `inherit_vip_expiry`,VIP 到期后头像框、座驾等权益自然失效,不需要额外删除。 +“日切资格”严格按 UTC 0 点;“金额配置”是当日首个成功 run 的快照,不伪造系统尚未保存的 0 点历史配置。正常情况下 cron 每分钟触发;如零点后 wallet/cron 长时间不可用,恢复前的后台金额修改会进入当日快照。运营若要保证修改次日生效,应在当日 run 已创建后修改。 -## Outbox 事件 +## 最终生效状态 -`wallet-service` 写出以下事件: - -- `VipPurchased` -- `VipRenewed` -- `VipUpgraded` -- `VipExpired` +所有调用方统一消费 `VipState`: -事件内容: +- `paid_vip`:真实购买会员; +- `equipped_trial_card`:当前有效且已佩戴的体验卡; +- `effective_vip`:最终展示和权限使用的 VIP; +- `effective_source`:`paid`、`trial` 或 `none`; +- `effective_benefits`:最终身份拥有的权益资格;可关闭的通知类权益还要与 `user_settings` 做 AND; +- `program_config`:当前 App 规则; +- `user_settings`:当前 App 下的进房/上线通知偏好。 -- `event_id` -- `user_id` -- `order_id` -- `from_level` -- `to_level` -- `expires_at_ms` -- `coin_spent` -- `resource_group_id` -- `occurred_at_ms` +program 启用时的优先级固定为:`trial_card_enabled=true` 且体验卡有效并已佩戴时覆盖展示;否则使用有效付费会员;二者都无效则为 `none`。关闭体验卡开关只停止它参与最终权限合并,背包和佩戴原始事实仍保留;关闭整个 program 时付费与卡片原始事实仍可查询,但最终身份为 `none`、不下发权益。 -消费者: +钱包只读取 `effective_vip.level` 对应的显式权益行。来源为 `trial` 时,再移除 `trial_enabled=false` 的权益;当前 `daily_coin_rebate` 不对体验卡开放。owner service 必须按 `benefit_code` 判断,不能改回 `level >= N`;进房和上线通知的执行方必须使用 wallet 已合并用户开关的结果。 -- `user-service` 可消费后更新用户资料投影,便于列表页快速展示 VIP 标识。 -- `room-service` 如需房间内展示 VIP 角标,可消费投影事件或通过用户资料聚合读取,不接入购买主链路。 -- `activity-service` 可用于 VIP 活动任务统计。 +## P1 权益编码 -## 缓存策略 +| 等级 | 本级新增权益 | +| --- | --- | +| VIP1 | `vip_badge_identity`、`vip_medal`、`avatar_frame`、`vip_title` | +| VIP2 | `chat_bubble`、`vip_gift`、`entry_effect`、`visitor_history` | +| VIP3 | `voice_wave`、`profile_card`、`custom_room_background`、`room_image_message` | +| VIP4 | `animated_avatar`、`animated_room_cover`、`mic_skin`、`room_border`、`vehicle` | +| VIP5 | `colored_room_name`、`colored_nickname`、`colored_id`、`gift_tray_skin` | +| VIP6 | `hide_profile_data`、`custom_avatar_frame`、`leaderboard_invisible`、`daily_coin_rebate` | +| VIP7 | `custom_profile_card`、`anonymous_profile_visit`、`custom_gift` | +| VIP8 | `room_entry_notice`、`custom_pretty_id`、`custom_vehicle` | +| VIP9 | `anti_kick`、`anti_mute`、`online_global_notice` | -VIP 配置可以缓存,用户 VIP 状态不要只放 Redis。 +初始化时高等级行已展开为完整集合,但这是 seed 结果,不是运行时继承规则。后台可以显式移除任一等级的某项权益。 -- `vip_levels`:本地内存缓存 + 短 TTL,后台改配置后通过版本号或事件刷新。 -- `user_vip_memberships`:MySQL 为准,Redis 只做读优化。 -- 购买成功后删除或刷新 `vip:me:{user_id}` 缓存。 -- 过期时间短且强相关权益展示,缓存 TTL 不能超过 `min(60s, expires_at_ms-now)`。 +## 房间权限和 IM -## 后台配置约束 +`room-service` 在需要房间权限时读取 wallet 的 `GetMyVip.state`: -后台配置 VIP 等级时必须校验: +- `custom_room_background`:P1 program 的房主保存或切换自定义背景前强校验;Lalu 保持原逻辑。 +- `anti_kick`:普通房主/管理员踢人时拒绝操作。 +- `anti_mute`:普通房主/管理员新增禁言时拒绝操作。 +- 平台封禁、风控、超级管理员使用独立系统治理入口,不经过上述普通房管守卫。 +- `room_entry_notice`:权益有效且用户开关开启时,随 `JoinRoomResponse`、`RoomUserJoined` 和腾讯云 IM 当前房间系统消息携带;只在用户进入的当前房间展示,不做全服广播。 -- 等级唯一且大于 0。 -- 金币价格大于 0。 -- 有效期首版固定 7 天。 -- 绑定资源组必须存在且可用。 -- 资源组里的装扮资源建议使用 `inherit_vip_expiry`。 -- 禁用等级只影响新购买,不影响已购买用户的剩余有效期。 -- 修改价格只影响修改后的新订单,不回改历史订单。 +`online_global_notice` 由 `POST /vip/online-notice` 触发:gateway 先调用 wallet `CheckVipBenefit`,同时校验权益资格和用户开关;然后从 user-service 取服务端用户资料,最后写 activity-service 全局播报 outbox。腾讯云 IM 使用 `TIMCustomElem`,`Ext=im_broadcast`、`Desc=vip_online_notice`、`broadcast_type=vip_online_notice`、`scope=global`。Flutter 在进程内按 UTC 日控制触发:同一进程每天最多触发一次,不持久化该标记;杀进程重启后允许再次触发。服务端不做用户日级去重,但相同 `command_id` 的 HTTP 重试映射到同一 `event_id`。Flutter 不能直接向全服群发消息。 -后台可以修改资源组,但已成功订单使用订单快照解释;用户已经拿到的权益不因为后台换资源组被自动替换。 +## App HTTP API -## 数据一致性原则 +统一前缀 `/api/v1`,响应使用 `{code,message,request_id,data}`: -- VIP 购买不是普通“资源组发放”,它必须先扣金币。 -- 不允许 gateway 先扣金币再单独调用资源发放。 -- 不允许 user-service 写 VIP 状态。 -- 不允许客户端传资源 ID、资源组 ID、价格。 -- 不允许用 Redis 作为 VIP 当前状态唯一来源。 -- 不允许后台直接改用户 VIP 状态来修单,修单必须走后台补单/补偿命令,并写历史和审计。 +- `GET /vip/packages`:program、可购买等级、每级权益和完整 state。 +- `GET /vip/me`:付费、体验卡、最终 VIP、权益资格和用户功能开关。 +- `POST /vip/purchase`:请求 `{command_id,level}`;价格、时长和资源由服务端读取。 +- `GET|PATCH /vip/settings`:读取或局部修改进房/上线通知偏好;空 PATCH 拒绝。 +- `POST /vip/online-notice`:请求 `{command_id}`,服务端校验权益、开关和资料后写全服播报 outbox。 +- `GET /vip/coin-rebates/current`:返回当前 UTC 日返现资格;无资格或 cron 尚未生成时 `found=false`。 +- `POST /vip/coin-rebates/statuses`:按最多 100 个 `rebate_ids` 恢复系统消息真实状态,或用受限分页查历史。 +- `POST /vip/coin-rebates/{rebate_id}/claim`:请求 `{command_id}`,金额、归属、过期和入账全由 wallet 裁决。 +- `GET /users/me/resources?resource_type=vip_trial_card&active_only=true`:查询背包体验卡资源实例。 +- `POST /vip/trial-cards/{entitlement_id}/equip`:佩戴指定背包实例。 +- `DELETE /vip/trial-cards/equipped`:卸下当前体验卡。 +- 历史通用背包装备/卸下接口命中 `vip_trial_card` 时由 wallet service 委托上述专用状态机,禁止仓储层直接改装备槽;Flutter VIP 流程仍使用专用接口以一次取得完整 `VipState`。 +- `POST /manager-center/vip-grants`:gateway 按当前 program 自动选择直接会员或 30 天体验卡发放;Fami 发卡不自动佩戴。 -## 实现顺序 +后台接口: -1. 扩展 protobuf:新增 `VipService`、VIP 等级、我的 VIP、购买 VIP 响应结构。 -2. 增加 MySQL 表:`vip_levels`、`user_vip_memberships`、`vip_purchase_orders`、`user_vip_history`。 -3. 在 `wallet-service` 实现 VIP repository,先完成等级查询和用户 VIP 查询。 -4. 实现 `PurchaseVip` 事务:等级校验、幂等、行锁、扣金币、资源组发放、会员状态更新、历史和 outbox。 -5. 在 `gateway-service` 增加 App HTTP API:`/api/v1/vip/levels`、`/api/v1/vip/me`、`/api/v1/vip/purchase`。 -6. 接入用户资料聚合:底部“我的”和房间用户信息只展示 VIP 摘要。 -7. 增加过期扫描任务和 outbox 消费投影。 -8. 再做后台 VIP 配置页面和后台补单/审计能力。 +- `GET|PUT /v1/admin/activity/vip-program` +- `GET|PUT /v1/admin/activity/vip-levels` +- `POST /v1/admin/activity/vip-trial-card-grants` +- `POST /v1/admin/activity/vip-grants`,仅用于 `direct_membership` program -## 测试重点 +## 权益落地边界 -必须覆盖: +当前服务端已强制执行购买有效期、体验卡状态合并、用户通知开关、每日金币返现资格/消息/手动领取入账、自定义房间背景、防踢、防禁言、房内进场通知和上线全服通知。装扮类权益由已绑定的钱包资源与 Flutter 展示链路执行。 -- 无 VIP 购买 VIP1 成功。 -- VIP1 未过期时续费 VIP1,到期时间追加 7 天。 -- VIP1 未过期时购买 VIP3,等级变 VIP3,到期时间追加 7 天。 -- VIP3 未过期时购买 VIP1 被拒绝。 -- VIP 过期后可以购买任意等级。 -- 金币不足时不写 VIP 状态、不发资源。 -- 资源组发放失败时金币扣费回滚。 -- 同一 `command_id` 重试返回同一订单。 -- 同一用户并发购买时只产生符合顺序的一条最终状态。 -- 后台禁用等级后不能新购买,但不影响已购买用户有效期。 +以下权益仍只有稳定编码和资格下发,不能仅凭本架构宣称业务已完整闭环:访客匿名/隐藏、榜单隐身、房间发图、动态媒体、彩色样式和礼物托盘等。各 owner 实现时必须调用 `CheckVipBenefit` 或消费 `effective_benefits`,并补自己的存储、审计、风控和边界测试。 diff --git a/docs/flutter对接/Huwaa首页房间筛选Flutter对接.md b/docs/flutter对接/Huwaa首页房间筛选Flutter对接.md new file mode 100644 index 00000000..7a2c8694 --- /dev/null +++ b/docs/flutter对接/Huwaa首页房间筛选Flutter对接.md @@ -0,0 +1,82 @@ +# Huwaa 首页房间筛选 Flutter 对接 + +## 地址 + +筛选目录: + +```http +GET /api/v1/rooms/filters +Authorization: Bearer +X-App-Code: huwaa +``` + +房间列表: + +```http +GET /api/v1/rooms?tab=hot®ion_id=1001 +GET /api/v1/rooms?tab=hot&country_code=AE +``` + +目录接口要求用户已登录并完成资料。租户权威值是 access token claims 中的 `app_code`;`X-App-Code`/包名只参与请求入口解析,不能覆盖 token 或用来跨租户切换。Huwaa 返回本 App 的全部 active 区域和其中 enabled 国家;其他 App 只返回当前用户所在 active 区域及其国家。 + +## 参数 + +目录接口没有 query 参数。每个 option 都返回 `query_key` 和 `query_value`,Flutter 按下面规则调用房间列表: + +- All:`query_key`、`query_value` 都为空,不传 `region_id` 和 `country_code`。 +- 区域:把 option 的 `query_key=region_id`、`query_value` 原样放进 query。 +- 国家:把 option 的 `query_key=country_code`、`query_value` 原样放进 query。 +- `region_id` 与 `country_code` 不能同时传;切换筛选项时清空旧 cursor,再从第一页请求。 +- 旧参数 `country` 继续兼容,新代码统一使用 `country_code`。 + +`parent_region_id` 只用于把国家展示在所属区域下。筛选国家时只传 `country_code`,不要把 `parent_region_id` 同时作为 `region_id` 传入。 + +## 返回值 + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req-room-filters", + "data": { + "all": { + "filter_type": "all", + "query_key": "", + "query_value": "" + }, + "regions": [ + { + "filter_type": "region", + "query_key": "region_id", + "query_value": "1001", + "region_id": 1001, + "region_code": "MIDDLE_EAST", + "region_name": "Middle East", + "sort_order": 10 + } + ], + "countries": [ + { + "filter_type": "country", + "query_key": "country_code", + "query_value": "AE", + "parent_region_id": 1001, + "country_id": 971, + "country_code": "AE", + "country_name": "United Arab Emirates", + "country_display_name": "UAE", + "country_flag": "🇦🇪", + "sort_order": 10 + } + ] + } +} +``` + +目录的数组顺序已经使用后台 `sort_order`。国家只在“国家 enabled 且所属区域 active”时下发,客户端不要自行拼接国家码或区域 ID。 + +`GET /api/v1/rooms` 响应中的旧 `data.countries` 字段继续保留,供旧版当前区域国家筛选使用;Huwaa 新版完整目录以本接口为准。 + +## 相关 IM/RTC + +筛选目录和列表筛选不创建房间、不入腾讯云 IM 群,也不签发 RTC token。用户实际进入房间后,继续按现有 JoinRoom 流程处理 IM/RTC。 diff --git a/docs/openapi/gateway.swagger.yaml b/docs/openapi/gateway.swagger.yaml index 13716a7c..9f42b175 100644 --- a/docs/openapi/gateway.swagger.yaml +++ b/docs/openapi/gateway.swagger.yaml @@ -123,7 +123,7 @@ responses: schema: $ref: "#/definitions/ErrorEnvelope" Forbidden: - description: 已认证但被禁用、封禁或无权限。 + description: 已认证但资料未完成、被禁用、封禁或无权限;常见 `code` 为 `PROFILE_REQUIRED`、`USER_DISABLED`、`PERMISSION_DENIED`。 schema: $ref: "#/definitions/ErrorEnvelope" NotFound: @@ -1014,13 +1014,41 @@ paths: $ref: "#/responses/Internal" "502": $ref: "#/responses/UpstreamError" + /api/v1/rooms/filters: + get: + tags: + - rooms + summary: 首页房间区域与国家筛选目录 + operationId: listRoomFilters + description: >- + 返回可直接用于房间发现列表的筛选项。每个 option 的 query_key/query_value 可原样作为 + GET /api/v1/rooms 的查询参数;All 的两个字段为空,表示不附加 region_id/country_code。 + Huwaa 返回当前 app scope 的全部 active 区域及其 enabled 国家,其他 App 只返回当前登录用户所在 active 区域,防止下发不可用的跨区选项。 + 该路由要求 access token 有效且用户资料已完成;资料未完成返回 403/PROFILE_REQUIRED。 + security: + - BearerAuth: [] + responses: + "200": + description: 查询成功;国家项只包含 enabled 国家与 active 区域映射的交集。 + schema: + $ref: "#/definitions/RoomFilterCatalogEnvelope" + "401": + $ref: "#/responses/Unauthorized" + "403": + $ref: "#/responses/Forbidden" + "502": + $ref: "#/responses/UpstreamError" /api/v1/rooms: get: tags: - rooms summary: 房间发现列表 operationId: listRooms - description: 按当前登录用户的服务端 region_id 查询房间发现列表;响应卡片显式返回 `im_group_id`,当前值等于 `room_id`,但客户端不能硬编码该映射。 + description: >- + Huwaa 不传 region_id/country_code 时查询 All,传 region_id 时查指定 active 区域,传 country_code 时查指定 enabled 国家,两者互斥。 + 非 Huwaa App 仍按当前登录用户的服务端 region_id 隔离,不接受客户端 region_id。 + 置顶之后的普通房依次是本国家有人、其他国家/区域有人、本国家没人、其他国家/区域没人。 + 响应卡片显式返回 im_group_id,当前值等于 room_id,但客户端不能硬编码该映射。 security: - BearerAuth: [] parameters: @@ -1049,6 +1077,23 @@ paths: in: query required: false type: string + - name: region_id + in: query + required: false + type: integer + format: int64 + minimum: 1 + description: Huwaa 区域筛选;必须原样使用 `/api/v1/rooms/filters` 的 active `region_id`,不得与 `country_code` 同传。 + - name: country_code + in: query + required: false + type: string + description: Huwaa 国家筛选;必须原样使用 `/api/v1/rooms/filters` 的 enabled 大写国家码,不得与 `region_id` 同传。 + - name: country + in: query + required: false + type: string + description: 旧客户端兼容别名,等价于 `country_code`;新客户端统一使用 `country_code`。 responses: "200": description: 查询成功,`data.rooms[*].im_group_id` 是 JoinRoom 成功后客户端加入的腾讯 IM 房间群 ID。 @@ -2155,6 +2200,7 @@ paths: tags: - resources summary: 佩戴我的资源 + description: vip_trial_card 会在 wallet-service 内部委托 VIP 专用状态机;该通用响应不含完整 VipState,VIP 客户端应使用 /api/v1/vip/trial-cards/{entitlement_id}/equip。 operationId: equipMyResource security: - BearerAuth: [] @@ -2475,6 +2521,7 @@ definitions: - DISPLAY_USER_ID_LEASE_EXPIRED - DISPLAY_USER_ID_PAYMENT_REQUIRED - USER_DISABLED + - PROFILE_REQUIRED - PERMISSION_DENIED - NOT_FOUND - CONFLICT @@ -2492,6 +2539,7 @@ definitions: - conflict - insufficient balance - permission denied + - profile required - not found request_id: type: string @@ -4829,6 +4877,12 @@ definitions: type: string room_short_id: type: string + country_code: + type: string + description: 房主国家投影的大写国家码,首页国家筛选和卡片展示均以该字段为准。 + country_flag: + type: string + description: 根据 `country_code` 派生的国旗 emoji;无有效国家投影时省略。 RoomListData: type: object properties: @@ -4838,6 +4892,134 @@ definitions: $ref: "#/definitions/RoomListItemData" next_cursor: type: string + countries: + type: array + description: 兼容旧客户端的当前用户区域国家列表;完整跨区域目录请使用 `/api/v1/rooms/filters`。 + items: + $ref: "#/definitions/RoomListCountryData" + RoomListCountryData: + type: object + required: + - country_code + properties: + country_id: + type: integer + format: int64 + country_code: + type: string + country_name: + type: string + country_display_name: + type: string + country_flag: + type: string + sort_order: + type: integer + format: int32 + RoomFilterAllData: + type: object + required: + - filter_type + - query_key + - query_value + properties: + filter_type: + type: string + enum: [all] + query_key: + type: string + description: 固定为空字符串;客户端不附加筛选参数。 + query_value: + type: string + description: 固定为空字符串。 + RoomFilterRegionData: + type: object + required: + - filter_type + - query_key + - query_value + - region_id + - region_code + - region_name + - sort_order + properties: + filter_type: + type: string + enum: [region] + query_key: + type: string + enum: [region_id] + query_value: + type: string + description: region_id 的十进制字符串,可原样回传。 + region_id: + type: integer + format: int64 + region_code: + type: string + region_name: + type: string + sort_order: + type: integer + format: int32 + RoomFilterCountryData: + type: object + required: + - filter_type + - query_key + - query_value + - parent_region_id + - country_id + - country_code + - country_name + - country_display_name + - country_flag + - sort_order + properties: + filter_type: + type: string + enum: [country] + query_key: + type: string + enum: [country_code] + query_value: + type: string + description: 规范化后的大写 country_code,可原样回传。 + parent_region_id: + type: integer + format: int64 + description: 仅用于客户端把国家分组到区域;筛选国家时不要同时传 region_id。 + country_id: + type: integer + format: int64 + country_code: + type: string + country_name: + type: string + country_display_name: + type: string + country_flag: + type: string + sort_order: + type: integer + format: int32 + RoomFilterCatalogData: + type: object + required: + - all + - regions + - countries + properties: + all: + $ref: "#/definitions/RoomFilterAllData" + regions: + type: array + items: + $ref: "#/definitions/RoomFilterRegionData" + countries: + type: array + items: + $ref: "#/definitions/RoomFilterCountryData" MyRoomData: type: object required: @@ -5581,6 +5763,13 @@ definitions: properties: data: $ref: "#/definitions/RoomListData" + RoomFilterCatalogEnvelope: + allOf: + - $ref: "#/definitions/GatewayOKEnvelopeBase" + - type: object + properties: + data: + $ref: "#/definitions/RoomFilterCatalogData" MyRoomEnvelope: allOf: - $ref: "#/definitions/GatewayOKEnvelopeBase" diff --git a/docs/主播公会BD后台架构.md b/docs/主播公会BD后台架构.md index b3fa03d8..fce57223 100644 --- a/docs/主播公会BD后台架构.md +++ b/docs/主播公会BD后台架构.md @@ -79,6 +79,8 @@ graph LR 当前 `user_id` 是内部稳定主键,后台仍必须在所有 host/Agency/BD 查询、幂等和审计 detail 中带上 `app_code`。如果某些现有表还使用全局 `PRIMARY KEY(user_id)`、`PRIMARY KEY(command_id)` 或 `PRIMARY KEY(event_id)`,这只能说明当前实现暂时要求这些 ID 全局唯一;不能据此省略 `app_code`,也不能在多 App 写入前声称同一个 `command_id` 可以跨 App 复用。 +经理中心所有目标用户操作都必须从已验证的 App token 取得 `app_code`,不接受客户端自报租户。App 差异统一由 user-service 的 `manager_operations` 范围策略解析:Huwaa 的基础范围是 `global`;其他 App 的基础范围是 `country`,后台开启“跨区经理”后有效范围扩展为同一有效 `region_id`,仍禁止跨区域。头像框、座驾、徽章、VIP、升级等级、BD Leader/Admin/Superadmin、封禁和转移国家的搜索与写入口共用同一个范围匹配器,不能各自判断具体 App 或让 `scope=all` 绕过数据范围;各动作原有功能权限仍独立校验。 + ```sql admin_operation_logs( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, @@ -432,8 +434,9 @@ service UserHostAdminService { } ``` -`CreateBDLeaderRequest` 必须携带 `region_id`。user-service 校验该区域 active,锁定目标用户行,必要时更新 `users.region_id`,再创建 `bd_profiles`;同一个 `command_id` 重试只返回已创建的 BD Leader 事实,不重复迁移。 -这属于允许改变用户区域的后台关系管理路径之一;完整来源见 [User Region Change Sources](./用户区域与国家开发.md#user-region-change-sources)。 +`CreateBDLeaderRequest` 不接受 `region_id`。user-service 锁定目标用户行,读取其当前 active `users.region_id`,再使用该区域创建或恢复 BD Leader 事实;不会因创建角色而迁移用户国家/区域。同一个 `command_id` 重试只返回已创建的 BD Leader 事实。 + +Agency 邀请、Host 主动申请、BD 邀请的区域例外也由 `user-service` 按 `app_code` 执行:仅 Huwaa 在邀请/申请创建及接受/审核阶段允许跨区,其他 App 保留区域校验。Agency 主动邀请 Host 不在例外内。Coin Seller 列表、子币商申请/审批和转账授权对所有 App 仍为同区域。 当前项目仍在开发阶段,proto 可以按当前事实调整;修改 `api/proto` 后必须运行 `make proto` 并提交生成文件。 @@ -445,7 +448,7 @@ service UserHostAdminService { - Cycle calculation 使用 deterministic item key。重跑同一个 `draft` cycle 可以替换 draft items 或返回现有结果,不能重复插入。 - Cycle approve 必须锁 cycle 当前状态为 `draft`。 - Posting 使用 `salary_item_id` 作为钱包幂等键。`post_failed` 重试只处理未 posted item。 -- Relationship admin action 要锁目标用户的 `users`、`host_profiles`、`agencies`、`bd_profiles`,避免和 App 邀请接受并发造成身份冲突。后台创建 BD Leader 需要在同一事务内更新 `users.region_id` 和 `bd_profiles.region_id`。 +- Relationship admin action 要锁目标用户的 `users`、`host_profiles`、`agencies`、`bd_profiles`,避免和 App 邀请接受并发造成身份冲突。创建 BD Leader 使用目标用户当前 `users.region_id`,不在角色事务内改写用户区域。 ## Event And Outbox @@ -511,6 +514,9 @@ Outbox consumers can update App message inbox, BI, risk systems, and export jobs | Posting fails after partial success | Retry posts only unposted items | | Posted item needs correction | Adjustment item created; original item remains immutable | | Admin closes Agency | Agency hidden from App search; existing facts remain auditable | +| Huwaa Manager creates a BD Leader in another country/region | Creation succeeds; target keeps its current region | +| Other App Manager creates a BD Leader in another country | Request is rejected before `CreateBDLeader` is called | +| Huwaa Coin Seller creates or approves a cross-region sub-seller relation | Request is rejected with the existing region boundary | ## Critical Rules @@ -520,4 +526,5 @@ Outbox consumers can update App message inbox, BI, risk systems, and export jobs - BD/BD Leader base is downstream host salary only. - Leader direct Agency salary source must be de-duplicated. - Every admin mutation must be audited and idempotent. +- Huwaa 跨区例外适用于 Agency 邀请、Host 主动申请、BD 邀请和全部经理中心目标用户操作;Coin Seller 仍为同区域。 - Redis cannot be the source of truth for policies, salary, relationships, or audit. diff --git a/docs/主播公会BD架构.md b/docs/主播公会BD架构.md index 146e7735..57a0bb88 100644 --- a/docs/主播公会BD架构.md +++ b/docs/主播公会BD架构.md @@ -9,7 +9,7 @@ ## Goals - 用户申请加入 Agency 并通过后成为主播;Agency 踢出用户只解除 Agency 归属,不取消主播身份。 -- 用户只能搜索并申请加入自己所属区域的 active Agency。 +- Huwaa 用户可搜索并申请加入本 App 内的跨区域 active Agency;其他 App 保留现有国家/区域限制。 - Agency 归属于 BD;BD 归属于 BD Leader。BD Leader 也是 BD,只是具备邀请 BD 的能力。 - BD 可以邀请用户成为 Agency;BD Leader 可以邀请用户成为 BD,也可以直接邀请用户成为 Agency。 - 用户被邀请成为 Agency 时自动成为主播;但已是主播的用户不能再成为 Agency。 @@ -279,22 +279,20 @@ agency_memberships( ## Region Rules -用户所属区域来自 `user-service.users.region_id`,不能由客户端提交。 +用户所属区域来自 `user-service.users.region_id`,不能由客户端提交。`app_code` 来自 gateway 已验证的 token 并写入 `RequestMeta`;客户端不能通过请求体或请求头伪造 Huwaa 例外。 国家/区域后台管理边界在 `hyapp-admin-server`:创建国家、启用/禁用国家、创建区域、启用/禁用区域不属于 App 侧 user-service RPC。App 侧只消费 user-service 已计算好的 `region_id` 和区域投影。 -后台创建 BD Leader 是关系管理命令,不是客户端区域提交:`hyapp-admin-server` 负责权限和审计,`user-service` 在同一事务里把目标用户的 `users.region_id` 更新为后台选择的区域,并用同一个区域创建 `bd_profiles`。 -用户区域允许变化的完整来源以 [User Region Change Sources](./用户区域与国家开发.md#user-region-change-sources) 为准;Host/Agency/BD 关系只消费当前 `users.region_id` 和各关系表里的区域快照。 +用户区域允许变化的完整来源以 [User Region Change Sources](./用户区域与国家开发.md#user-region-change-sources) 为准;Host/Agency/BD 关系创建后保留目标用户当前区域,Huwaa 跨区组织关系不会顺带迁移 `users.region_id`。 | Action | Region Rule | | --- | --- | -| 后台创建 BD Leader | 后台必须选择 active 区域;目标用户当前区域可以不同,创建成功后目标用户 `users.region_id` 和 BD Leader `bd_profiles.region_id` 都变为所选区域 | -| 搜索 Agency | 只返回 `agency.region_id == user.region_id` 且 active/join_enabled | -| 申请加入 Agency | 申请人 `region_id` 必须等于 Agency `region_id` | -| BD 邀请 Agency | BD 和被邀请人必须在同一区域,除非后台显式跨区授权 | -| Leader 邀请 BD | Leader 和被邀请人必须在同一区域,除非后台显式跨区授权 | -| Leader 邀请 Agency | Leader 和被邀请人必须在同一区域,除非后台显式跨区授权 | -| 用户改国家导致区域变化 | 不自动迁移 Agency/BD 归属;新申请按新区域限制,历史统计按事件快照归属 | - -如果后续业务需要跨区 Agency 或跨区 BD,必须增加显式 `region_scope` 和后台审批,不能绕过区域规则直接查全局 Agency。 +| 经理创建 BD Leader | Huwaa 经理可搜索并创建其他国家/区域的 BD Leader;其他 App 保持经理与目标用户同国家。创建后 BD Leader 使用目标用户已有 `users.region_id`,不迁移用户国家/区域 | +| 搜索 Agency | Huwaa 返回本 App 内所有 active/join_enabled Agency;其他 App 保留按当前用户国家过滤 | +| 申请加入 Agency | Huwaa 申请创建和 Agency 审核通过均允许跨区;其他 App 要求申请人与 Agency 同区域 | +| Agency 主动邀请 Host | 所有 App 仍要求目标用户与 Agency 同区域;Huwaa 例外只适用于 Host 主动申请 | +| BD/Leader 邀请 Agency | Huwaa 的邀请创建和接受均允许跨区;其他 App 要求邀请人与目标用户同区域 | +| Leader 邀请 BD | Huwaa 的邀请创建和接受均允许跨区;其他 App 要求 Leader 与目标用户同区域 | +| Coin Seller | 所有 App 继续按区域返回 active 币商;子币商申请创建、审批建立关系和转账前授权均要求父子币商同区域 | +| 用户改国家导致区域变化 | 不自动迁移 Agency/BD 归属;新申请或邀请按 `app_code` 对应规则处理,历史统计按事件快照归属 | ## Application And Invitation Flows @@ -309,16 +307,16 @@ sequenceDiagram C->>G: GET /agencies/search G->>U: SearchAgenciesForUser(user_id) - U->>H: load user region and active agencies - H-->>U: active agencies in region - U-->>G: active agencies in region + U->>H: load app_code policy, user country/region and active agencies + H-->>U: Huwaa all active agencies; other App same-country agencies + U-->>G: app-scoped agency list G-->>C: agency list C->>G: POST /host/applications G->>U: ApplyToAgency(user_id, agency_id) U->>H: load user region and agency H->>H: lock user host profile and agency - H->>H: create pending application + H->>H: enforce app-scoped region policy and create pending application H-->>U: application U-->>G: application ``` @@ -353,7 +351,7 @@ Kick must not delete `host_profiles` and must not delete relationship history. T BDInviteAgency(target_user_id) -> target must not be active host -> target must not own active agency - -> target region must match BD region + -> Huwaa allows cross-region; other App target region must match BD region -> create role_invitation(type=agency, inviter_bd_user_id) -> target accepts -> create agency owned by target @@ -374,7 +372,7 @@ BD can invite self only when: LeaderInviteBD(target_user_id) -> inviter must be active bd_leader -> target must not be active BD - -> target region must match leader region + -> Huwaa allows cross-region; other App target region must match leader region -> create role_invitation(type=bd) -> target accepts -> create bd_profiles(role=bd, parent_leader_user_id=leader) @@ -610,7 +608,7 @@ Host/Agency/BD 的 App 可见通知统一进入 App `消息` tab 的 `system` | BD joins another Agency | BD profile remains active; host membership is separate | | BD wants own Agency after joining another Agency | Must first leave or be removed from active host membership; then self-invite can create Agency | | Agency owner wants to leave own Agency | Not allowed through normal leave; Agency close or transfer is backend ability | -| User region changes | Existing relationships remain; new search/application uses new region; historical stats use event snapshots | +| User region changes | Existing relationships remain; new search/application/invitation follows the current App policy; historical stats use event snapshots | | App earning summary and wallet balance differ | Wallet balance is authority for withdrawable amount; earning summary is business explanation | | Client retries application or invitation action | Same `command_id` returns existing result; different `command_id` must hit uniqueness guard | @@ -622,7 +620,8 @@ Host/Agency/BD 的 App 可见通知统一进入 App `消息` tab 的 `system` | Scenario | Expected | | --- | --- | -| User searches Agency | Only same-region active joinable agencies returned | +| Huwaa user searches Agency | All active joinable agencies in Huwaa are returned, including other regions | +| Other App user searches Agency | Existing country/region restriction remains in force | | User applies and Agency approves | User becomes active host and active Agency member | | Agency kicks host | Membership ends, host profile remains active | | Kicked host reapplies to another Agency | New active membership created | @@ -630,6 +629,9 @@ Host/Agency/BD 的 App 可见通知统一进入 App `消息` tab 的 `system` | BD invites self as Agency while not host | Agency created, BD also becomes host and Agency owner | | BD joins another Agency | BD remains BD and becomes host under that Agency | | BD Leader invites BD | Target gets BD profile, not host profile | +| Huwaa cross-region Agency/BD invitation is accepted | Target keeps its own region and the invited relationship is created | +| Huwaa manager creates cross-region BD Leader | Target keeps its own region and receives active BD Leader identity | +| Huwaa user lists or invites Coin Seller | Only same-region sellers or sub-seller targets are allowed | | Host works across two Agencies in one day | Stats split by relation snapshot | | App reads earning summary before backend settlement | Shows pending/unsettled state, not withdrawable money | | Same application command retried | Returns the original pending application | @@ -640,7 +642,7 @@ Host/Agency/BD 的 App 可见通知统一进入 App `消息` tab 的 `system` - Agency owner is a host, but ordinary active host cannot become Agency. - BD and host identities can coexist; BD role alone does not imply host. - BD Leader is a BD with extra invitation rights. -- Region constraints are enforced by server-side `region_id`. +- Region policy is selected by authenticated `app_code`: Huwaa only exempts Agency invitation, Host application, BD invitation and manager-created BD Leader; other App paths and all Coin Seller relations retain server-side region restrictions. - App cannot configure policies, approve salary, post wallet balance, or mutate withdrawals. - Wallet is the only owner of salary balance and withdrawal state. - All relationship changes must be auditable and idempotent. diff --git a/docs/定时任务服务架构.md b/docs/定时任务服务架构.md index feae2e68..06621625 100644 --- a/docs/定时任务服务架构.md +++ b/docs/定时任务服务架构.md @@ -152,21 +152,21 @@ tasks: timeout: "3s" lock_ttl: "30s" batch_size: 100 - app_codes: ["lalu"] + app_codes: ["lalu","fami","huwaa"] user_region_rebuild: enabled: true interval: "5s" timeout: "5s" lock_ttl: "30s" batch_size: 500 - app_codes: ["lalu"] + app_codes: ["lalu","fami","huwaa"] message_fanout: enabled: true interval: "1s" timeout: "10s" lock_ttl: "30s" batch_size: 500 - app_codes: ["lalu"] + app_codes: ["lalu","fami","huwaa"] mic_open_session_compensation: enabled: true interval: "60s" @@ -175,7 +175,7 @@ tasks: batch_size: 100 pending_publish_max_age: "2m" publishing_session_max_age: "12h" - app_codes: ["lalu"] + app_codes: ["lalu","fami","huwaa"] ``` ## Cron Database diff --git a/docs/房间Outbox补偿开发.md b/docs/房间Outbox补偿开发.md index 779d4d95..1eba6233 100644 --- a/docs/房间Outbox补偿开发.md +++ b/docs/房间Outbox补偿开发.md @@ -96,6 +96,8 @@ rocketmq: namespace: "" send_timeout: "3s" retry: 2 + # 安全默认值;全部 room consumer 完成 legacy||typed 兼容订阅后才切 event_type。 + room_event_tag_mode: "legacy" room_outbox: enabled: false topic: "hyapp_room_outbox" @@ -127,12 +129,13 @@ outbox_worker: | 字段 | 规则 | | --- | --- | | `rocketmq.enabled` | 控制 RocketMQ client 是否启用;本地默认关闭,线上示例开启 | +| `rocketmq.room_event_tag_mode` | `legacy` 是无损迁移默认值;`event_type` 只在所有 consumer 已双订阅后显式开启 | | `rocketmq.room_outbox.enabled` | 控制 room outbox 是否可发布到 MQ;`publish_mode=mq/dual` 时必须开启 | | `rocketmq.room_outbox.tencent_im_consumer_enabled` | 控制 room-service 是否作为 IM bridge consumer 消费 `hyapp_room_outbox` | | `rocketmq.rocket_launch.enabled` | 控制火箭点火后是否发布和消费延迟发射唤醒消息 | | `outbox_worker.publish_mode` | 本地默认 `direct`,线上建议 `mq`,迁移期可用 `dual` | | `poll_interval` | worker 空轮询间隔,必须大于 0,避免 busy loop | -| `batch_size` | 每轮最大抢占条数,必须大于 0,避免全表扫描 | +| `batch_size` | 每轮最多处理条数;同步 publisher 逐条 claim,避免批尾尚未发送 lease 就已过期 | | `publish_timeout` | 单条 outbox 发布 deadline,避免外部系统拖死 worker | | `max_retry_count` | 达到次数后标记 `failed`,防止 poison event 永久刷日志 | diff --git a/docs/接口清单.md b/docs/接口清单.md index f157b13c..06a88141 100644 --- a/docs/接口清单.md +++ b/docs/接口清单.md @@ -57,7 +57,8 @@ | GET | `/api/v1/users/me/friend-requests` | users | `listMyFriendApplications` | 我的好友申请列表 | | GET | `/api/v1/users/me/friends` | users | `listMyFriends` | 我的好友列表 | | DELETE | `/api/v1/users/{user_id}/friend` | users | `deleteFriend` | 双向删除好友关系 | -| GET | `/api/v1/rooms` | rooms | `listRooms` | 房间发现列表,支持 `tab=hot/new`,卡片返回 `im_group_id` | +| GET | `/api/v1/rooms/filters` | rooms | `listRoomFilters` | Flutter 首页区域/国家筛选目录,下发可原样回传的 `region_id/country_code` | +| GET | `/api/v1/rooms` | rooms | `listRooms` | 房间发现列表;Huwaa 默认 All,支持互斥的 `region_id/country_code` 及 `tab=hot/new`,卡片返回 `im_group_id` | | GET | `/api/v1/rooms/me` | rooms | `getMyRoom` | 查询 Mine 顶部我的房间卡片 | | GET | `/api/v1/rooms/feeds` | rooms | `listRoomFeeds` | Mine 页 Visited/Friend/Following/Followed 房间流 | | GET | `/api/v1/rooms/current` | rooms | `getCurrentRoom` | 查询当前可恢复房间 | diff --git a/docs/新App上线配置检查清单.md b/docs/新App上线配置检查清单.md new file mode 100644 index 00000000..6411abb1 --- /dev/null +++ b/docs/新App上线配置检查清单.md @@ -0,0 +1,351 @@ +# 新 App 上线配置检查清单 + +本文档定义 HyApp 新 App 从租户注册到正式运营的配置边界、业务 owner、开启条件和验收标准。它不是“把旧 App 数据全量复制一遍”的脚本说明;每项数据必须按 owner service 和业务键管理,不能跨租户复用原始自增 ID。 + +## 1. 使用方式 + +每次新 App 上线前,先建立一份当次上线记录,填写: + +| 字段 | 说明 | +| --- | --- | +| `app_code` | 后端租户编码,全链路使用同一个小写值 | +| App 名称 | 用户可见品牌名 | +| Android package | Android 包名,必须和 Firebase、Google Play 一致 | +| iOS bundle ID | iOS bundle identifier,必须和 Firebase、Apple 后台一致 | +| 对照 App | 只用于确定初始产品口径,不代表可以直接复制原始 ID | +| 计划开服时间 | UTC epoch milliseconds,用于活动、版本和内容生效时间 | +| 支持国家/区域 | 国家、区域、内容和价格配置的权威范围 | +| 负责人 | 后端、客户端、运营、支付、IM/RTC 各自验收人 | + +状态只使用以下四种: + +- `NOT_STARTED`:尚未配置。 +- `CONFIGURED`:已写入,但未经过真实请求验证。 +- `VERIFIED`:已用新 `app_code` 走通生产链路并保留证据。 +- `BLOCKED`:存在会阻断上线的问题,必须写明 owner 和解决动作。 + +## 2. 上线硬性原则 + +1. 新 App 数据必须使用新 `app_code`;不能依赖代码中的 `lalu` 默认值维持运行。 +2. 复制配置必须按 `resource_code`、`group_code`、`region_code`、`product_code` 等业务键重建引用,不能复制旧 App 的自增 ID。 +3. 礼物、活动、VIP、游戏等只有主配置表不算完成;所有价格、区域、资源、奖励和 worker 必须一起验证。 +4. 敏感值只能写入线上环境变量或已忽略的运行时 env,不能写入可提交 YAML、SQL、文档或代码。 +5. 全部时间使用 UTC,范围统一使用 `[start_ms, end_ms)`。 +6. 共享 RocketMQ topic 不代表多 App 已经就绪;必须确认消息 payload、consumer context、幂等表和 outbox claim 都保留 `app_code`。 +7. 只有完成“配置写入 → 服务加载 → 真实 API/IM/RTC/MQ 请求 → 日志与数据回查”才能标记 `VERIFIED`。 + +## 3. App 注册与租户基础 + +Owner:`user-service`、`gateway-service`。 + +| 配置项 | 数据/配置入口 | 开启条件 | 验收标准 | +| --- | --- | --- | --- | +| App 注册 | `hyapp_user.apps` | `status=active`,`app_code` 和包名唯一 | 按 `X-App-Code` 和 package 都能解析到同一 App | +| Android/iOS 包信息 | `apps` 及客户端 flavor | 与应用商店、Firebase 完全一致 | 真实安装包登录成功 | +| Firebase 项目 | `user-service.third_party.firebase.projects.` | 项目 ID、凭据和签名指纹都属于新 App | Google/Firebase token 在新 App 成功,不被其他 App 接受 | +| JWT/请求元数据 | gateway → gRPC `RequestMeta.app_code` | 所有需要租户隔离的 RPC 透传 | 下游日志和数据行均为新 `app_code` | +| 登录风控 | `auth_risk_configs`、`login_risk_country_blocks` | 明确复制或新建策略 | 允许国家可登录,拦截国家返回预期错误 | + +## 4. 国家、区域与数据可见性 + +Owner:`user-service`。 + +| 配置项 | 主表 | 开启条件 | 验收标准 | +| --- | --- | --- | --- | +| 国家 | `countries` | 运营国家 `enabled=1` | `/api/v1/countries` 只返回新 App 允许的国家 | +| 区域 | `regions` | `status=active` | 后台和 App 能查到预期区域 | +| 国家归属 | `region_countries` | 每个启用国家有且只有一个有效业务区域 | 新注册用户的 `region_id` 正确 | +| 存量用户重建 | `user_region_rebuild_tasks` | 只在映射变更或补历史数据时执行 | task 完成且 `UserRegionChanged` outbox 已投递 | + +不能根据服务器所在地、IP 或容器时区推断业务国家/区域。 + +## 5. 管理后台与 App 内容入口 + +Owner:`server/admin`。 + +| 配置项 | 主表 | 开启条件 | 验收标准 | +| --- | --- | --- | --- | +| App 通用配置 | `admin_app_configs` | 新 App 必须有自己的 `group + key` | H5 入口和功能跳转不读取其他 App 值 | +| Banner | `admin_app_banners` | 至少一个符合首页/区域/国家口径的 active 内容 | `/api/v1/app/banners` 在目标国家返回预期内容 | +| 弹窗 | `admin_app_popups` | 按产品需求设为 active,并配置生效时间 | `/api/v1/app/popups` 可见且跳转正确 | +| 开屏 | `admin_app_splash_screens` | 按 platform/国家/区域生效 | `/api/v1/app/splash-screens` 返回预期素材 | +| 版本控制 | `admin_app_versions` | Android/iOS 当前生产版本均有记录 | `/api/v1/app/version` 返回正确版本、build 和强更策略 | +| Explore Tab | `admin_app_explore_tabs` | 显式启用并配置预期顺序 | App 探索入口与后台一致 | + +内容 API 返回 `200` 但 `items=[]` 只证明接口可用,不代表运营配置已完成。 + +## 6. 腾讯云 IM、RTC 与播报 + +Owner:`gateway-service`、`activity-service`、`room-service`;客户端长连属于腾讯云 IM。 + +| 配置项 | 配置/链路 | 开启条件 | 验收标准 | +| --- | --- | --- | --- | +| IM UserSig | `gateway-service.tencent_im` | SDKAppID、secret、admin identifier 与线上 IM 应用一致 | 新 App 真实用户能登录 IM | +| IM callback | gateway callback | callback 能从合法 GroupID/请求中确定 `app_code` | Fami/Lalu 回调不会互相落库 | +| RTC UserSig | `gateway-service.tencent_rtc` | SDKAppID、secret、room ID 类型与客户端一致 | 真实设备进房、上麦、发流成功 | +| RTC callback | `tencent_rtc.callback_url` | 不能把 `app_code` 写死为其他 App | 回调日志与房间租户一致 | +| 全局/区域播报群 | `activity-service.broadcast` | 为新 App 和所有 active region 建立群 | 群 ID 含新 `app_code`,客户端能加入 | +| 播报 outbox | `im_broadcast_outbox` | worker 必须按记录 `app_code` claim、发送和 mark | 新 App 消息从 pending 到 delivered,无跨 App 更新 | + +至少实测一条全局播报、一条区域播报和一条房间系统消息。 + +## 7. 房间配置 + +Owner:`room-service`。Room Cell 仍然是房间核心状态 owner;新 App 上线不得把房间状态复制到其他服务。 + +| 配置项 | 主表 | 开启条件 | 验收标准 | +| --- | --- | --- | --- | +| 麦位 | `room_seat_configs` | 有新 App 配置,否则明确接受代码默认值 | 创房后麦位数量与客户端一致 | +| 火箭 | `room_rocket_configs` | `enabled=1` 且奖励、油箱、频控已设定 | 完成加油、发射、奖励和 MQ 验证 | +| 宝箱 | `room_treasure_configs` | 产品需要时显式开启 | 开关与 App 入口一致 | +| 人机房 | `room_human_robot_configs` | 只有配好机器人账号和策略后才能开启 | 机器人加入/离开不污染真人 presence | +| 区域置顶 | `room_region_pins` | 按新 App 运营需求单独配置 | 房间变更区域后重新建立置顶 | + +## 8. 礼物、资源与装扮 + +Owner:`wallet-service`。 + +礼物上线是一组不可拆分的配置: + +| 配置项 | 主表 | 开启条件 | +| --- | --- | --- | +| 资源定义 | `resources` | 依赖资源必须 `status=active`,素材 URL 可访问 | +| 资源组 | `resource_groups`、`resource_group_items` | 所有活动/VIP/成就引用必须映射到新 App 资源 | +| 资源商店 | `resource_shop_items` | 价格、有效期和上架状态已审核 | +| 礼物类型 | `gift_type_configs` | 礼物分类已按产品口径启用 | +| 礼物配置 | `gift_configs` | `status=active`,生效时间正确 | +| 礼物价格 | `wallet_gift_prices` | 每个 active 礼物都有有效价格 | +| 礼物区域 | `gift_config_regions` | 地区限制和新 App 的 region 映射一致 | +| 钻石分成 | `gift_diamond_ratio_configs` | 送礼所在区域有唯一有效分成规则 | + +验收必须使用新 App 真实账号执行:礼物列表 → 余额校验 → 扣款 → Room Cell 表现 → `RoomGiftSent` outbox → 统计/活动/通知下游。 + +## 9. 充值、支付与兑换 + +Owner:`wallet-service`。 + +| 配置项 | 主表/运行配置 | 开启条件 | 验收标准 | +| --- | --- | --- | --- | +| 充值商品 | `wallet_recharge_products` | 新 App 商品 active,币种、金额、金币数正确 | App 返回商品和后台一致 | +| 商品区域 | `wallet_recharge_product_regions` | 用新 App `product_id` 和 `region_id` 重建 | 每个运营区域都有预期商品 | +| Google Play | `google_play` 及包名/服务账号 | 新 package 和验单凭据正确 | 测试购买、验单、发币、consume 均成功 | +| 第三方支付 | `third_party_payment_channels/methods` | 渠道、国家、币种和回调签名已配置 | 下单和回调的 `app_code` 一致 | +| USDT/币安等外部充值 | service YAML/env 的 per-App 映射 | 必须有新 App 账户/地址,不能回落到 Lalu | 创单、归集、确认数和入账正确 | +| 钻石兑换 | `wallet_diamond_exchange_rules` | active 规则已按运营口径配置 | 边界值和余额流水一致 | +| 红包 | `red_packet_configs` 及档位表 | 开关、金额、份数、过期 worker 均支持新 App | 发、抢、退、播报完整闭环 | + +## 10. VIP + +Owner:`wallet-service`;行为唯一依据是 `vip_program_configs`。 + +| 配置项 | 主表 | 开启条件 | 验收标准 | +| --- | --- | --- | --- | +| VIP 方案 | `vip_program_configs` | `program_type`、等级数、购买/升降级、发放模式明确 | API 返回的方案与后台一致 | +| VIP 等级 | `vip_levels` | 只有完成价格、时长和资源组配置的等级才能 active | `/api/v1/vip/packages` 返回预期等级 | +| VIP 权益 | `vip_level_benefits` | 每个 active 等级的权益和资源引用有效 | 购买/发卡后权益生效 | +| 日奖励 | cron `vip_daily_coin_rebate` | cron `app_codes` 含新 App,金额和 UTC 日切口径已确认 | 当日 run、领取和钱包流水一致 | +| 进房/上线播报 | activity IM broadcast | 用户开关、VIP 级别和广播群完整 | 真实设备收到新 App 播报 | + +等级处于 `disabled` 时,套餐接口返回空数组是正确保护行为,但该 App 不能因此被认为“VIP 已上线”。 + +## 11. 活动与奖励 + +Owner:`activity-service`。 + +| 能力 | 主表/配置 | 上线必须同时确认 | +| --- | --- | --- | +| 注册奖励 | `registration_reward_configs` | 开关、奖励类型、金额/资源组、每日限制 | +| 首充奖励 | `first_recharge_reward_configs/tiers` | 主开关和至少一个 active 档位 | +| 累充奖励 | `cumulative_recharge_reward_configs/tiers` | 主开关、周期口径和 active 档位 | +| 邀请奖励 | `invite_activity_reward_configs/tiers` | 邀请关系、有效邀请和充值事件消费就绪 | +| 七日签到 | `seven_day_checkin_configs/versions/rewards` | 开关、一个当前版本和 1–7 天完整奖励 | +| 每日任务 | `task_definitions/versions` | active 任务、周期、指标和奖励口径一致 | +| 成就 | `achievement_definitions/conditions` | active 成就引用的徽章资源属于新 App | +| 成长等级 | `growth_level_tracks/rules/tiers` | 财富/魅力/游戏轨道及展示资源完整 | +| CP 周榜 | `cp_weekly_rank_configs/rewards` | 开关、排名数、关系类型和奖励齐全 | +| 周星 | `weekly_star_cycles/gifts/rewards` | 当前 UTC 时间命中 active 周期,礼物和 Top 奖励完整 | +| 房间流水奖励 | `room_turnover_reward_configs/tiers` | 开关、周期、档位和钱包发放链路完整 | +| 转盘 | `wheel_rule_versions/prize_tiers` | 当前规则版本 active,奖品和 RTP 口径已审核 | + +主开关、奖励档位和奖励资源缺任何一项,该活动都不得标记上线。 + +## 12. 幸运礼物 + +Owner:`lucky-gift-service`。`activity-service` 中的旧副本不能作为正式运行依据。 + +| 配置项 | 主表/链路 | 验收标准 | +| --- | --- | --- | +| 规则版本 | `lucky_gift_rule_versions` | 每个运营奖池有 active 规则 | +| 阶段档位 | `lucky_gift_stage_tiers` | 规则对应的 stage/tier 完整且 enabled | +| 独立奖池 | `lucky_pools` | 按 `app_code + pool_id` 初始化;只复制规则时不能复制对照 App 的余额、累计入池/出池或用户运行态 | +| 礼物映射 | 礼物配置与 pool 映射 | 只有新 App active 礼物才能进入抽奖 | +| owner outbox | `lucky_gift_outbox` | 抽奖事实持久化并发布到独立 MQ topic | +| 区域飘屏 | activity 独立 consumer group | 不得复用其他 consumer group,且保留 `app_code` | + +真实验收要覆盖:普通奖、大奖、幂等重试、钱包入账、outbox 投递和区域飘屏。 + +## 13. 游戏 + +Owner:`game-service`。 + +| 配置项 | 主表 | 开启条件 | +| --- | --- | --- | +| 游戏平台 | `game_platforms` | 平台 active,启动和回调参数完整 | +| 游戏目录 | `game_catalog` | 新 App 明确选择要开放的游戏,不强制与对照 App 数量相同 | +| 展示规则 | `game_display_rules` | 每个 active 游戏有可见规则 | +| 自研游戏配置 | `game_self_game_configs`、风控/新手策略 | 游戏开关、RTP、风控参数已审核 | +| 机器人 | `game_self_game_robots` | 自研对战需要补位时,必须有新 App 真实机器人用户 | +| 档位奖池 | `game_self_game_stake_pools` | 每个可玩档位有 active 奖池和初始水位 | +| 回调归属 | gateway JWT → `RequestMeta.app_code` | 厂商回调不能默认记入 Lalu | + +第三方游戏少于对照 App 不一定是错误;必须由产品/运营给出明确游戏清单。但自研游戏已对用户开放时,机器人和奖池为空属于上线阻断。 + +## 14. cron、MQ、outbox 和通知 + +| 配置项 | Owner | 开启条件 | 验收标准 | +| --- | --- | --- | --- | +| cron 任务 | `cron-service` | 所有应在新 App 运行的 task `app_codes` 含新 App | `cron_task_runs` 按 `task_name + app_code` 生成成功记录 | +| owner outbox publisher | room/wallet/user/game/lucky-gift | 只有 owner 服务扫描自己的 outbox | 新 App pending 可持续转 delivered,无长时间积压 | +| RocketMQ consumer group | 每个下游服务 | 不得共享 owner 的 outbox 状态或其他业务消费组 | group 存在、无异常 lag、重投幂等 | +| 统计消费 | `statistics-service` | wallet/room/user/game 事实消费器已启用 | 新 App 聚合表产生 UTC 日数据 | +| 私有通知 | `notice-service` | RocketMQ 消费器为主链路 | 钱包/CP/房间通知均使用新 App 租户 | +| 系统/活动 inbox | `activity-service` message owner | 后台通知通过 owner API/fanout job 创建 | 不直写 inbox/fanout 表,能从 App 查看并已读 | + +`notice-service` 的旧轮询 worker 关闭、RocketMQ consumer 开启是正常迁移状态,不能只看一个 `enabled=false` 就判定新 App 未开通。 + +## 15. 统计与数据大屏 + +Owner:`statistics-service`;后台查询入口在 `server/admin`。 + +1. 确认 `WalletRechargeRecorded`、`GameOrderSettled`、`RoomGiftSent`、`RoomUserJoined`、`UserRegistered` 事件都带新 `app_code`。 +2. 确认消费幂等表已记录新 App 事件。 +3. 确认充值、游戏、礼物、活跃、注册和留存聚合表在 UTC 边界正确生成。 +4. 确认 admin 大屏选择新 App 时只查聚合表,不回查 owner 明细库。 +5. 使用可控测试账号产生一组注册、进房、充值、游戏、送礼事实,核对次日聚合。 + +## 16. 客户端与发布 + +| 项目 | 必须验证 | +| --- | --- | +| flavor/品牌 | App 名称、图标、包名、bundle ID、深链、主题和隐私页均为新 App | +| API host | 生产包指向生产 API,不得混入测试 host | +| Firebase | flavor 使用自己的 `google-services.json`/iOS 配置 | +| IM/RTC | SDKAppID、UserSig 及回调链路完整 | +| 支付 | package/product ID/签名与商店后台一致 | +| 版本 API | 首个生产 build 已写入 `admin_app_versions` | +| 埋点 | 心跳、页面、社交漏斗和游戏事件使用新 `app_code` | + +必须在真实 Android/iOS 设备上执行最终验收,不能只用 curl 代替 IM、RTC、支付和客户端跳转验证。 + +## 17. 正式上线验收清单 + +### 17.1 配置一致性 + +- [ ] 新 App 在所有 owner 库的 `app_code` 一致。 +- [ ] 所有跨表引用已按业务键重建,不存在指向对照 App ID 的记录。 +- [ ] 全部敏感配置只存在运行时 env/密钥管理系统。 +- [ ] 两台或多台副本的 YAML、镜像 tag 和配置 hash 一致。 + +### 17.2 业务冒烟 + +- [ ] 注册、登录、刷新 token、用户资料、国家/区域归属正常。 +- [ ] IM 登录、单聊、房间群、全局/区域播报正常。 +- [ ] RTC 进房、上麦、发流、回调和离开正常。 +- [ ] 创房、进房、麦位、踢人、锁房、房间恢复正常。 +- [ ] 礼物列表、送礼扣款、房间表现、收礼统计正常。 +- [ ] 充值商品、真实测试购买、回调验单、钱包入账正常。 +- [ ] VIP/活动/签到/任务/成就/周星等已宣布上线的能力全部真实验证。 +- [ ] 第三方游戏启动、回调归属和余额一致;自研游戏机器人/奖池可用。 + +### 17.3 异步与数据 + +- [ ] owner outbox 无新 App 长时间 pending/failed 积压。 +- [ ] RocketMQ consumer group 存在且无异常 lag。 +- [ ] cron 所需 task 已产生新 App 成功运行记录。 +- [ ] 通知、inbox、播报和 push 不串 App。 +- [ ] 统计聚合表有新 App 真实事实,UTC 边界正确。 + +### 17.4 发布结论 + +只有以下条件全部满足才能标记“可正式运营”: + +1. P0 阻断项为 0。 +2. 所有宣布上线的功能已完成真实设备和生产链路验证。 +3. 运营明确接受未上线的可选功能,且 App 客户端不展示无配置入口。 +4. 发布镜像、数据迁移、MQ、YAML/env、客户端版本和回滚方案均有记录。 + +## 18. Fami 生产差异快照(2026-07-13) + +本节是对生产 YAML、生产 MySQL、线上镜像和公网 API 的只读快照,用于保留 Fami 从试运营转正式运营前的基线。后续修复后应追加新日期的验收结果,不覆盖本快照。 + +### 18.1 P0 阻断项 + +| 项目 | Fami 当前状态 | Lalu 对照 | 必须动作 | +| --- | --- | --- | --- | +| 礼物 | `gift_configs=0`、`wallet_gift_prices=0` | 95 个 active 礼物、103 个 active 价格 | 按业务键创建 Fami 礼物和价格,验证送礼闭环 | +| 资源 | 535 条全部 `deleted`,有效资源/资源组/商城均为 0 | 501 active 资源、56 active 资源组 | 重建 Fami 资源体系和全部引用 | +| VIP | 方案 active,但 VIP1–VIP10 全部 disabled | VIP1–VIP8 active | 只启用已完成价格、权益和资源配置的 VIP1–VIP9 | +| RTC callback | 两台 gateway 回调 URL 都写死 `app_code=lalu` | Lalu 符合当前值 | 调整回调归属,分别验证 Fami/Lalu RTC 事件 | +| IM 播报 worker | 线上 `activity-service:20260712-main-7d011ce` 仍以默认 Lalu context claim/reconcile | Lalu 已投递 71,564 条 | 发布多 App claim/建群逻辑,实测 Fami 全局和区域播报 | +| 幸运礼物 owner | `lucky-gift-service` 规则版本和档位均为 0 | 15 个规则版本、255 个档位 | 在 owner 库配置,不使用 activity 旧副本代替 | +| 首充奖励 | 开关 0,3 个档位全部 disabled | 开关 1,3 个 active | 确认奖励后启用并走真实充值事实 | +| 累充奖励 | 开关 0,8 个档位全部 disabled | 开关 1,8 个 active | 确认周期和奖励后启用 | +| 七日签到 | 开关 0,无生效版本,无奖励 | 开关 1,有生效版本和 7 天奖励 | 创建 Fami 版本和 7 天奖励后启用 | +| CP 周榜 | 开关 0,无奖励 | 开关 1,3 个奖励 | 确认关系类型和奖励后启用 | +| 周星 | 当前 UTC 时间无 active 周期 | Lalu 有 1 个 active 周期 | 创建 Fami 周期、指定礼物和 Top 奖励 | +| 自研游戏运行数据 | 机器人 0,档位奖池 0 | dice 机器人 80,dice/rock 奖池 8 | 如 Fami 对用户开放自研游戏,先初始化机器人和奖池 | + +### 18.2 运营内容缺口 + +| 项目 | Fami 当前状态 | Lalu 对照 | +| --- | --- | --- | +| 首页 Banner | 公网 API `items=0` | 相同请求返回 10 条 | +| 弹窗 | 公网 API `items=0` | 1 条 | +| 开屏 | 公网 API `items=0` | 1 条 | +| 版本控制 | `version=""`、`build_number=0` | Android `1.0.9/109` | +| H5 入口 | 只有 1 条 app config,缺 23 个 `h5-links` | 24 条 app config | +| 第三方游戏目录 | 34 个 active | 37 个 active;Fami 少 `Rocket`、`TeenPattiPro`、`LavaSlot` | +| 成长/成就展示资源 | 6 个头像框、25 个徽章档位和 32 个成就引用资源,但 Fami 无 active 资源 | Lalu 有 active 资源 | + +第三方游戏的三个差异需产品确认,不能在没有授权和产品结论时自动复制。 + +### 18.3 条件项 + +- 币安账户映射当前只有 Lalu。 +- TRC20 USDT 收款地址当前只有 Lalu。 +- Fami 已有 16 个 active 充值商品和对齐的第三方支付方式;上述两项只在产品要求 Fami 开放对应渠道时才是阻断。 + +### 18.4 已就绪项 + +- Fami App 注册状态为 active。 +- Firebase 项目已配置。 +- 16 个 cron 任务均已启用且 `app_codes` 包含 `lalu/fami/huwaa`。 +- Fami 和 Lalu 均有 141 个 enabled 国家,启用国家无缺口。 +- Firebase、登录风控、CP 关系配置已建立。 +- 红包、房间火箭、邀请奖励、注册金币奖励、房间流水奖励已开启。 +- 财富/魅力/游戏成长等级的规则数量已与 Lalu 对齐,但展示资源仍受资源体系缺失阻断。 +- room、wallet、user、game、statistics 的共享 MQ/outbox 主开关已开启。 + +### 18.5 Fami 建议收敛顺序 + +1. 重建 Fami 礼物、价格、资源、资源组和商城,修复成长/成就展示引用。 +2. 修复 RTC callback 的 App 归属,发布 activity 多 App 播报 claim/建群逻辑。 +3. 在 `lucky-gift-service` owner 库配置 Fami 规则和档位,完成独立 MQ consumer 验证。 +4. 完成 VIP 价格/权益/资源后启用 VIP1–VIP9。 +5. 按产品范围启用首充、累充、七日签到、CP 周榜和周星。 +6. 补齐 Banner、弹窗、开屏、版本和 H5 入口。 +7. 确认 Fami 游戏清单;如开放自研游戏,初始化机器人和档位奖池。 +8. 执行第 17 节的真实设备、业务、MQ、数据与统计验收,确认 P0 为 0 后再标记可正式运营。 + +### 18.6 首批修复记录(2026-07-13) + +本次只收敛 RTC/IM 业务租户隔离和幸运礼物初始化,不代表第 18.1 节其他 P0 已解除。 + +| 项目 | 修复结果 | 验证证据 | 状态 | +| --- | --- | --- | --- | +| RTC callback | 共享腾讯 RTC 应用不再从 URL 固定读取 `app_code=lalu`;gateway 验签后按 `room_id` 调 room-service 解析真实房间租户,再提交对应 Room Cell | 两台 gateway 的 callback URL 均已改为无查询参数地址;`rooms.room_id` 无重复并已增加唯一索引;gateway/room 双节点运行镜像 `20260713-fami-app-scope-1` 且 healthy | `CONFIGURED`;仍需 Fami/Lalu 各一次真实设备上麦、退流回调后升为 `VERIFIED` | +| IM 播报 worker | 同一腾讯 IM SDKAppID 下,群 ID、outbox claim、发送、mark 和 reconcile 均按记录 `app_code` 隔离;`broadcast.app_codes` 显式配置 `lalu/fami/huwaa` | 腾讯 IM 云端已存在 `hy_fami_bc_g_v2` 及区域 24–30 的 7 个 `hy_fami_bc_r_v2_*` 群;activity 双节点运行镜像 `20260713-fami-app-scope-1` 且无 reconcile 错误 | `VERIFIED`(建群与运行态);仍需客户端真实收一条区域/全局播报完成产品验收 | +| 幸运礼物规则 | 从 Lalu 复制所有规则版本和阶段档位到 Fami,只变更租户键 | Fami `lucky` 为 6 个规则版本/106 个档位;`super_lucky` 为 9 个规则版本/149 个档位,enabled 数量与 Lalu 一致 | `VERIFIED`(配置数据) | +| 幸运礼物奖池 | 未复制 Lalu 奖池状态;Fami 的 `lucky`、`super_lucky` 各自独立初始化 | 两个池均为 `balance=100000`、`reserve_floor=21000`、`total_in=0`、`total_out=0`;Fami RTP 窗口、用户状态、抽奖记录和 outbox 均为 0 | `VERIFIED`(初始化数据);仍需真实普通奖/大奖/钱包/outbox 冒烟 | + +发布前已执行 `make proto`、`make test` 和 `docker compose config`;发布后 6 个实例均 healthy、restart count 为 0,相关 CLB 后端权重均恢复为 100。 diff --git a/docs/语音房区域房间列表架构.md b/docs/语音房区域房间列表架构.md index 6ed581a7..ce2ab8c7 100644 --- a/docs/语音房区域房间列表架构.md +++ b/docs/语音房区域房间列表架构.md @@ -1,6 +1,6 @@ # Voice Room Region Room List Architecture -本文档定义 App 房间列表的区域化架构。目标是让同一区域内的国家看到同一套房间列表,不同区域看到不同列表,同时不破坏现有 `Room Cell` 单房间状态 owner 模型。 +本文档定义 App 房间列表的区域化架构。默认 App 仍按用户区域隔离;Huwaa 首页默认读取全部区域,并允许客户端使用服务端目录下发的 active `region_id` 或 enabled `country_code` 做单一筛选。这些列表范围不改变现有 `Room Cell` 单房间状态 owner 模型。 本文的区域隔离始终发生在单个 `app_code` 内。不同 App 即使使用相同国家、区域和 `room_id`,房间列表也必须互相不可见。 @@ -13,8 +13,8 @@ | App 国家/区域解析 | `user-service` | App 注册、改国家和用户投影只读 enabled 国家与 active 区域映射 | | 用户区域归属 | `user-service` | 注册和改国家后写入 `users.region_id` | | 房间可见区域 | `room-service` | 房间创建时绑定 `visible_region_id` | -| 房间列表读模型 | `room-service` | 按 `visible_region_id` 查询列表卡片 | -| HTTP 列表入口 | `gateway-service` | 鉴权后解析用户区域并调用 room-service | +| 房间列表读模型 | `room-service` | 按默认区域、All、指定区域或指定国家查询列表卡片 | +| HTTP 列表入口 | `gateway-service` | 鉴权后解析 App 策略,并用 active 区域目录校验客户筛选 | | 实时进房校验 | `room-service` | 列表只负责发现,进入房间仍以 `JoinRoom` 为准 | 本阶段不做: @@ -22,15 +22,15 @@ | Excluded | Reason | | --- | --- | | 推荐系统 | 首版先实现区域隔离和稳定排序,不做个性化推荐 | -| 跨区域混排 | 产品语义要求不同区域列表隔离 | -| 客户端提交 `region_id` | 区域必须由服务端按用户国家计算,避免伪造 | +| 非 Huwaa App 客户端提交 `region_id` | 这些 App 仍按用户国家投影的服务端区域隔离 | +| Huwaa 客户端提交任意区域/国家值 | 只接受 `/api/v1/rooms/filters` 中 active 区域和 enabled 国家的字段值 | | IP 国家决定房间列表 | `country_by_ip` 只用于审计和风控辅助,不代表用户选择国家 | | 把列表塞进 Room Cell | Room Cell 是单房间高频状态 owner,不承担多房间列表查询 | | 创建者改国家自动迁移房间 | 房间可见区域是创建时确定的业务属性,避免直播中列表突然漂移 | ## Core Rule -房间列表按 `region_id` 隔离,而不是按 `country` 隔离。 +非 Huwaa 房间列表默认按 `region_id` 隔离,而不是按 `country` 隔离。Huwaa All 会合并所有区域;普通房在置顶之后固定排成“本国家有人 → 其他国家/区域有人 → 本国家没人 → 其他国家/区域没人”。 例如: @@ -89,10 +89,10 @@ sequenceDiagram participant R as room-service participant S as MySQL/Redis - C->>G: GET /api/v1/rooms?tab=hot&limit=20 + C->>G: GET /api/v1/rooms?tab=hot&limit=20[®ion_id|country_code] G->>U: GetUser(user_id) U-->>G: region_id - G->>R: ListRooms(region_id, tab, cursor, limit) + G->>R: ListRooms(viewer_region_id, all/filter_region_id/country_code, tab, cursor, limit) R->>S: Read room_list_entries + active room_region_pins S-->>R: RoomListItem[] R-->>G: ListRoomsResponse @@ -107,11 +107,12 @@ gateway -> room-service.JoinRoom -> Tencent IM join group callback guard 因此列表允许秒级最终一致;真正能否进入房间由 `JoinRoom` 和 IM 守卫决定。 -区域置顶只影响当前 `visible_region_id` 的公共发现列表排序。`room_region_pins` 保存 `app_code + visible_region_id + room_id` 的运营置顶关系;查询时只 join `status=active` 且 `expires_at_ms > now_ms` 的记录,排序优先级为: +默认/指定区域列表只应用全区置顶、该 `visible_region_id` 的区域置顶和已选国家置顶。Huwaa All 会合并所有区域,但每个区域/国家置顶仍只能命中房间自身 `visible_region_id` 和房主国家,不会把某区置顶扩散到其他区域。`room_region_pins` 查询只使用 `status=active` 且 `expires_at_ms > now_ms` 的记录,排序优先级为: -1. 有效区域置顶在普通房间之前。 -2. 置顶房间按 `weight DESC, expires_at_ms DESC, room_id ASC`。 -3. 普通房间继续按 `hot` 的 `sort_score DESC` 或 `new` 的 `created_at_ms DESC`。 +1. 同一房间同时命中多种置顶时固定选 `global > region > country`,次级类型不能用更高 weight 反超。 +2. 有效全区/区域置顶在国家置顶和普通房间之前。 +3. 选定置顶类型后,房间按 `weight DESC, expires_at_ms DESC, room_id ASC`。 +4. 普通房间继续按本国家/在线桶及 `hot` 的 `sort_score DESC` 或 `new` 的 `created_at_ms DESC`。 置顶不进入 Room Cell、command log 或 snapshot;它是房间列表读模型的运营控制面。房间改区域后,旧区域置顶不会自动跨区域生效,后台需要在新区域重新建立置顶。 @@ -287,6 +288,8 @@ sort_score = heat * 1000 + online_count * 100 + occupied_seat_count * 10 + fresh ```text GET /api/v1/rooms?tab=hot&cursor=&limit=20 GET /api/v1/rooms?tab=new&cursor=&limit=20 +GET /api/v1/rooms?tab=hot®ion_id=2002 +GET /api/v1/rooms?tab=hot&country_code=BD ``` 响应继续使用 gateway envelope: @@ -322,6 +325,8 @@ GET /api/v1/rooms?tab=new&cursor=&limit=20 - `limit` 默认 `20`,最大 `50`。 - `cursor` 使用不透明字符串,客户端不能解析或拼装。 - gateway 从 access token 得到 `user_id`,再查 user-service 获取 `region_id`。 +- Huwaa 不传 `region_id/country_code` 表示 All;两者互斥,且只能回传 `/api/v1/rooms/filters` 的字段值。 +- 显式 `region_id` 写入不透明 cursor,不允许把 A 区游标复用到 B 区。 - 如果 user-service 查询失败,gateway 应返回错误,不应该用 IP 国家临时兜底。 - `im_group_id` 当前等于 `room_id`,只用于客户端准备腾讯 IM 房间群;真正入群必须等 `JoinRoom` 成功后由 IM 回调守卫校验 presence。 @@ -341,12 +346,16 @@ message ListRoomsRequest { string tab = 4; string cursor = 5; int32 limit = 6; + string query = 7; + string country_code = 8; + string viewer_country_code = 9; + bool all_visible_regions = 10; + int64 filter_region_id = 11; } message RoomListItem { string room_id = 1; int64 owner_user_id = 2; - int64 host_user_id = 3; string title = 4; string cover_url = 5; string mode = 6; @@ -359,6 +368,7 @@ message RoomListItem { string app_code = 13; string room_short_id = 14; bool locked = 15; + string country_code = 16; } message ListRoomsResponse { @@ -371,7 +381,7 @@ message ListRoomsResponse { - 新增 proto 字段只能追加,不能复用字段号。 - `visible_region_id` 使用 `int64`,`0` 表示 `GLOBAL`。 -- 不要把 `country` 传给 room-service 列表接口;国家到区域的映射不属于 room-service。 +- 客户端原始 `country/country_code` 不能直传 room-service;gateway 先校验 enabled 国家与 active 区域映射,再传规范化的 `country_code` 给列表读模型。 - `app_code` 只从 `RequestMeta` 来;客户端不能在列表 query/body 里自报另一个 App。 ## Write Path Updates @@ -409,8 +419,8 @@ message ListRoomsResponse { 不允许: -- 用户看到其他区域专属列表。 -- 客户端通过伪造 `region_id` 越权获取列表。 +- 非 Huwaa/无 All 权限的用户看到其他区域专属列表。 +- Huwaa 客户端传入不在 `/api/v1/rooms/filters` active 目录中的 `region_id`,或同时传 `region_id/country_code`。 - 列表查询直接访问 Room Cell 全量内存。 - Redis 成为唯一数据源。 diff --git a/docs/送礼连击V2协议.md b/docs/送礼连击V2协议.md new file mode 100644 index 00000000..ddf12f08 --- /dev/null +++ b/docs/送礼连击V2协议.md @@ -0,0 +1,94 @@ +# 送礼连击 V2 协议 + +## 边界 + +V2 只改变客户端封批、可靠重试和结算结果重放,不改变房间状态 owner: + +- Flutter 立即播放连击反馈,第一批立即请求,后续点击按远程窗口聚合。 +- HTTP 仍是账务入口;`room-service` 的 Room Cell 串行提交房间状态。 +- `wallet-service` 负责扣费,`lucky-gift-service` 负责逐份独立抽奖,`room-service` 只编排持久化 Saga。 +- V1 路由和顶层响应字段只为已发布的旧客户端保留;当前 Flutter 固定调用 V2,不做 V1 降级。 + +## HTTP 契约 + +新客户端调用: + +```text +POST /api/v2/rooms/gift/send +``` + +请求沿用 V1 字段,并要求: + +```json +{ + "room_id": "room-id", + "command_id": "stable-combo-session_b1", + "combo_session_id": "stable-combo-session", + "batch_seq": 1, + "target_type": "user", + "target_user_ids": [1001, 1002], + "gift_id": "gift-id", + "gift_count": 20, + "pool_id": "optional-lucky-pool" +} +``` + +`command_id` 是唯一财务幂等键。超时、429 和临时 5xx 必须复用同一请求体与同一 `command_id`;`combo_session_id + batch_seq` 只用于客户端批次顺序和审计。当前 Flutter 不在 404 或旧响应下改调 V1,发布前必须先保证 V2 路由可用。 + +响应继续返回 V1 顶层的 `billing_receipt_id`、`coin_balance_after`、`lucky_gift(s)`、`batch_display` 等字段,同时增加 `v2`: + +```json +{ + "billing_receipt_id": "legacy-compatible-receipt", + "coin_balance_after": 9000, + "v2": { + "api_version": 2, + "command_id": "stable-combo-session_b1", + "combo_session_id": "stable-combo-session", + "batch_seq": 1, + "idempotent_replay": false, + "committed_room_version": 123, + "billing_receipt_id": "legacy-compatible-receipt", + "coin_balance_after": 9000, + "balance_version": 456, + "settled_gift_count": 20, + "settled_target_user_ids": [1001, 1002], + "lucky_hits": [], + "operation_status": "committed" + } +} +``` + +相同 `command_id` 的重试返回首次提交的同一账务回执、余额版本、实际目标、幸运结果和批次展示,仅把 `idempotent_replay` 置为 `true`。历史 V1 command log 没有 typed 结果时保持原有 snapshot-only 行为,不伪造结算结果。 + +## 客户端封批规则 + +礼物面板响应的 `combo_config` 控制客户端策略: + +- `enabled`:紧急关闭微批;关闭后仍调用 V2,只是不再聚合连续点击。 +- `api_version`:服务端契约版本标记,当前 Flutter 只接受 V2,不用它选择 V1 入口。 +- `window_ms`:后续点击固定封批窗口,默认 300ms。 +- `max_gift_count_per_batch`:单目标数量上限,最大 999。 +- `max_gift_units_per_batch`:`gift_count * 去重收礼人数` 上限,最大 5000。 +- `max_in_flight`:最多 2 个 HTTP 批次在途。 +- `max_pending_batches`:本地有界队列上限。 +- `retry_max_attempts`、`retry_base_delay_ms`:可靠重试上限和指数退避基数;429 优先遵守 `Retry-After`。 +- `rollout_basis_points`:按 `app_code + user_id` 稳定分桶,0 到 10000;只灰度微批策略,不改变 V2 HTTP 入口。 + +后台通过以下 app-scoped 接口读取和全量替换配置: + +```text +GET /admin/app-config/gift-combo +PUT /admin/app-config/gift-combo +``` + +服务端在 gateway、room、wallet、lucky 四层都强制校验 `gift_count <= 999`、总 gift units `<= 5000` 和幂等键长度。远程配置只能收紧客户端行为,不能提高 owner 上限。 + +## 上线顺序 + +1. 先执行 `services/room-service/deploy/mysql/migrations/002_gift_operation_v2.sql`,再滚动发布 protobuf、wallet、lucky、room、gateway,确认 V2 可用后发布当前 Flutter;已发布的旧 Flutter 继续走服务端 V1。 +2. 首次上线把 `rollout_basis_points` 设为 0,验证 V2 单批发送、重复 `command_id` 重放和 Gift Saga 恢复指标;该值不会让当前 Flutter 回退 V1。 +3. 按 App 从小流量提高微批灰度;异常时把 `enabled=false` 或灰度归零,客户端仍使用 V2 单批发送。 +4. MQ 先保持 `wallet_event_tag_mode=legacy`、`room_event_tag_mode=legacy`。确认所有兼容 consumer 已上线并排空存量后,再分别切为 `event_type`;不要在同一批发布中同时切 producer 和删除 legacy selector。 + +数据库迁移、服务发布、Flutter 微批灰度和 MQ Tag 切换是四个独立开关。任何一步都不能用“客户端已限流”替代 owner 幂等、Saga 恢复或 Broker 侧过滤。 diff --git a/pkg/apptracking/limits.go b/pkg/apptracking/limits.go new file mode 100644 index 00000000..40090f05 --- /dev/null +++ b/pkg/apptracking/limits.go @@ -0,0 +1,68 @@ +// Package apptracking defines the transport and persistence limits shared by the +// public App tracking endpoint and statistics-service. +package apptracking + +import ( + "bytes" + "encoding/json" + "strings" +) + +const ( + // MaxBatchSize keeps one request bounded before gateway fans it into the + // statistics write transaction. + MaxBatchSize = 50 + // MaxPropertiesBytes bounds the raw JSON value after surrounding whitespace + // is removed. The value is rejected instead of truncated because truncation + // can change JSON meaning or produce invalid JSON. + MaxPropertiesBytes = 4096 + + // Text limits are byte limits, not rune limits. They mirror the durable + // app_tracking_events columns and therefore apply identically at both service + // boundaries; an oversized client value must never be deferred to MySQL. + MaxAppCodeBytes = 32 + MaxEventIDBytes = 160 + MaxEventNameBytes = 96 + MaxEventTypeBytes = 64 + MaxScreenBytes = 128 + MaxTargetTypeBytes = 64 + MaxTargetIDBytes = 128 + MaxDeviceIDBytes = 128 + MaxSessionIDBytes = 160 + MaxPlatformBytes = 32 + MaxAppVersionBytes = 64 + MaxLanguageBytes = 32 + MaxTimezoneBytes = 64 + MaxErrorCodeBytes = 64 +) + +// ValidBatchSize rejects both empty reports and reports large enough to hold a +// statistics transaction open for an unbounded period. +func ValidBatchSize(size int) bool { + return size > 0 && size <= MaxBatchSize +} + +// NormalizeText removes transport-only surrounding whitespace and checks the +// encoded byte length. Oversized values return false and are never shortened: +// identifiers and error codes are facts whose suffix may be significant. +func NormalizeText(value string, maxBytes int) (string, bool) { + value = strings.TrimSpace(value) + if maxBytes > 0 && len(value) > maxBytes { + return "", false + } + return value, true +} + +// NormalizeProperties returns a detached, trimmed JSON value. Empty and JSON +// null both mean no properties; non-empty values must satisfy the same byte and +// syntax contract in gateway and statistics-service. +func NormalizeProperties(raw json.RawMessage) (json.RawMessage, bool) { + trimmed := bytes.TrimSpace(raw) + if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) { + return nil, true + } + if len(trimmed) > MaxPropertiesBytes || !json.Valid(trimmed) { + return nil, false + } + return json.RawMessage(bytes.Clone(trimmed)), true +} diff --git a/pkg/apptracking/limits_test.go b/pkg/apptracking/limits_test.go new file mode 100644 index 00000000..58c69207 --- /dev/null +++ b/pkg/apptracking/limits_test.go @@ -0,0 +1,36 @@ +package apptracking + +import ( + "encoding/json" + "strings" + "testing" +) + +func TestNormalizeTextUsesBytesAndNeverTruncates(t *testing.T) { + if got, ok := NormalizeText(" "+strings.Repeat("e", MaxErrorCodeBytes)+" ", MaxErrorCodeBytes); !ok || got != strings.Repeat("e", MaxErrorCodeBytes) { + t.Fatalf("text at byte limit must be trimmed but otherwise preserved: ok=%v got=%q", ok, got) + } + if got, ok := NormalizeText(strings.Repeat("e", 185), MaxErrorCodeBytes); ok || got != "" { + t.Fatalf("oversized error_code must be rejected, never truncated: ok=%v got=%q", ok, got) + } + // 一个汉字编码为三个 UTF-8 bytes;这里防止调用方误把字段限制实现成 rune 数。 + if got, ok := NormalizeText(strings.Repeat("界", 22), MaxErrorCodeBytes); ok || got != "" { + t.Fatalf("text limits must count UTF-8 bytes: ok=%v got=%q", ok, got) + } +} + +func TestNormalizePropertiesAndBatchContract(t *testing.T) { + if !ValidBatchSize(1) || !ValidBatchSize(MaxBatchSize) || ValidBatchSize(0) || ValidBatchSize(MaxBatchSize+1) { + t.Fatalf("batch bounds do not match the shared contract") + } + got, ok := NormalizeProperties(json.RawMessage(` {"screen":"home"} `)) + if !ok || string(got) != `{"screen":"home"}` { + t.Fatalf("valid properties mismatch: ok=%v got=%s", ok, got) + } + if got, ok := NormalizeProperties(json.RawMessage(`"` + strings.Repeat("x", MaxPropertiesBytes) + `"`)); ok || got != nil { + t.Fatalf("properties over the encoded byte limit must be rejected: ok=%v bytes=%d", ok, len(got)) + } + if got, ok := NormalizeProperties(json.RawMessage(`{"broken"`)); ok || got != nil { + t.Fatalf("invalid properties JSON must be rejected: ok=%v got=%s", ok, got) + } +} diff --git a/pkg/giftlimits/limits.go b/pkg/giftlimits/limits.go new file mode 100644 index 00000000..edb35da5 --- /dev/null +++ b/pkg/giftlimits/limits.go @@ -0,0 +1,32 @@ +// Package giftlimits defines the owner-enforced ceiling for one gift command. +// Runtime rate limits may be stricter, but no transport or service is allowed to raise these values +// independently because gift_count fan-out reaches wallet, lucky draw, Room Cell and outbox storage. +package giftlimits + +import "strings" + +const ( + MaxCommandIDBytes = 128 + MaxGiftCount = int64(999) + MaxTotalGiftUnits = int64(5_000) +) + +// ValidCommandID bounds the financial idempotency key before it reaches Redis/MySQL key material. +func ValidCommandID(commandID string) bool { + commandID = strings.TrimSpace(commandID) + return commandID != "" && len(commandID) <= MaxCommandIDBytes +} + +// WorkUnits calculates gift_count*unique_target_count without overflow and applies the cross-service +// hard ceiling. Callers must pass a deduplicated target count; wallet additionally rejects duplicates. +func WorkUnits(giftCount int64, uniqueTargetCount int) (int64, bool) { + if giftCount <= 0 || giftCount > MaxGiftCount || uniqueTargetCount <= 0 { + return 0, false + } + targetCount := int64(uniqueTargetCount) + if targetCount > MaxTotalGiftUnits/giftCount { + return 0, false + } + units := giftCount * targetCount + return units, units <= MaxTotalGiftUnits +} diff --git a/pkg/giftlimits/limits_test.go b/pkg/giftlimits/limits_test.go new file mode 100644 index 00000000..4cb832ef --- /dev/null +++ b/pkg/giftlimits/limits_test.go @@ -0,0 +1,29 @@ +package giftlimits + +import ( + "strings" + "testing" +) + +func TestWorkUnitsAppliesCountAndFanoutCeilings(t *testing.T) { + if units, ok := WorkUnits(999, 5); !ok || units != 4_995 { + t.Fatalf("valid maximum-near workload mismatch: units=%d ok=%t", units, ok) + } + for _, input := range []struct { + count int64 + targets int + }{{0, 1}, {1_000, 1}, {999, 6}, {1, 0}} { + if units, ok := WorkUnits(input.count, input.targets); ok || units != 0 { + t.Fatalf("invalid workload accepted: %+v units=%d", input, units) + } + } +} + +func TestValidCommandIDUsesTrimmedNonEmptyByteBound(t *testing.T) { + if !ValidCommandID(" combo-1:42 ") { + t.Fatal("normal stable combo command id was rejected") + } + if ValidCommandID(" ") || ValidCommandID(strings.Repeat("x", MaxCommandIDBytes+1)) { + t.Fatal("empty or oversized command id was accepted") + } +} diff --git a/pkg/luckygiftmq/messages.go b/pkg/luckygiftmq/messages.go new file mode 100644 index 00000000..9779e370 --- /dev/null +++ b/pkg/luckygiftmq/messages.go @@ -0,0 +1,143 @@ +// Package luckygiftmq defines the RocketMQ wire contract owned by lucky-gift-service. +package luckygiftmq + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + + "google.golang.org/protobuf/proto" + luckygifteventsv1 "hyapp.local/api/proto/events/luckygift/v1" +) + +const ( + MessageTypeLuckyGiftOutboxEvent = "lucky_gift_outbox_event" + EventTypeLuckyGiftDrawn = "LuckyGiftDrawn" + TagLuckyGiftDrawn = EventTypeLuckyGiftDrawn + + // LuckyGiftDisplayMinMultiplierPPM keeps room and regional top-banner policy on the same exact 10x boundary. + // The owner still publishes every granted positive reward; consumers apply this presentation threshold independently. + LuckyGiftDisplayMinMultiplierPPM int64 = 10_000_000 +) + +// LuckyGiftOutboxMessage is the inspectable JSON wrapper around one protobuf owner envelope. +// Metadata is duplicated outside envelope only for RocketMQ diagnostics and routing; the protobuf envelope remains authoritative. +type LuckyGiftOutboxMessage struct { + MessageType string `json:"message_type"` + AppCode string `json:"app_code"` + EventID string `json:"event_id"` + EventType string `json:"event_type"` + DrawID string `json:"draw_id"` + OccurredAtMS int64 `json:"occurred_at_ms"` + Envelope []byte `json:"envelope"` +} + +// EventTypeTag validates an owner-controlled event type before it becomes a broker-side typed Tag. +// Only registered fact names are accepted so malformed outbox data cannot publish to an unconsumed routing key. +func EventTypeTag(eventType string) (string, error) { + switch tag := strings.TrimSpace(eventType); tag { + case EventTypeLuckyGiftDrawn: + return tag, nil + case "": + return "", errors.New("lucky gift event_type tag is required") + default: + return "", fmt.Errorf("unsupported lucky gift event_type tag %q", eventType) + } +} + +// EncodeLuckyGiftOutboxMessage serializes the protobuf envelope without converting int64 business identifiers through JSON numbers. +func EncodeLuckyGiftOutboxMessage(envelope *luckygifteventsv1.EventEnvelope) ([]byte, error) { + if err := validateLuckyGiftEnvelope(envelope); err != nil { + return nil, err + } + envelopeBytes, err := proto.Marshal(envelope) + if err != nil { + return nil, err + } + return json.Marshal(LuckyGiftOutboxMessage{ + MessageType: MessageTypeLuckyGiftOutboxEvent, + AppCode: envelope.GetAppCode(), + EventID: envelope.GetEventId(), + EventType: envelope.GetEventType(), + DrawID: envelope.GetDrawId(), + OccurredAtMS: envelope.GetOccurredAtMs(), + Envelope: envelopeBytes, + }) +} + +// DecodeLuckyGiftOutboxMessage restores and validates the owner envelope. +// Wrapper/envelope equality is checked explicitly so operators never diagnose one set of visible metadata while consumers process another. +func DecodeLuckyGiftOutboxMessage(body []byte) (*luckygifteventsv1.EventEnvelope, LuckyGiftOutboxMessage, error) { + var message LuckyGiftOutboxMessage + if err := json.Unmarshal(body, &message); err != nil { + return nil, LuckyGiftOutboxMessage{}, err + } + if message.MessageType != MessageTypeLuckyGiftOutboxEvent { + return nil, LuckyGiftOutboxMessage{}, errors.New("unexpected lucky gift outbox message_type") + } + if len(message.Envelope) == 0 { + return nil, LuckyGiftOutboxMessage{}, errors.New("lucky gift outbox envelope is required") + } + var envelope luckygifteventsv1.EventEnvelope + if err := proto.Unmarshal(message.Envelope, &envelope); err != nil { + return nil, LuckyGiftOutboxMessage{}, err + } + if err := validateLuckyGiftEnvelope(&envelope); err != nil { + return nil, LuckyGiftOutboxMessage{}, err + } + if message.AppCode != envelope.GetAppCode() || + message.EventID != envelope.GetEventId() || + message.EventType != envelope.GetEventType() || + message.DrawID != envelope.GetDrawId() || + message.OccurredAtMS != envelope.GetOccurredAtMs() { + return nil, LuckyGiftOutboxMessage{}, errors.New("lucky gift outbox wrapper does not match envelope") + } + return &envelope, message, nil +} + +func validateLuckyGiftEnvelope(envelope *luckygifteventsv1.EventEnvelope) error { + if envelope == nil { + return errors.New("lucky gift outbox envelope is required") + } + if strings.TrimSpace(envelope.GetAppCode()) == "" || + strings.TrimSpace(envelope.GetEventId()) == "" || + strings.TrimSpace(envelope.GetDrawId()) == "" || + envelope.GetOccurredAtMs() <= 0 || + len(envelope.GetBody()) == 0 { + return errors.New("lucky gift outbox envelope is incomplete") + } + // draw_id 是一次中奖事实的天然幂等键;固定 event_id 规则后,所有消费者都能用同一个键收敛重投, + // 也避免某个 producer 临时生成随机 ID 导致 room/activity 各自重复派生外部飘屏。 + if envelope.GetEventId() != "lucky_gift_drawn:"+envelope.GetDrawId() { + return errors.New("lucky gift outbox event_id does not match draw_id") + } + if _, err := EventTypeTag(envelope.GetEventType()); err != nil { + return err + } + + // The current topic carries only granted winning facts. Validate the protobuf body here so poison data fails before broker publish + // and is retried from the owner outbox instead of being acknowledged and rejected independently by every consumer. + var drawn luckygifteventsv1.LuckyGiftDrawn + if err := proto.Unmarshal(envelope.GetBody(), &drawn); err != nil { + return fmt.Errorf("decode lucky gift drawn body: %w", err) + } + if strings.TrimSpace(drawn.GetDrawId()) == "" || + drawn.GetDrawId() != envelope.GetDrawId() || + strings.TrimSpace(drawn.GetCommandId()) == "" || + strings.TrimSpace(drawn.GetRoomId()) == "" || + strings.TrimSpace(drawn.GetGiftId()) == "" || + drawn.GetGiftCount() <= 0 || + drawn.GetSenderUserId() <= 0 || + drawn.GetTargetUserId() <= 0 || + drawn.GetCoinSpent() <= 0 || + drawn.GetMultiplierPpm() <= 0 || + drawn.GetEffectiveRewardCoins() <= 0 || + strings.ToLower(strings.TrimSpace(drawn.GetRewardStatus())) != "granted" || + drawn.GetDrawCreatedAtMs() <= 0 || + drawn.GetDrawCreatedAtMs() != envelope.GetOccurredAtMs() || + drawn.GetRewardGrantedAtMs() <= 0 { + return errors.New("lucky gift drawn body is incomplete") + } + return nil +} diff --git a/pkg/luckygiftmq/messages_test.go b/pkg/luckygiftmq/messages_test.go new file mode 100644 index 00000000..b69259eb --- /dev/null +++ b/pkg/luckygiftmq/messages_test.go @@ -0,0 +1,123 @@ +package luckygiftmq + +import ( + "encoding/json" + "testing" + + "google.golang.org/protobuf/proto" + luckygifteventsv1 "hyapp.local/api/proto/events/luckygift/v1" +) + +func TestLuckyGiftOutboxMessageRoundTrip(t *testing.T) { + envelope := validLuckyGiftEnvelope(t) + body, err := EncodeLuckyGiftOutboxMessage(envelope) + if err != nil { + t.Fatalf("EncodeLuckyGiftOutboxMessage failed: %v", err) + } + + decoded, message, err := DecodeLuckyGiftOutboxMessage(body) + if err != nil { + t.Fatalf("DecodeLuckyGiftOutboxMessage failed: %v", err) + } + if !proto.Equal(decoded, envelope) { + t.Fatalf("decoded envelope mismatch: got=%v want=%v", decoded, envelope) + } + if message.MessageType != MessageTypeLuckyGiftOutboxEvent || message.EventType != EventTypeLuckyGiftDrawn || message.DrawID != "draw-1" { + t.Fatalf("decoded wrapper metadata mismatch: %+v", message) + } +} + +func TestLuckyGiftOutboxMessageRejectsWrapperEnvelopeDrift(t *testing.T) { + envelope := validLuckyGiftEnvelope(t) + body, err := EncodeLuckyGiftOutboxMessage(envelope) + if err != nil { + t.Fatalf("EncodeLuckyGiftOutboxMessage failed: %v", err) + } + var message LuckyGiftOutboxMessage + if err := json.Unmarshal(body, &message); err != nil { + t.Fatalf("decode wrapper: %v", err) + } + message.DrawID = "draw-other" + tampered, err := json.Marshal(message) + if err != nil { + t.Fatalf("encode tampered wrapper: %v", err) + } + if _, _, err := DecodeLuckyGiftOutboxMessage(tampered); err == nil { + t.Fatal("wrapper/envelope drift must be rejected") + } +} + +func TestLuckyGiftOutboxMessageRejectsUnsettledFact(t *testing.T) { + envelope := validLuckyGiftEnvelope(t) + var drawn luckygifteventsv1.LuckyGiftDrawn + if err := proto.Unmarshal(envelope.GetBody(), &drawn); err != nil { + t.Fatalf("decode lucky body: %v", err) + } + drawn.RewardStatus = "pending" + body, err := proto.Marshal(&drawn) + if err != nil { + t.Fatalf("encode pending lucky body: %v", err) + } + envelope.Body = body + if _, err := EncodeLuckyGiftOutboxMessage(envelope); err == nil { + t.Fatal("pending reward must not be published as LuckyGiftDrawn") + } +} + +func TestLuckyGiftEventTypeTagAndDisplayBoundary(t *testing.T) { + tag, err := EventTypeTag(EventTypeLuckyGiftDrawn) + if err != nil || tag != TagLuckyGiftDrawn { + t.Fatalf("EventTypeTag result: tag=%q err=%v", tag, err) + } + if _, err := EventTypeTag("LuckyGiftUnknown"); err == nil { + t.Fatal("unsupported lucky gift event type must be rejected") + } + if LuckyGiftDisplayMinMultiplierPPM != 10_000_000 { + t.Fatalf("display threshold=%d, want exact 10x ppm", LuckyGiftDisplayMinMultiplierPPM) + } +} + +func validLuckyGiftEnvelope(t *testing.T) *luckygifteventsv1.EventEnvelope { + t.Helper() + const drawCreatedAtMS int64 = 1_783_940_398_274 + drawn := &luckygifteventsv1.LuckyGiftDrawn{ + DrawId: "draw-1", + CommandId: "command-1", + RoomId: "room-1", + PoolId: "default", + GiftId: "43", + GiftCount: 1, + SenderUserId: 123456, + TargetUserId: 654321, + SenderName: "Lucky User", + SenderAvatar: "https://cdn.example/avatar.png", + SenderDisplayUserId: "123456", + SenderPrettyDisplayUserId: "888888", + VisibleRegionId: 1, + CountryId: 86, + CoinSpent: 100, + RuleVersion: 7, + ExperiencePool: "normal", + SelectedTierId: "normal_50x", + MultiplierPpm: 50_000_000, + BaseRewardCoins: 5_000, + EffectiveRewardCoins: 5_000, + HighMultiplier: true, + RewardStatus: "granted", + WalletTransactionId: "wallet-tx-1", + DrawCreatedAtMs: drawCreatedAtMS, + RewardGrantedAtMs: drawCreatedAtMS + 20, + } + body, err := proto.Marshal(drawn) + if err != nil { + t.Fatalf("marshal lucky body: %v", err) + } + return &luckygifteventsv1.EventEnvelope{ + EventId: "lucky_gift_drawn:draw-1", + EventType: EventTypeLuckyGiftDrawn, + AppCode: "lalu", + DrawId: "draw-1", + OccurredAtMs: drawCreatedAtMS, + Body: body, + } +} diff --git a/pkg/outboxpartition/partitions.go b/pkg/outboxpartition/partitions.go new file mode 100644 index 00000000..67d01c05 --- /dev/null +++ b/pkg/outboxpartition/partitions.go @@ -0,0 +1,163 @@ +// Package outboxpartition provides the shared tenant scheduling rules used by +// owner-service outbox workers. It does not read or publish business facts; +// repositories retain ownership of indexed claims and delivery state. +package outboxpartition + +import ( + "context" + "fmt" + "sort" + "strings" + "sync" + "sync/atomic" + "time" + + "hyapp/pkg/appcode" +) + +const DefaultDiscoveryRefreshInterval = 5 * time.Minute + +// Partitions resolves the App partitions visible to one outbox lane and rotates +// the first partition across claims. A configured allowlist is immutable for the +// process lifetime; an empty allowlist falls back to low-frequency DB discovery. +type Partitions struct { + configured []string + refreshInterval time.Duration + + mu sync.Mutex + cached []string + hasCache bool + nextRefresh time.Time + cursor atomic.Uint64 +} + +// New creates a partition scheduler. Operators can stage a backlog safely by +// configuring only the Apps currently allowed to publish; an empty list keeps +// automatic discovery as the compatibility default for existing deployments. +func New(configured []string, refreshInterval time.Duration) *Partitions { + if refreshInterval <= 0 { + refreshInterval = DefaultDiscoveryRefreshInterval + } + return &Partitions{ + configured: Normalize(configured, false), + refreshInterval: refreshInterval, + } +} + +// Resolve returns configured Apps without touching MySQL. In discovery mode it +// refreshes at most once per interval across all local worker goroutines. A +// failed refresh returns the last successful list alongside the error so the +// caller can log the stale condition without stopping already-known partitions. +func (p *Partitions) Resolve(ctx context.Context, discover func(context.Context) ([]string, error)) ([]string, error) { + if p == nil { + return nil, nil + } + if len(p.configured) > 0 { + return append([]string(nil), p.configured...), nil + } + if discover == nil { + return nil, nil + } + + p.mu.Lock() + defer p.mu.Unlock() + now := time.Now().UTC() + if p.hasCache && now.Before(p.nextRefresh) { + return append([]string(nil), p.cached...), nil + } + + discovered, err := discover(ctx) + if err != nil { + // Move the next attempt out by the normal interval: a transient database + // error must not turn every hot poll into a DISTINCT query stampede. + p.nextRefresh = now.Add(p.refreshInterval) + if p.hasCache { + return append([]string(nil), p.cached...), err + } + return nil, err + } + p.cached = Normalize(discovered, true) + p.hasCache = true + p.nextRefresh = now.Add(p.refreshInterval) + return append([]string(nil), p.cached...), nil +} + +// Order rotates the first App atomically while preserving the remaining cyclic +// order. Multiple worker goroutines therefore share one fair cursor instead of +// independently preferring the first configured tenant. +func (p *Partitions) Order(appCodes []string) []string { + if len(appCodes) == 0 { + return nil + } + if p == nil { + return append([]string(nil), appCodes...) + } + start := int((p.cursor.Add(1) - 1) % uint64(len(appCodes))) + ordered := make([]string, 0, len(appCodes)) + for offset := 0; offset < len(appCodes); offset++ { + ordered = append(ordered, appCodes[(start+offset)%len(appCodes)]) + } + return ordered +} + +// Normalize canonicalizes tenant keys, removes blanks and duplicates, and can +// sort DB-discovered keys for deterministic rotation. Explicit configuration +// preserves operator order so staged rollouts remain readable and predictable. +func Normalize(values []string, sorted bool) []string { + normalized := make([]string, 0, len(values)) + seen := make(map[string]struct{}, len(values)) + for _, value := range values { + if strings.TrimSpace(value) == "" { + continue + } + value = appcode.Normalize(value) + if _, exists := seen[value]; exists { + continue + } + seen[value] = struct{}{} + normalized = append(normalized, value) + } + if sorted { + sort.Strings(normalized) + } + return normalized +} + +// ClaimFair gives every App one bounded first-pass quota, then lets non-empty +// partitions use capacity left by empty queues. Each callback remains App-scoped +// and is expected to perform its own indexed SKIP LOCKED transaction. +func ClaimFair[T any](ctx context.Context, orderedApps []string, limit int, claim func(context.Context, string, int) ([]T, error)) ([]T, error) { + if limit <= 0 || len(orderedApps) == 0 || claim == nil { + return nil, nil + } + quota := (limit + len(orderedApps) - 1) / len(orderedApps) + claimed := make([]T, 0, limit) + for pass := 0; pass < 2 && len(claimed) < limit; pass++ { + for _, app := range orderedApps { + remaining := limit - len(claimed) + if remaining <= 0 { + break + } + appLimit := remaining + if pass == 0 && appLimit > quota { + appLimit = quota + } + records, err := claim(ctx, app, appLimit) + if err != nil { + return nil, err + } + if len(records) > appLimit { + // Silently truncating would strand already-claimed rows until lease + // expiry. Treat repository limit violations as explicit worker errors. + return nil, fmt.Errorf("outbox claim for app %q returned %d records above limit %d", app, len(records), appLimit) + } + claimed = append(claimed, records...) + } + if pass == 0 && len(claimed) == 0 { + // A globally empty first pass must stop here; a second identical pass + // only doubles indexed polling queries and cannot discover new work. + break + } + } + return claimed, nil +} diff --git a/pkg/outboxpartition/partitions_test.go b/pkg/outboxpartition/partitions_test.go new file mode 100644 index 00000000..fcff7720 --- /dev/null +++ b/pkg/outboxpartition/partitions_test.go @@ -0,0 +1,96 @@ +package outboxpartition + +import ( + "context" + "errors" + "reflect" + "testing" + "time" +) + +func TestConfiguredPartitionsNormalizeAndSkipDiscovery(t *testing.T) { + partitions := New([]string{" LALU ", "fami", "HUWAA", "fami", ""}, time.Hour) + discoverCalls := 0 + apps, err := partitions.Resolve(context.Background(), func(context.Context) ([]string, error) { + discoverCalls++ + return nil, errors.New("must not run") + }) + if err != nil { + t.Fatalf("resolve configured partitions: %v", err) + } + if want := []string{"lalu", "fami", "huwaa"}; !reflect.DeepEqual(apps, want) { + t.Fatalf("configured apps = %v, want %v", apps, want) + } + if discoverCalls != 0 { + t.Fatalf("configured allowlist unexpectedly queried discovery %d times", discoverCalls) + } +} + +func TestDiscoveredPartitionsAreCachedAndStaleListSurvivesRefreshFailure(t *testing.T) { + partitions := New(nil, time.Hour) + discoverCalls := 0 + discover := func(context.Context) ([]string, error) { + discoverCalls++ + if discoverCalls == 1 { + return []string{"huwaa", " LALU ", "fami"}, nil + } + return nil, errors.New("mysql unavailable") + } + first, err := partitions.Resolve(context.Background(), discover) + if err != nil || !reflect.DeepEqual(first, []string{"fami", "huwaa", "lalu"}) { + t.Fatalf("initial discovery apps=%v err=%v", first, err) + } + second, err := partitions.Resolve(context.Background(), discover) + if err != nil || !reflect.DeepEqual(second, first) || discoverCalls != 1 { + t.Fatalf("cached discovery apps=%v err=%v calls=%d", second, err, discoverCalls) + } + + // Force only the refresh boundary; production reaches this state after the + // five-minute interval without exposing a public cache mutation API. + partitions.mu.Lock() + partitions.nextRefresh = time.Time{} + partitions.mu.Unlock() + stale, err := partitions.Resolve(context.Background(), discover) + if err == nil || !reflect.DeepEqual(stale, first) { + t.Fatalf("failed refresh must retain stale apps: apps=%v err=%v", stale, err) + } +} + +func TestClaimFairRotatesLaluFamiHuwaaAndSkipsEmptyQueue(t *testing.T) { + partitions := New([]string{"lalu", "fami", "huwaa"}, time.Hour) + queues := map[string][]string{ + "lalu": {"l1", "l2", "l3"}, + "fami": nil, + "huwaa": {"h1", "h2", "h3"}, + } + claim := func(_ context.Context, app string, limit int) ([]string, error) { + queue := queues[app] + if len(queue) < limit { + limit = len(queue) + } + result := append([]string(nil), queue[:limit]...) + queues[app] = queue[limit:] + return result, nil + } + + apps, err := partitions.Resolve(context.Background(), nil) + if err != nil { + t.Fatalf("resolve configured apps: %v", err) + } + first, err := ClaimFair(context.Background(), partitions.Order(apps), 3, claim) + if err != nil || !reflect.DeepEqual(first, []string{"l1", "h1", "l2"}) { + t.Fatalf("first fair claim records=%v err=%v", first, err) + } + second, err := ClaimFair(context.Background(), partitions.Order(apps), 3, claim) + if err != nil || !reflect.DeepEqual(second, []string{"h2", "l3", "h3"}) { + t.Fatalf("rotated fair claim records=%v err=%v", second, err) + } + emptyCalls := 0 + empty, err := ClaimFair[string](context.Background(), partitions.Order(apps), 3, func(context.Context, string, int) ([]string, error) { + emptyCalls++ + return nil, nil + }) + if err != nil || len(empty) != 0 || emptyCalls != 3 { + t.Fatalf("empty queues records=%v err=%v calls=%d", empty, err, emptyCalls) + } +} diff --git a/pkg/outboxreplay/replay.go b/pkg/outboxreplay/replay.go new file mode 100644 index 00000000..df8cfb14 --- /dev/null +++ b/pkg/outboxreplay/replay.go @@ -0,0 +1,88 @@ +// Package outboxreplay provides the process-level hard bound used by manual +// owner-outbox repairs. Normal workers intentionally keep polling; an operator +// replay must instead stop after an exact maximum even while the partition +// remains non-empty. +package outboxreplay + +import ( + "context" + "errors" + "fmt" + "strings" + + "hyapp/pkg/appcode" +) + +const ExecuteConfirmation = "REPLAY_EXACT_APP_OUTBOX_BATCH" + +// Scope is the immutable tenant and total-record bound for one process run. +// Limit is not a worker batch size: Run enforces it across every claim/publish +// iteration and returns once that many rows have been handled. +type Scope struct { + AppCode string + Limit int +} + +// Result reports whether the selected App partition became empty before the +// hard limit. Processed counts rows claimed by processOne, including a row that +// was released as retryable after a publish failure. +type Result struct { + Processed int + Drained bool +} + +// NewScope rejects an implicit tenant or an unbounded run before any database +// or MQ client is opened. maxLimit is command-specific so a service may impose +// a smaller operational envelope without weakening this shared contract. +func NewScope(rawAppCode string, limit int, maxLimit int) (Scope, error) { + if strings.TrimSpace(rawAppCode) == "" { + // appcode.Normalize intentionally preserves a legacy empty=>lalu default + // for request compatibility. An operator command must reject omission + // before normalization or a missing flag would target a real tenant. + return Scope{}, errors.New("app-code is required") + } + appCode := appcode.Normalize(rawAppCode) + if maxLimit <= 0 { + return Scope{}, errors.New("max limit must be positive") + } + if limit <= 0 || limit > maxLimit { + return Scope{}, fmt.Errorf("limit must be between 1 and %d", maxLimit) + } + return Scope{AppCode: appCode, Limit: limit}, nil +} + +// Run calls processOne at most Scope.Limit times. processOne must use the +// owner's ordinary indexed claim path with an effective batch size of one; +// accepting a larger count here would let an implementation silently strand +// already-claimed tail rows after this process reaches its advertised limit. +func Run(ctx context.Context, scope Scope, processOne func(context.Context) (int, error)) (Result, error) { + if ctx == nil { + ctx = context.Background() + } + if strings.TrimSpace(scope.AppCode) == "" || scope.Limit <= 0 { + return Result{}, errors.New("valid replay scope is required") + } + if processOne == nil { + return Result{}, errors.New("processOne is required") + } + + result := Result{} + for result.Processed < scope.Limit { + if err := ctx.Err(); err != nil { + return result, err + } + processed, err := processOne(ctx) + if processed < 0 || processed > 1 { + return result, fmt.Errorf("single-record replay processed %d rows", processed) + } + result.Processed += processed + if err != nil { + return result, err + } + if processed == 0 { + result.Drained = true + return result, nil + } + } + return result, nil +} diff --git a/pkg/outboxreplay/replay_test.go b/pkg/outboxreplay/replay_test.go new file mode 100644 index 00000000..28398ae2 --- /dev/null +++ b/pkg/outboxreplay/replay_test.go @@ -0,0 +1,81 @@ +package outboxreplay + +import ( + "context" + "errors" + "testing" +) + +func TestNewScopeNormalizesAndBoundsTenantReplay(t *testing.T) { + scope, err := NewScope(" HUWAA ", 3, 10) + if err != nil { + t.Fatalf("NewScope failed: %v", err) + } + if scope.AppCode != "huwaa" || scope.Limit != 3 { + t.Fatalf("scope mismatch: %+v", scope) + } + for _, test := range []struct { + name string + app string + limit int + max int + }{ + {name: "missing app", limit: 1, max: 10}, + {name: "zero limit", app: "huwaa", max: 10}, + {name: "above maximum", app: "huwaa", limit: 11, max: 10}, + {name: "invalid maximum", app: "huwaa", limit: 1}, + } { + t.Run(test.name, func(t *testing.T) { + if _, err := NewScope(test.app, test.limit, test.max); err == nil { + t.Fatal("expected validation error") + } + }) + } +} + +func TestRunStopsAtProcessWideLimit(t *testing.T) { + calls := 0 + result, err := Run(context.Background(), Scope{AppCode: "huwaa", Limit: 3}, func(context.Context) (int, error) { + calls++ + return 1, nil + }) + if err != nil { + t.Fatalf("Run failed: %v", err) + } + if calls != 3 || result.Processed != 3 || result.Drained { + t.Fatalf("hard-limit result mismatch: calls=%d result=%+v", calls, result) + } +} + +func TestRunStopsWhenPartitionDrains(t *testing.T) { + calls := 0 + result, err := Run(context.Background(), Scope{AppCode: "huwaa", Limit: 5}, func(context.Context) (int, error) { + calls++ + if calls == 3 { + return 0, nil + } + return 1, nil + }) + if err != nil { + t.Fatalf("Run failed: %v", err) + } + if calls != 3 || result.Processed != 2 || !result.Drained { + t.Fatalf("drained result mismatch: calls=%d result=%+v", calls, result) + } +} + +func TestRunCountsClaimedFailureAndRejectsBatchClaims(t *testing.T) { + publishErr := errors.New("publish failed") + result, err := Run(context.Background(), Scope{AppCode: "huwaa", Limit: 5}, func(context.Context) (int, error) { + return 1, publishErr + }) + if !errors.Is(err, publishErr) || result.Processed != 1 { + t.Fatalf("failed row result mismatch: result=%+v err=%v", result, err) + } + + if _, err := Run(context.Background(), Scope{AppCode: "huwaa", Limit: 5}, func(context.Context) (int, error) { + return 2, nil + }); err == nil { + t.Fatal("expected multi-row claim rejection") + } +} diff --git a/pkg/rocketmqx/rocketmqx.go b/pkg/rocketmqx/rocketmqx.go index 483178f9..5de4be1f 100644 --- a/pkg/rocketmqx/rocketmqx.go +++ b/pkg/rocketmqx/rocketmqx.go @@ -7,11 +7,14 @@ import ( "context" "errors" "fmt" + "log/slog" "net" "strings" "sync" "time" + "hyapp/pkg/logx" + rocketmq "github.com/apache/rocketmq-client-go/v2" "github.com/apache/rocketmq-client-go/v2/consumer" "github.com/apache/rocketmq-client-go/v2/primitive" @@ -178,6 +181,7 @@ func (p *Producer) SendSync(ctx context.Context, message Message) error { type Consumer struct { mu sync.Mutex client rocketmq.PushConsumer + group string started bool } @@ -218,7 +222,7 @@ func NewConsumer(cfg ConsumerConfig) (*Consumer, error) { if err != nil { return nil, err } - return &Consumer{client: client}, nil + return &Consumer{client: client, group: group}, nil } // Subscribe binds a topic/tag selector to a handler. Empty tag subscribes all. @@ -242,7 +246,7 @@ func (c *Consumer) Subscribe(topic string, tag string, handler Handler) error { if message == nil { continue } - err := handler(ctx, ConsumedMessage{ + consumed := ConsumedMessage{ Topic: message.Topic, Tag: message.GetTags(), Keys: message.GetKeys(), @@ -250,9 +254,23 @@ func (c *Consumer) Subscribe(topic string, tag string, handler Handler) error { Body: message.Body, Properties: message.GetProperties(), ReconsumeTimes: message.ReconsumeTimes, - }) - if err != nil { - return consumer.ConsumeRetryLater, err + } + if err := handler(ctx, consumed); err != nil { + // rocketmq-client-go v2.1.2 在 callback error 非空时会把同一 pull batch 的 MessageExt + // 整批交给日志格式化;其 Message.String 未持有 properties 锁,而并行消费 goroutine + // 同时写 CONSUME_START_TIME 会触发 runtime fatal,进程无法通过 recover 自救。这里仅记录 + // 已复制出的标量身份,不传 Message、Body 或 Properties,避免日志再次进入不安全 String。 + logx.Error(ctx, "rocketmq_consume_handler_failed", err, + slog.String("consumer_group", c.group), + slog.String("topic", consumed.Topic), + slog.String("tag", consumed.Tag), + slog.String("keys", consumed.Keys), + slog.String("msg_id", consumed.MsgID), + slog.Int("reconsume_times", int(consumed.ReconsumeTimes)), + ) + // ConsumeRetryLater 才是 SDK 的 send-back/retry 决策信号;callback error 仅驱动其 + // 危险的整批错误日志。返回 nil error 不会确认消息,仍保留原有重试与死信策略。 + return consumer.ConsumeRetryLater, nil } } return consumer.ConsumeSuccess, nil diff --git a/pkg/rocketmqx/rocketmqx_test.go b/pkg/rocketmqx/rocketmqx_test.go new file mode 100644 index 00000000..33d789bf --- /dev/null +++ b/pkg/rocketmqx/rocketmqx_test.go @@ -0,0 +1,94 @@ +package rocketmqx + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "strings" + "testing" + + "hyapp/pkg/logx" + + "github.com/apache/rocketmq-client-go/v2/consumer" + "github.com/apache/rocketmq-client-go/v2/primitive" +) + +type fakePushConsumer struct { + callback func(context.Context, ...*primitive.MessageExt) (consumer.ConsumeResult, error) +} + +func (f *fakePushConsumer) Start() error { return nil } +func (f *fakePushConsumer) Shutdown() error { return nil } +func (f *fakePushConsumer) Subscribe(_ string, _ consumer.MessageSelector, callback func(context.Context, ...*primitive.MessageExt) (consumer.ConsumeResult, error)) error { + f.callback = callback + return nil +} +func (f *fakePushConsumer) Unsubscribe(string) error { return nil } +func (f *fakePushConsumer) Suspend() {} +func (f *fakePushConsumer) Resume() {} +func (f *fakePushConsumer) GetOffsetDiffMap() map[string]int64 { return nil } + +func TestSubscribeHandlerErrorReturnsRetryLaterWithoutSDKError(t *testing.T) { + var output bytes.Buffer + if err := logx.Init(logx.Config{Service: "rocketmqx-test", Env: "test", Format: "json", Output: &output}); err != nil { + t.Fatalf("init test logger: %v", err) + } + + client := &fakePushConsumer{} + consumerWrapper := &Consumer{client: client, group: "group-safe-log"} + handlerErr := errors.New("projection unavailable") + if err := consumerWrapper.Subscribe("topic-safe-log", "tag-safe-log", func(context.Context, ConsumedMessage) error { + return handlerErr + }); err != nil { + t.Fatalf("subscribe: %v", err) + } + if client.callback == nil { + t.Fatal("subscribe callback was not captured") + } + + extended := &primitive.MessageExt{MsgId: "msg-safe-log", ReconsumeTimes: 3} + // 直接初始化嵌入的 Message,避免复制其 RWMutex;Queue 仍故意保持 nil,确保日志路径 + // 一旦把 MessageExt 交给 String 格式化就会在测试中立即暴露。 + extended.Topic = "topic-safe-log" + extended.Body = []byte("sensitive-body-must-not-be-logged") + extended.WithTag("tag-safe-log") + extended.WithKeys([]string{"event-safe-log"}) + extended.WithProperty("private-property", "secret-value-must-not-be-logged") + // Queue 故意保持 nil:MessageExt.String 会解引用 Queue 并 panic。callback 能完成并写日志, + // 因而同时锁定包装层只记录复制后的安全标量,绝不把 MessageExt 交给格式化器。 + result, callbackErr := client.callback(context.Background(), extended) + if result != consumer.ConsumeRetryLater { + t.Fatalf("consume result = %v, want ConsumeRetryLater", result) + } + if callbackErr != nil { + t.Fatalf("callback error = %v, want nil so SDK does not format the shared message batch", callbackErr) + } + + rawLog := strings.TrimSpace(output.String()) + var record map[string]any + if err := json.Unmarshal([]byte(rawLog), &record); err != nil { + t.Fatalf("decode safe consume log %q: %v", rawLog, err) + } + for key, want := range map[string]string{ + "msg": "rocketmq_consume_handler_failed", + "consumer_group": "group-safe-log", + "topic": "topic-safe-log", + "tag": "tag-safe-log", + "keys": "event-safe-log", + "msg_id": "msg-safe-log", + "error": handlerErr.Error(), + } { + if got := record[key]; got != want { + t.Errorf("log field %s = %#v, want %q", key, got, want) + } + } + if got := record["reconsume_times"]; got != float64(3) { + t.Errorf("log field reconsume_times = %#v, want 3", got) + } + for _, forbidden := range []string{"sensitive-body-must-not-be-logged", "private-property", "secret-value-must-not-be-logged"} { + if strings.Contains(rawLog, forbidden) { + t.Errorf("safe consume log leaked forbidden message data %q: %s", forbidden, rawLog) + } + } +} diff --git a/pkg/roommq/messages.go b/pkg/roommq/messages.go index b06e1ef7..c56f6041 100644 --- a/pkg/roommq/messages.go +++ b/pkg/roommq/messages.go @@ -4,6 +4,9 @@ package roommq import ( "encoding/json" "errors" + "fmt" + "sort" + "strings" "google.golang.org/protobuf/proto" roomeventsv1 "hyapp.local/api/proto/events/room/v1" @@ -13,10 +16,83 @@ const ( MessageTypeRoomOutboxEvent = "room_outbox_event" MessageTypeRoomRocketLaunchDue = "room_rocket_launch_due" - TagRoomOutboxEvent = "room_outbox_event" + // TagRoomOutboxEvent 是 typed tag 上线前所有房间事实共用的 legacy Tag。 + // 迁移窗口内 consumer 必须订阅 legacy || relevant typed tags,等所有旧消息 + // 和重试消息排空后才允许移除 legacy,不能直接切换造成存量事实不可见。 + TagRoomOutboxEvent = "room_outbox_event" + + // 以下事件类型是当前 room topic 下游实际使用的稳定事实名。producer 的 + // typed 模式直接使用 envelope.event_type,consumer 复用这些常量拼 selector, + // 避免 producer/consumer 两侧手写字符串漂移后静默漏消费。 + EventTypeRoomGiftSent = "RoomGiftSent" + EventTypeRoomUserJoined = "RoomUserJoined" + EventTypeRoomUserLeft = "RoomUserLeft" + EventTypeRoomUserKicked = "RoomUserKicked" + EventTypeRoomMicChanged = "RoomMicChanged" + EventTypeRoomRocketIgnited = "RoomRocketIgnited" + EventTypeRoomRocketRewardGranted = "RoomRocketRewardGranted" + EventTypeRoomPasswordChanged = "RoomPasswordChanged" + TagRoomRocketLaunchDue = "room_rocket_launch_due" ) +// EventTagMode 控制 room outbox 的 Broker Tag;它只改变 MQ 路由,不改变消息体协议。 +type EventTagMode string + +const ( + // EventTagModeLegacy 是安全默认值,保证缺少新配置的旧环境继续发布统一 Tag。 + EventTagModeLegacy EventTagMode = "legacy" + // EventTagModeEventType 只应在所有 consumer 完成兼容订阅滚动后显式启用。 + EventTagModeEventType EventTagMode = "event_type" +) + +// EventTypeTag 把一个受控的 room event_type 转成 RocketMQ Tag。事件类型来自 +// owner outbox 而不是用户输入,但仍需拒绝表达式操作符和标点,避免坏数据扩大 +// 订阅范围或发布成 consumer 无法安全表达的 Tag。 +func EventTypeTag(eventType string) (string, error) { + tag := strings.TrimSpace(eventType) + if tag == "" { + return "", errors.New("room event_type tag is required") + } + if tag == TagRoomOutboxEvent { + return "", errors.New("room event_type cannot reuse the legacy room tag") + } + for _, char := range tag { + if (char >= 'a' && char <= 'z') || + (char >= 'A' && char <= 'Z') || + (char >= '0' && char <= '9') || + char == '_' || char == '-' { + continue + } + return "", fmt.Errorf("room event_type %q cannot be used as a RocketMQ tag", eventType) + } + return tag, nil +} + +// LegacyCompatibleTagExpression 构造无损迁移 selector:legacy 分支消费切换前 +// 已存在 Broker 的消息,typed 分支只接收当前 consumer 真正处理的事件。排序和 +// 去重保证同一 consumer group 的所有副本注册完全相同的订阅元数据。 +func LegacyCompatibleTagExpression(eventTypes ...string) (string, error) { + typedTags := make([]string, 0, len(eventTypes)) + seen := make(map[string]struct{}, len(eventTypes)) + for _, eventType := range eventTypes { + tag, err := EventTypeTag(eventType) + if err != nil { + return "", err + } + if _, exists := seen[tag]; exists { + continue + } + seen[tag] = struct{}{} + typedTags = append(typedTags, tag) + } + if len(typedTags) == 0 { + return "", errors.New("at least one typed room event tag is required") + } + sort.Strings(typedTags) + return TagRoomOutboxEvent + " || " + strings.Join(typedTags, " || "), nil +} + // RoomOutboxMessage is the MQ representation of one durable room_outbox fact. type RoomOutboxMessage struct { MessageType string `json:"message_type"` diff --git a/pkg/roommq/messages_test.go b/pkg/roommq/messages_test.go index 5ab67f7c..64898bee 100644 --- a/pkg/roommq/messages_test.go +++ b/pkg/roommq/messages_test.go @@ -1,6 +1,7 @@ package roommq import ( + "strings" "testing" "google.golang.org/protobuf/proto" @@ -34,6 +35,59 @@ func TestRoomOutboxMessageRoundTrip(t *testing.T) { } } +func TestRoomEventTypeTagUsesExactTrimmedEventType(t *testing.T) { + tag, err := EventTypeTag(" RoomGiftSent ") + if err != nil { + t.Fatalf("EventTypeTag failed: %v", err) + } + if tag != EventTypeRoomGiftSent { + t.Fatalf("event tag = %q, want %q", tag, EventTypeRoomGiftSent) + } + + for _, invalid := range []string{"", TagRoomOutboxEvent, "Room GiftSent", "RoomGiftSent || *", "Room.GiftSent"} { + if _, err := EventTypeTag(invalid); err == nil { + t.Fatalf("EventTypeTag(%q) unexpectedly succeeded", invalid) + } + } +} + +func TestRoomLegacyCompatibleTagExpressionIsDeterministicAndDeduplicated(t *testing.T) { + first, err := LegacyCompatibleTagExpression( + EventTypeRoomUserJoined, + EventTypeRoomGiftSent, + EventTypeRoomUserJoined, + ) + if err != nil { + t.Fatalf("LegacyCompatibleTagExpression failed: %v", err) + } + second, err := LegacyCompatibleTagExpression(EventTypeRoomGiftSent, EventTypeRoomUserJoined) + if err != nil { + t.Fatalf("LegacyCompatibleTagExpression failed: %v", err) + } + if first != second { + t.Fatalf("expressions differ by input order: first=%q second=%q", first, second) + } + want := TagRoomOutboxEvent + " || " + EventTypeRoomGiftSent + " || " + EventTypeRoomUserJoined + if first != want { + t.Fatalf("expression = %q, want %q", first, want) + } + if strings.Count(first, EventTypeRoomUserJoined) != 1 { + t.Fatalf("typed tag was not deduplicated: %q", first) + } +} + +func TestRoomLegacyCompatibleTagExpressionRejectsUnsafeOrMissingTypedTags(t *testing.T) { + for _, eventTypes := range [][]string{ + nil, + {TagRoomOutboxEvent}, + {"RoomGiftSent || *"}, + } { + if _, err := LegacyCompatibleTagExpression(eventTypes...); err == nil { + t.Fatalf("LegacyCompatibleTagExpression(%v) unexpectedly succeeded", eventTypes) + } + } +} + func TestRoomRocketLaunchDueRoundTrip(t *testing.T) { encoded, err := EncodeRoomRocketLaunchDueMessage(RoomRocketLaunchDueMessage{ AppCode: "default", diff --git a/pkg/tencentim/rest_client.go b/pkg/tencentim/rest_client.go index 330355ab..6710f796 100644 --- a/pkg/tencentim/rest_client.go +++ b/pkg/tencentim/rest_client.go @@ -464,7 +464,14 @@ func (c *RESTClient) post(ctx context.Context, command string, payload any, out response, err := c.httpClient.Do(request) if err != nil { - return err + var transportErr *url.Error + if errors.As(err, &transportErr) { + // net/http 的 url.Error.Error 会拼入完整请求 URL,而腾讯 IM UserSig 位于 + // query string。只向上保留底层网络原因,既维持 errors.Is 的超时/取消 + // 语义,又避免日志和 notice last_error 持久化可用签名。 + return fmt.Errorf("tencent im request failed: %w", transportErr.Err) + } + return fmt.Errorf("tencent im request failed: %w", err) } defer response.Body.Close() diff --git a/pkg/tencentim/rest_client_test.go b/pkg/tencentim/rest_client_test.go index 501d597f..bfb06e09 100644 --- a/pkg/tencentim/rest_client_test.go +++ b/pkg/tencentim/rest_client_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "io" "net/http" "strings" @@ -345,6 +346,26 @@ func TestRESTClientKickUserBuildsTencentRequest(t *testing.T) { } } +func TestRESTClientTransportErrorDoesNotExposeUserSig(t *testing.T) { + client := newTestRESTClient(t, func(request *http.Request) (*http.Response, error) { + if request.URL.Query().Get("usersig") == "" { + t.Fatal("test request must contain the signed Tencent IM query") + } + return nil, errors.New("synthetic EOF") + }) + + err := client.KickUser(context.Background(), 10002) + if err == nil { + t.Fatal("expected transport failure") + } + if strings.Contains(err.Error(), "usersig=") || strings.Contains(err.Error(), "sdkappid=") { + t.Fatalf("transport error exposed signed request URL: %q", err.Error()) + } + if !strings.Contains(err.Error(), "synthetic EOF") { + t.Fatalf("transport cause must remain actionable: %q", err.Error()) + } +} + func newTestRESTClient(t *testing.T, roundTrip roundTripFunc) *RESTClient { t.Helper() diff --git a/pkg/walletmq/messages.go b/pkg/walletmq/messages.go index 062a8f86..8370637b 100644 --- a/pkg/walletmq/messages.go +++ b/pkg/walletmq/messages.go @@ -4,15 +4,92 @@ package walletmq import ( "encoding/json" "errors" + "fmt" + "sort" "strings" ) const ( MessageTypeWalletOutboxEvent = "wallet_outbox_event" + // TagWalletOutboxEvent 是 typed tag 上线前所有钱包事实共用的 legacy Tag。 + // 兼容窗口内消费者必须同时订阅它和自己需要的 EventType Tag,确保 producer + // 切换前已经进入 Broker 的旧消息仍会被同一 consumer group 完整消费。 TagWalletOutboxEvent = "wallet_outbox_event" + + // 以下常量只描述当前 wallet topic 消费者实际处理的事件类型。producer 使用 + // outbox.event_type 作为 Tag,因此消费者和消息体分派必须复用同一拼写,避免 + // Tag 过滤成功但 handler 因事件名漂移而静默跳过。 + EventTypeWalletRechargeRecorded = "WalletRechargeRecorded" + EventTypeWalletCoinSellerStockPurchased = "WalletCoinSellerStockPurchased" + EventTypeWalletLuckyGiftRewardCredited = "WalletLuckyGiftRewardCredited" + EventTypeWalletTaskRewardCredited = "WalletTaskRewardCredited" + EventTypeWalletWheelRewardCredited = "WalletWheelRewardCredited" + EventTypeWalletRoomTurnoverRewardCredited = "WalletRoomTurnoverRewardCredited" + EventTypeWalletInviteActivityRewardCredited = "WalletInviteActivityRewardCredited" + EventTypeWalletAgencyOpeningRewardCredited = "WalletAgencyOpeningRewardCredited" + EventTypeWalletGiftIncomeCredited = "WalletGiftIncomeCredited" + EventTypeWalletBalanceChanged = "WalletBalanceChanged" + EventTypeWalletGiftDebited = "WalletGiftDebited" + EventTypeWalletRedPacketCreated = "WalletRedPacketCreated" + EventTypeWalletRedPacketClaimed = "WalletRedPacketClaimed" + EventTypeWalletRedPacketRefunded = "WalletRedPacketRefunded" + EventTypeVIPActivated = "VipActivated" + EventTypeVIPDailyCoinRebateAvailable = "VipDailyCoinRebateAvailable" + EventTypeResourceGranted = "ResourceGranted" + EventTypeResourceGroupGranted = "ResourceGroupGranted" ) +// EventTypeTag converts one committed wallet event type into its RocketMQ Tag. +// Event types are controlled identifiers rather than user input; rejecting expression +// operators and punctuation here prevents a malformed outbox row from widening a +// subscription or being published under a Tag no consumer can express safely. +func EventTypeTag(eventType string) (string, error) { + tag := strings.TrimSpace(eventType) + if tag == "" { + return "", errors.New("wallet event_type tag is required") + } + if tag == TagWalletOutboxEvent { + return "", errors.New("wallet event_type cannot reuse the legacy wallet tag") + } + for _, char := range tag { + if (char >= 'a' && char <= 'z') || + (char >= 'A' && char <= 'Z') || + (char >= '0' && char <= '9') || + char == '_' || char == '-' { + continue + } + return "", fmt.Errorf("wallet event_type %q cannot be used as a RocketMQ tag", eventType) + } + return tag, nil +} + +// LegacyCompatibleTagExpression builds the compatibility selector used during +// the typed-tag migration: legacy messages remain consumable, while newly published +// messages are filtered by the exact event types handled by this consumer. +// Sorting and deduplication make the expression deterministic so every process in +// the same consumer group registers byte-for-byte identical subscription metadata. +func LegacyCompatibleTagExpression(eventTypes ...string) (string, error) { + typedTags := make([]string, 0, len(eventTypes)) + seen := make(map[string]struct{}, len(eventTypes)) + for _, eventType := range eventTypes { + tag, err := EventTypeTag(eventType) + if err != nil { + return "", err + } + if _, exists := seen[tag]; exists { + continue + } + seen[tag] = struct{}{} + typedTags = append(typedTags, tag) + } + if len(typedTags) == 0 { + return "", errors.New("at least one typed wallet event tag is required") + } + sort.Strings(typedTags) + return TagWalletOutboxEvent + " || " + strings.Join(typedTags, " || "), nil +} + // WalletOutboxMessage is the MQ representation of one committed wallet_outbox fact. type WalletOutboxMessage struct { MessageType string `json:"message_type"` diff --git a/pkg/walletmq/messages_test.go b/pkg/walletmq/messages_test.go index c31f021c..c638ee68 100644 --- a/pkg/walletmq/messages_test.go +++ b/pkg/walletmq/messages_test.go @@ -1,6 +1,9 @@ package walletmq -import "testing" +import ( + "strings" + "testing" +) func TestWalletOutboxMessageRoundTrip(t *testing.T) { encoded, err := EncodeWalletOutboxMessage(WalletOutboxMessage{ @@ -27,3 +30,59 @@ func TestWalletOutboxMessageRoundTrip(t *testing.T) { t.Fatalf("decoded message mismatch: %+v", decoded) } } + +func TestEventTypeTagUsesExactTrimmedEventType(t *testing.T) { + tag, err := EventTypeTag(" WalletRechargeRecorded ") + if err != nil { + t.Fatalf("EventTypeTag failed: %v", err) + } + if tag != EventTypeWalletRechargeRecorded { + t.Fatalf("event tag = %q, want %q", tag, EventTypeWalletRechargeRecorded) + } + + for _, invalid := range []string{"", TagWalletOutboxEvent, "Wallet RechargeRecorded", "WalletRechargeRecorded || *", "Wallet.RechargeRecorded"} { + if _, err := EventTypeTag(invalid); err == nil { + t.Fatalf("EventTypeTag(%q) unexpectedly succeeded", invalid) + } + } +} + +func TestLegacyCompatibleTagExpressionIsDeterministicAndDeduplicated(t *testing.T) { + first, err := LegacyCompatibleTagExpression( + EventTypeWalletGiftDebited, + EventTypeWalletRechargeRecorded, + EventTypeWalletGiftDebited, + ) + if err != nil { + t.Fatalf("LegacyCompatibleTagExpression failed: %v", err) + } + second, err := LegacyCompatibleTagExpression( + EventTypeWalletRechargeRecorded, + EventTypeWalletGiftDebited, + ) + if err != nil { + t.Fatalf("LegacyCompatibleTagExpression failed: %v", err) + } + if first != second { + t.Fatalf("expressions differ by input order: first=%q second=%q", first, second) + } + want := TagWalletOutboxEvent + " || " + EventTypeWalletGiftDebited + " || " + EventTypeWalletRechargeRecorded + if first != want { + t.Fatalf("expression = %q, want %q", first, want) + } + if strings.Count(first, EventTypeWalletGiftDebited) != 1 { + t.Fatalf("typed tag was not deduplicated: %q", first) + } +} + +func TestLegacyCompatibleTagExpressionRejectsUnsafeOrMissingTypedTags(t *testing.T) { + for _, eventTypes := range [][]string{ + nil, + {TagWalletOutboxEvent}, + {"WalletRechargeRecorded || *"}, + } { + if _, err := LegacyCompatibleTagExpression(eventTypes...); err == nil { + t.Fatalf("LegacyCompatibleTagExpression(%v) unexpectedly succeeded", eventTypes) + } + } +} diff --git a/pkg/xerr/catalog.go b/pkg/xerr/catalog.go index 85c3dc00..1eab22c0 100644 --- a/pkg/xerr/catalog.go +++ b/pkg/xerr/catalog.go @@ -83,6 +83,8 @@ var catalog = map[Code]Spec{ VIPLevelDisabled: spec(codes.FailedPrecondition, httpStatusConflict, VIPLevelDisabled, "vip level is disabled"), VIPDowngradeNotAllowed: spec(codes.FailedPrecondition, httpStatusConflict, VIPDowngradeNotAllowed, "vip downgrade is not allowed"), VIPRechargeRequired: spec(codes.FailedPrecondition, httpStatusConflict, VIPRechargeRequired, "vip recharge is required"), + VIPCoinRebateNotFound: spec(codes.NotFound, httpStatusNotFound, VIPCoinRebateNotFound, "vip coin rebate not found"), + VIPCoinRebateExpired: spec(codes.FailedPrecondition, httpStatusConflict, VIPCoinRebateExpired, "vip coin rebate expired"), RedPacketDisabled: spec(codes.FailedPrecondition, httpStatusConflict, RedPacketDisabled, "red packet is disabled"), RedPacketInvalidAmountTier: spec(codes.InvalidArgument, httpStatusBadRequest, RedPacketInvalidAmountTier, "invalid argument"), diff --git a/pkg/xerr/errors.go b/pkg/xerr/errors.go index db2db022..f761474e 100644 --- a/pkg/xerr/errors.go +++ b/pkg/xerr/errors.go @@ -100,6 +100,10 @@ const ( VIPDowngradeNotAllowed Code = "VIP_DOWNGRADE_NOT_ALLOWED" // VIPRechargeRequired 表示目标 VIP 等级需要用户先达到累计充值门槛。 VIPRechargeRequired Code = "VIP_RECHARGE_REQUIRED" + // VIPCoinRebateNotFound 表示返现不存在,或不属于当前 App/用户。 + VIPCoinRebateNotFound Code = "VIP_COIN_REBATE_NOT_FOUND" + // VIPCoinRebateExpired 表示返现已越过 UTC 次日日切的排他结束边界。 + VIPCoinRebateExpired Code = "VIP_COIN_REBATE_EXPIRED" // RedPacketDisabled 表示红包功能未启用。 RedPacketDisabled Code = "RED_PACKET_DISABLED" diff --git a/pkg/xerr/grpc.go b/pkg/xerr/grpc.go index ce09f661..a526601b 100644 --- a/pkg/xerr/grpc.go +++ b/pkg/xerr/grpc.go @@ -1,6 +1,7 @@ package xerr import ( + "context" "errors" "google.golang.org/genproto/googleapis/rpc/errdetails" @@ -27,6 +28,16 @@ func ToGRPCError(err error) error { return err } + // context 终止描述的是调用链生命周期,而不是业务内部故障。仓储层会直接返回 + // context.Canceled/DeadlineExceeded;若先走兜底 Internal,主动取消的并发 sibling + // 会在服务端被误报成 INTERNAL_ERROR,并把一次正常的快速失败伪装成数据库事故。 + if errors.Is(err, context.Canceled) { + return status.Error(codes.Canceled, context.Canceled.Error()) + } + if errors.Is(err, context.DeadlineExceeded) { + return status.Error(codes.DeadlineExceeded, context.DeadlineExceeded.Error()) + } + domainErr, ok := As(err) if !ok { domainErr = &Error{Code: Internal, Message: "internal error"} diff --git a/pkg/xerr/grpc_test.go b/pkg/xerr/grpc_test.go index 8f48e6ec..59e4004e 100644 --- a/pkg/xerr/grpc_test.go +++ b/pkg/xerr/grpc_test.go @@ -1,6 +1,8 @@ package xerr import ( + "context" + "fmt" "go/ast" "go/parser" "go/token" @@ -29,6 +31,29 @@ func TestToGRPCErrorCarriesReason(t *testing.T) { } } +// TestToGRPCErrorPreservesContextTermination 防止并发聚合器主动取消 sibling RPC 时, +// database/sql 返回的 context 错误被兜底成 Internal,污染告警并掩盖真正的首个失败。 +func TestToGRPCErrorPreservesContextTermination(t *testing.T) { + tests := []struct { + name string + err error + code codes.Code + }{ + {name: "canceled", err: context.Canceled, code: codes.Canceled}, + {name: "wrapped canceled", err: fmt.Errorf("query interrupted: %w", context.Canceled), code: codes.Canceled}, + {name: "deadline exceeded", err: context.DeadlineExceeded, code: codes.DeadlineExceeded}, + {name: "wrapped deadline exceeded", err: fmt.Errorf("query interrupted: %w", context.DeadlineExceeded), code: codes.DeadlineExceeded}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if got := status.Code(ToGRPCError(test.err)); got != test.code { + t.Fatalf("grpc code mismatch: got %s want %s", got, test.code) + } + }) + } +} + func TestCatalogCoversDeclaredCodes(t *testing.T) { declared := declaredCodesFromSource(t) diff --git a/scripts/apply-local-mysql-initdb.sh b/scripts/apply-local-mysql-initdb.sh index 98e90247..c23340d5 100755 --- a/scripts/apply-local-mysql-initdb.sh +++ b/scripts/apply-local-mysql-initdb.sh @@ -28,6 +28,15 @@ SQL_FILES=( "${LOCAL_INITDB_DIR}/999_local_grants.sql" ) +# initdb only creates missing tables; a reused local MySQL volume therefore +# needs incremental migrations as well. Keep the owned user-service migrations +# in this local bootstrap path so authentication schema changes (for example +# login_audit client-version fields) cannot leave a locally logged-in client +# receiving a misleading 401 during token refresh. +USER_MIGRATION_FILES=( + "services/user-service/deploy/mysql/migrations/012_login_audit_client_version.sql" +) + # Keep MySQL as the only required dependency for this script. Business services # are started after schema/grants are known to match the current repo. "${SCRIPT_DIR}/prepare-local-mysql-initdb.sh" @@ -61,3 +70,13 @@ for sql_file in "${SQL_FILES[@]}"; do printf 'applying mysql init file: %s\n' "${sql_file}" docker compose exec -T mysql mysql --default-character-set=utf8mb4 -h 127.0.0.1 -uroot -p"${MYSQL_ROOT_PASSWORD}" < "${sql_file}" done + +for sql_file in "${USER_MIGRATION_FILES[@]}"; do + if [[ ! -f "${sql_file}" ]]; then + printf 'skipping mysql migration because it is absent or not a regular file: %s\n' "${sql_file}" + continue + fi + + printf 'applying mysql migration: %s\n' "${sql_file}" + docker compose exec -T mysql mysql --default-character-set=utf8mb4 -h 127.0.0.1 -uroot -p"${MYSQL_ROOT_PASSWORD}" < "${sql_file}" +done diff --git a/scripts/apply-local-rocketmq-topics.sh b/scripts/apply-local-rocketmq-topics.sh index 83a96c43..07050dbe 100755 --- a/scripts/apply-local-rocketmq-topics.sh +++ b/scripts/apply-local-rocketmq-topics.sh @@ -44,6 +44,7 @@ TOPICS=( "hyapp_wallet_outbox" "hyapp_wallet_realtime_outbox" "hyapp_room_outbox" + "hyapp_lucky_gift_outbox" "hyapp_room_rocket_launch" "hyapp_user_outbox" "hyapp_message_action_outbox" @@ -102,8 +103,8 @@ fi # Topic creation is idempotent, but each updateTopic call shells into the # broker container and blocks local startup. Query once and only create missing -# topics; a warm `make run` then pays one cheap route read instead of seven -# management writes. +# topics; a warm `make run` then pays one cheap route read instead of repeating +# management writes for the full fixed topic set. topic_list="$(docker compose exec -T rocketmq-broker "${MQADMIN}" topicList -n "${NAMESRV}" 2>/dev/null || true)" missing_topics=() for topic in "${TOPICS[@]}"; do diff --git a/scripts/mysql/060_configurable_vip_program.sql b/scripts/mysql/060_configurable_vip_program.sql new file mode 100644 index 00000000..fdb21f5a --- /dev/null +++ b/scripts/mysql/060_configurable_vip_program.sql @@ -0,0 +1,360 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE hyapp_wallet; + +-- 本迁移只建立按 App 可配置的 VIP 规则和体验卡事实;不会为 Fami 猜测价格、有效期或资源组。 +CREATE TABLE IF NOT EXISTS vip_program_configs ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + program_type VARCHAR(32) NOT NULL COMMENT '体系类型:legacy_timed/tiered_privilege_v1', + level_count INT NOT NULL COMMENT '该 App 可配置等级数量', + same_level_expiry_policy VARCHAR(32) NOT NULL COMMENT '同级续费有效期策略:extend_remaining', + upgrade_expiry_policy VARCHAR(32) NOT NULL COMMENT '升级有效期策略:extend_remaining/replace_from_now', + downgrade_purchase_policy VARCHAR(32) NOT NULL COMMENT '降级购买策略:reject', + benefit_inheritance_policy VARCHAR(32) NOT NULL COMMENT '权益矩阵策略:当前固定 target_only,每级保存完整集合', + grant_mode VARCHAR(32) NOT NULL COMMENT '后台赠送语义:direct_membership/trial_card', + trial_card_enabled BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否允许体验卡背包和佩戴流程', + status VARCHAR(32) NOT NULL COMMENT '业务状态:active/disabled', + config_version BIGINT NOT NULL DEFAULT 1 COMMENT '配置版本;订单和会员状态持久化该快照版本', + updated_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '最后更新管理员 ID', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code), + KEY idx_vip_program_status (status, updated_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='按 App 配置的 VIP 体系规则'; + +CREATE TABLE IF NOT EXISTS vip_level_benefits ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + level INT NOT NULL COMMENT '拥有该权益的具体 VIP 等级', + benefit_code VARCHAR(96) NOT NULL COMMENT '稳定权益编码,跨服务权限判断只使用该值', + name VARCHAR(128) NOT NULL COMMENT '权益展示名称', + benefit_type VARCHAR(32) NOT NULL COMMENT '权益类型:decoration/function', + unlock_level INT NOT NULL COMMENT 'P1 初始解锁等级,仅用于展示来源,不参与运行时继承', + status VARCHAR(32) NOT NULL COMMENT '业务状态:active/disabled', + trial_enabled BOOLEAN NOT NULL DEFAULT TRUE COMMENT '体验卡是否可获得;金币返现必须为 false', + resource_id BIGINT NOT NULL DEFAULT 0 COMMENT '绑定资源 ID;0 表示尚未绑定或纯功能权益', + resource_type VARCHAR(32) NOT NULL DEFAULT '' COMMENT '绑定资源类型;空表示纯功能或待运营配置', + execution_scope VARCHAR(32) NOT NULL COMMENT '实际执行方:wallet/room/user/notice', + auto_equip BOOLEAN NOT NULL DEFAULT FALSE COMMENT '资源下发后是否自动佩戴最高等级样式', + sort_order INT NOT NULL DEFAULT 0 COMMENT '权益展示顺序', + metadata_json JSON NULL COMMENT '执行方扩展配置;初始 P1 权益不猜测参数', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, level, benefit_code), + KEY idx_vip_benefit_code (app_code, benefit_code, status, level), + KEY idx_vip_benefit_level_sort (app_code, level, status, sort_order) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='VIP 每等级完整权益矩阵'; + +-- 体验卡元数据和通用背包事实按 entitlement_id 一一对应;佩戴切换不更新卡片绝对过期时间。 +CREATE TABLE IF NOT EXISTS user_vip_trial_cards ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + trial_card_id VARCHAR(96) NOT NULL COMMENT '体验卡实例 ID', + command_id VARCHAR(128) NOT NULL COMMENT '发卡命令幂等 ID', + request_hash VARCHAR(128) NOT NULL COMMENT '发卡请求语义哈希', + user_id BIGINT NOT NULL COMMENT '持有用户 ID', + entitlement_id VARCHAR(96) NOT NULL COMMENT '通用背包权益实例 ID,也是佩戴入口标识', + resource_id BIGINT NOT NULL DEFAULT 0 COMMENT '可选资源 ID;0 表示直接按等级和时长发卡', + level INT NOT NULL COMMENT '体验卡 VIP 等级快照', + name VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'VIP 等级名称快照', + status VARCHAR(32) NOT NULL COMMENT '卡片业务状态:active/revoked', + duration_ms BIGINT NOT NULL COMMENT '发卡时长快照;佩戴切换不改变该值', + effective_at_ms BIGINT NOT NULL COMMENT '卡片开始计时,UTC epoch ms', + expires_at_ms BIGINT NOT NULL COMMENT '卡片绝对过期时间,UTC epoch ms', + grant_source VARCHAR(32) NOT NULL COMMENT '发放来源', + source_grant_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '通用资源发放 ID', + operator_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '发放操作人用户 ID', + reason VARCHAR(256) NOT NULL DEFAULT '' COMMENT '发放原因', + program_type VARCHAR(32) NOT NULL COMMENT '发卡时 VIP 体系快照', + config_version BIGINT NOT NULL COMMENT '发卡时 VIP 配置版本', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, trial_card_id), + UNIQUE KEY uk_vip_trial_card_command (app_code, command_id), + UNIQUE KEY uk_vip_trial_card_entitlement (app_code, entitlement_id), + KEY idx_vip_trial_card_user (app_code, user_id, status, expires_at_ms), + KEY idx_vip_trial_card_resource (app_code, resource_id, status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='VIP 体验卡背包元数据'; + +-- purchase、直接会员赠送与体验卡发放共享同一命令守卫,先串行幂等键再获取业务行锁。 +CREATE TABLE IF NOT EXISTS vip_command_locks ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + command_id VARCHAR(128) NOT NULL COMMENT 'VIP 写命令幂等 ID', + biz_type VARCHAR(64) NOT NULL COMMENT '命令类型:vip_purchase/vip_grant/vip_trial_card_grant', + request_hash VARCHAR(128) NOT NULL COMMENT '请求语义哈希', + created_at_ms BIGINT NOT NULL COMMENT '首次命令时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '最后写入时间,UTC epoch ms', + PRIMARY KEY (app_code, command_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='VIP 写命令串行化守卫'; + +-- 旧库按列逐项补齐;所有 ALTER 都可重复执行,且默认值保持 Lalu 的历史语义。 +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_vip_memberships' AND COLUMN_NAME = 'program_type') = 0, + 'ALTER TABLE user_vip_memberships ADD COLUMN program_type VARCHAR(32) NOT NULL DEFAULT ''legacy_timed'' COMMENT ''激活时使用的 VIP 体系快照'' AFTER status', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 历史成功命令必须在上线前进入同一守卫空间,否则升级后一段时间内仍可能走旧唯一键竞争路径。 +-- wallet_transactions 的业务索引以 app_code 为首列;这里显式限定当前已注册 App,避免生产迁移 +-- 因只按 biz_type 过滤退化为全表扫描。新 App 上线后的命令会直接经过运行时 command guard。 +INSERT IGNORE INTO vip_command_locks ( + app_code, command_id, biz_type, request_hash, created_at_ms, updated_at_ms +) +SELECT app_code, command_id, biz_type, request_hash, created_at_ms, updated_at_ms +FROM wallet_transactions +WHERE app_code IN ('lalu', 'fami', 'huwaa', 'yumi') + AND biz_type IN ('vip_purchase', 'vip_grant'); + +INSERT IGNORE INTO vip_command_locks ( + app_code, command_id, biz_type, request_hash, created_at_ms, updated_at_ms +) +SELECT app_code, command_id, 'vip_trial_card_grant', request_hash, created_at_ms, updated_at_ms +FROM user_vip_trial_cards; + +-- Lalu 显式保持现状;Fami 使用替换升级、体验卡赠送和每级完整权益集合。 +INSERT IGNORE INTO vip_program_configs ( + app_code, program_type, level_count, same_level_expiry_policy, upgrade_expiry_policy, + downgrade_purchase_policy, benefit_inheritance_policy, grant_mode, trial_card_enabled, + status, config_version, updated_by_admin_id, created_at_ms, updated_at_ms +) VALUES + ('lalu', 'legacy_timed', 10, 'extend_remaining', 'extend_remaining', + 'reject', 'target_only', 'direct_membership', FALSE, 'active', 1, 0, 0, 0), + ('fami', 'tiered_privilege_v1', 9, 'extend_remaining', 'replace_from_now', + 'reject', 'target_only', 'trial_card', TRUE, 'active', 1, 0, 0, 0); + +-- Fami 等级初始均不可购买;30 天只满足配置校验,价格与资源组保持 0,须由运营确认后启用。 +INSERT IGNORE INTO vip_levels ( + app_code, level, name, status, price_coin, duration_ms, reward_resource_group_id, + required_recharge_coin_amount, sort_order, created_at_ms, updated_at_ms +) VALUES + ('fami', 1, 'VIP1', 'disabled', 0, 2592000000, 0, 0, 10, 0, 0), + ('fami', 2, 'VIP2', 'disabled', 0, 2592000000, 0, 0, 20, 0, 0), + ('fami', 3, 'VIP3', 'disabled', 0, 2592000000, 0, 0, 30, 0, 0), + ('fami', 4, 'VIP4', 'disabled', 0, 2592000000, 0, 0, 40, 0, 0), + ('fami', 5, 'VIP5', 'disabled', 0, 2592000000, 0, 0, 50, 0, 0), + ('fami', 6, 'VIP6', 'disabled', 0, 2592000000, 0, 0, 60, 0, 0), + ('fami', 7, 'VIP7', 'disabled', 0, 2592000000, 0, 0, 70, 0, 0), + ('fami', 8, 'VIP8', 'disabled', 0, 2592000000, 0, 0, 80, 0, 0), + ('fami', 9, 'VIP9', 'disabled', 0, 2592000000, 0, 0, 90, 0, 0); + +-- 每级直接持久化最终权益集合,不在运行时根据 unlock_level 自动继承。 +INSERT IGNORE INTO vip_level_benefits ( + app_code, level, benefit_code, name, benefit_type, unlock_level, status, trial_enabled, + resource_id, resource_type, execution_scope, auto_equip, sort_order, metadata_json, + created_at_ms, updated_at_ms +) +SELECT + 'fami', levels.level, benefits.benefit_code, benefits.name, benefits.benefit_type, + benefits.unlock_level, 'active', benefits.trial_enabled, 0, '', benefits.execution_scope, + benefits.auto_equip, benefits.sort_order, + CASE + WHEN benefits.benefit_code = 'online_global_notice' + THEN JSON_OBJECT('message', '欢迎进入Fami,祝你有美好的一天') + ELSE NULL + END, + 0, 0 +FROM ( + SELECT 1 AS level UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 + UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 +) AS levels +INNER JOIN ( + SELECT 'vip_badge_identity' AS benefit_code, 'VIP标识' AS name, 'function' AS benefit_type, 1 AS unlock_level, TRUE AS trial_enabled, 'user' AS execution_scope, FALSE AS auto_equip, 10 AS sort_order + UNION ALL SELECT 'vip_medal', 'VIP勋章', 'decoration', 1, TRUE, 'user', TRUE, 20 + UNION ALL SELECT 'avatar_frame', 'VIP头像框', 'decoration', 1, TRUE, 'user', TRUE, 30 + UNION ALL SELECT 'vip_title', 'VIP称号', 'function', 1, TRUE, 'user', FALSE, 40 + UNION ALL SELECT 'chat_bubble', 'VIP聊天气泡', 'decoration', 2, TRUE, 'room', TRUE, 50 + UNION ALL SELECT 'vip_gift', 'VIP礼物', 'function', 2, TRUE, 'room', FALSE, 60 + UNION ALL SELECT 'entry_effect', 'VIP进场通知', 'function', 2, TRUE, 'room', FALSE, 70 + UNION ALL SELECT 'visitor_history', '查看访客', 'function', 2, TRUE, 'user', FALSE, 80 + UNION ALL SELECT 'voice_wave', 'VIP声波纹', 'decoration', 3, TRUE, 'room', TRUE, 90 + UNION ALL SELECT 'profile_card', 'VIP资料卡皮肤', 'decoration', 3, TRUE, 'user', TRUE, 100 + UNION ALL SELECT 'custom_room_background', '自定义房间背景', 'function', 3, TRUE, 'room', FALSE, 110 + UNION ALL SELECT 'room_image_message', '房间发图', 'function', 3, TRUE, 'room', FALSE, 120 + UNION ALL SELECT 'animated_avatar', '动态头像', 'function', 4, TRUE, 'user', FALSE, 130 + UNION ALL SELECT 'animated_room_cover', '动态房间封面', 'function', 4, TRUE, 'room', FALSE, 140 + UNION ALL SELECT 'mic_skin', '麦克风皮肤', 'decoration', 4, TRUE, 'room', TRUE, 150 + UNION ALL SELECT 'room_border', '房间边框', 'decoration', 4, TRUE, 'room', TRUE, 160 + UNION ALL SELECT 'vehicle', 'VIP座驾', 'decoration', 4, TRUE, 'room', TRUE, 170 + UNION ALL SELECT 'colored_room_name', 'VIP彩色房间名称', 'function', 5, TRUE, 'room', FALSE, 180 + UNION ALL SELECT 'colored_nickname', 'VIP彩色昵称', 'function', 5, TRUE, 'user', FALSE, 190 + UNION ALL SELECT 'colored_id', 'VIP彩色ID', 'function', 5, TRUE, 'user', FALSE, 200 + UNION ALL SELECT 'gift_tray_skin', '送礼托盘皮肤', 'function', 5, TRUE, 'room', FALSE, 210 + UNION ALL SELECT 'hide_profile_data', '隐藏个人数据', 'function', 6, TRUE, 'user', FALSE, 220 + UNION ALL SELECT 'custom_avatar_frame', '定制头框', 'function', 6, TRUE, 'user', FALSE, 230 + UNION ALL SELECT 'leaderboard_invisible', '榜单隐身', 'function', 6, TRUE, 'user', FALSE, 240 + UNION ALL SELECT 'daily_coin_rebate', '金币返现', 'function', 6, FALSE, 'wallet', FALSE, 250 + UNION ALL SELECT 'custom_profile_card', '定制资料卡', 'function', 7, TRUE, 'user', FALSE, 260 + UNION ALL SELECT 'anonymous_profile_visit', '匿名访问主页', 'function', 7, TRUE, 'user', FALSE, 270 + UNION ALL SELECT 'custom_gift', '定制礼物', 'function', 7, TRUE, 'room', FALSE, 280 + UNION ALL SELECT 'room_entry_notice', '进房高亮通知', 'function', 8, TRUE, 'room', FALSE, 290 + UNION ALL SELECT 'custom_pretty_id', '定制靓号', 'function', 8, TRUE, 'user', FALSE, 300 + UNION ALL SELECT 'custom_vehicle', '定制座驾', 'function', 8, TRUE, 'room', FALSE, 310 + UNION ALL SELECT 'anti_kick', '防踢', 'function', 9, TRUE, 'room', FALSE, 320 + UNION ALL SELECT 'anti_mute', '防禁言', 'function', 9, TRUE, 'room', FALSE, 330 + UNION ALL SELECT 'online_global_notice', '上线全服通知', 'function', 9, TRUE, 'notice', FALSE, 340 +) AS benefits ON benefits.unlock_level <= levels.level; + +-- 早期执行过本迁移的环境已有权益行;只给空元数据补默认文案,不覆盖运营已配置的 App 文案。 +UPDATE vip_level_benefits +SET metadata_json = JSON_OBJECT('message', '欢迎进入Fami,祝你有美好的一天') +WHERE app_code = 'fami' + AND benefit_code = 'online_global_notice' + AND COALESCE(JSON_LENGTH(metadata_json), 0) = 0; + +-- resource_id 使用 resources 自增主键;空素材和仅含 vip_level 的 metadata 明确等待运营补齐视觉资产。 +INSERT IGNORE INTO resources ( + app_code, resource_code, resource_type, name, status, grantable, manager_grant_enabled, + grant_strategy, wallet_asset_type, wallet_asset_amount, price_type, coin_price, gift_point_amount, + usage_scope_json, asset_url, preview_url, animation_url, metadata_json, sort_order, + created_by_user_id, updated_by_user_id, created_at_ms, updated_at_ms +) VALUES + ('fami', 'vip_trial_card_1', 'vip_trial_card', 'VIP1体验卡', 'active', TRUE, TRUE, 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":1}', 10, 0, 0, 0, 0), + ('fami', 'vip_trial_card_2', 'vip_trial_card', 'VIP2体验卡', 'active', TRUE, TRUE, 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":2}', 20, 0, 0, 0, 0), + ('fami', 'vip_trial_card_3', 'vip_trial_card', 'VIP3体验卡', 'active', TRUE, TRUE, 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":3}', 30, 0, 0, 0, 0), + ('fami', 'vip_trial_card_4', 'vip_trial_card', 'VIP4体验卡', 'active', TRUE, TRUE, 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":4}', 40, 0, 0, 0, 0), + ('fami', 'vip_trial_card_5', 'vip_trial_card', 'VIP5体验卡', 'active', TRUE, TRUE, 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":5}', 50, 0, 0, 0, 0), + ('fami', 'vip_trial_card_6', 'vip_trial_card', 'VIP6体验卡', 'active', TRUE, TRUE, 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":6}', 60, 0, 0, 0, 0), + ('fami', 'vip_trial_card_7', 'vip_trial_card', 'VIP7体验卡', 'active', TRUE, TRUE, 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":7}', 70, 0, 0, 0, 0), + ('fami', 'vip_trial_card_8', 'vip_trial_card', 'VIP8体验卡', 'active', TRUE, TRUE, 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":8}', 80, 0, 0, 0, 0), + ('fami', 'vip_trial_card_9', 'vip_trial_card', 'VIP9体验卡', 'active', TRUE, TRUE, 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":9}', 90, 0, 0, 0, 0); + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_vip_memberships' AND COLUMN_NAME = 'config_version') = 0, + 'ALTER TABLE user_vip_memberships ADD COLUMN config_version BIGINT NOT NULL DEFAULT 1 COMMENT ''激活时使用的 VIP 配置版本'' AFTER program_type', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vip_purchase_orders' AND COLUMN_NAME = 'previous_expires_at_ms') = 0, + 'ALTER TABLE vip_purchase_orders ADD COLUMN previous_expires_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''购买前会员过期时间快照,UTC epoch ms'' AFTER previous_level', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vip_purchase_orders' AND COLUMN_NAME = 'new_expires_at_ms') = 0, + 'ALTER TABLE vip_purchase_orders ADD COLUMN new_expires_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''购买后会员过期时间快照,UTC epoch ms'' AFTER previous_expires_at_ms', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vip_purchase_orders' AND COLUMN_NAME = 'duration_ms') = 0, + 'ALTER TABLE vip_purchase_orders ADD COLUMN duration_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''购买等级的有效期配置快照'' AFTER price_coin', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vip_purchase_orders' AND COLUMN_NAME = 'program_type') = 0, + 'ALTER TABLE vip_purchase_orders ADD COLUMN program_type VARCHAR(32) NOT NULL DEFAULT ''legacy_timed'' COMMENT ''订单执行的 VIP 体系快照'' AFTER duration_ms', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vip_purchase_orders' AND COLUMN_NAME = 'config_version') = 0, + 'ALTER TABLE vip_purchase_orders ADD COLUMN config_version BIGINT NOT NULL DEFAULT 1 COMMENT ''订单执行的 VIP 配置版本'' AFTER program_type', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vip_purchase_orders' AND COLUMN_NAME = 'expiry_policy') = 0, + 'ALTER TABLE vip_purchase_orders ADD COLUMN expiry_policy VARCHAR(32) NOT NULL DEFAULT ''extend_remaining'' COMMENT ''本单实际采用的有效期策略'' AFTER config_version', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_vip_history' AND COLUMN_NAME = 'previous_expires_at_ms') = 0, + 'ALTER TABLE user_vip_history ADD COLUMN previous_expires_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''变更前过期时间快照,UTC epoch ms'' AFTER new_level', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_vip_history' AND COLUMN_NAME = 'new_expires_at_ms') = 0, + 'ALTER TABLE user_vip_history ADD COLUMN new_expires_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''变更后过期时间快照,UTC epoch ms'' AFTER previous_expires_at_ms', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_vip_history' AND COLUMN_NAME = 'program_type') = 0, + 'ALTER TABLE user_vip_history ADD COLUMN program_type VARCHAR(32) NOT NULL DEFAULT ''legacy_timed'' COMMENT ''变更使用的 VIP 体系快照'' AFTER new_expires_at_ms', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_vip_history' AND COLUMN_NAME = 'config_version') = 0, + 'ALTER TABLE user_vip_history ADD COLUMN config_version BIGINT NOT NULL DEFAULT 1 COMMENT ''变更使用的 VIP 配置版本'' AFTER program_type', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 老库 history 在上述有效期列刚增加时只能得到 0,不能据此否定存量会员的日切资格。 +-- 仅对“完全没有新格式有效期快照”的当前会员写入一条迁移基线;previous_* 保持 0 +-- 明确表示迁移不猜测基线前状态。稳定 order_id 与 NOT EXISTS 使脚本重复执行不增行, +-- 已有完整新格式 history 的用户也不会被覆盖或改写。 +SET @vip_history_baseline_ms := CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED); + +INSERT INTO user_vip_history ( + app_code, user_id, action, previous_level, new_level, previous_expires_at_ms, + new_expires_at_ms, program_type, config_version, order_id, created_at_ms +) +SELECT + memberships.app_code, + memberships.user_id, + 'migration_baseline', + 0, + memberships.level, + 0, + memberships.expires_at_ms, + memberships.program_type, + memberships.config_version, + 'vip_configurable_060_baseline', + @vip_history_baseline_ms +FROM user_vip_memberships AS memberships +WHERE memberships.level > 0 + AND memberships.expires_at_ms > 0 + AND NOT EXISTS ( + SELECT 1 + FROM user_vip_history AS complete_history + WHERE complete_history.app_code = memberships.app_code + AND complete_history.user_id = memberships.user_id + AND complete_history.new_level > 0 + AND complete_history.new_expires_at_ms > 0 + ) + AND NOT EXISTS ( + SELECT 1 + FROM user_vip_history AS existing_baseline + WHERE existing_baseline.app_code = memberships.app_code + AND existing_baseline.user_id = memberships.user_id + AND existing_baseline.order_id = 'vip_configurable_060_baseline' + ); diff --git a/scripts/mysql/061_vip_daily_coin_rebate.sql b/scripts/mysql/061_vip_daily_coin_rebate.sql new file mode 100644 index 00000000..3683e28b --- /dev/null +++ b/scripts/mysql/061_vip_daily_coin_rebate.sql @@ -0,0 +1,78 @@ +-- VIP 每日金币返现由 wallet-service 独占:日 run 固化 UTC 日切金额矩阵,用户返现行保存资格、窗口和领取交易。 +-- 本迁移可重复执行;不猜测任一等级金额,历史空配置会安全关闭,等待运营填 metadata_json.coin_amount 后再启用。 + +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE hyapp_wallet; + +CREATE TABLE IF NOT EXISTS vip_daily_coin_rebate_runs ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + task_day CHAR(10) NOT NULL COMMENT 'UTC 日期,YYYY-MM-DD', + run_id VARCHAR(96) NOT NULL COMMENT '稳定批任务 ID', + day_start_ms BIGINT NOT NULL COMMENT 'UTC 当日开始,epoch ms', + day_end_ms BIGINT NOT NULL COMMENT 'UTC 次日开始,排他结束 epoch ms', + config_version BIGINT NOT NULL COMMENT '创建 run 时 VIP 配置版本', + level_amounts_json JSON NOT NULL COMMENT '等级到返现金额的不可变快照', + status VARCHAR(32) NOT NULL COMMENT 'running/completed', + last_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '已扫描会员游标', + scanned_count BIGINT NOT NULL DEFAULT 0 COMMENT '累计扫描会员数', + created_count BIGINT NOT NULL DEFAULT 0 COMMENT '累计创建返现数', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + completed_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '完成时间,UTC epoch ms', + PRIMARY KEY (app_code, task_day), + UNIQUE KEY uk_vip_rebate_run_id (app_code, run_id), + KEY idx_vip_rebate_run_status (app_code, status, task_day) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='VIP 每日金币返现运行快照'; + +CREATE TABLE IF NOT EXISTS vip_daily_coin_rebates ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + rebate_id VARCHAR(96) NOT NULL COMMENT '返现事实 ID', + run_id VARCHAR(96) NOT NULL COMMENT '来源日 run ID', + task_day CHAR(10) NOT NULL COMMENT 'UTC 日期,YYYY-MM-DD', + user_id BIGINT NOT NULL COMMENT '领取用户 ID', + vip_level INT NOT NULL COMMENT 'UTC 日切时付费 VIP 等级快照', + vip_name VARCHAR(64) NOT NULL COMMENT 'VIP 名称快照', + coin_amount BIGINT NOT NULL COMMENT '返现金币正整数快照', + status VARCHAR(32) NOT NULL COMMENT 'claimable/claimed;expired 由查询层投影', + available_at_ms BIGINT NOT NULL COMMENT '领取开始,UTC epoch ms', + expires_at_ms BIGINT NOT NULL COMMENT '领取排他结束,UTC epoch ms', + config_version BIGINT NOT NULL COMMENT '来源 run 的 VIP 配置版本', + claimed_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '实际领取时间,UTC epoch ms', + wallet_transaction_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '领取入账交易 ID', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, rebate_id), + UNIQUE KEY uk_vip_rebate_user_day (app_code, task_day, user_id), + KEY idx_vip_rebate_user_day (app_code, user_id, task_day, rebate_id), + KEY idx_vip_rebate_expiry (app_code, status, expires_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='VIP 每日金币返现领取事实'; + +UPDATE vip_level_benefits +SET status = 'disabled', trial_enabled = FALSE +WHERE benefit_code = 'daily_coin_rebate' + AND ( + JSON_EXTRACT(metadata_json, '$.coin_amount') IS NULL + OR JSON_TYPE(JSON_EXTRACT(metadata_json, '$.coin_amount')) <> 'INTEGER' + OR CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.coin_amount')) AS DECIMAL(65, 0)) <= 0 + OR CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.coin_amount')) AS DECIMAL(65, 0)) > 9223372036854775807 + ); + +UPDATE vip_level_benefits +SET trial_enabled = FALSE, execution_scope = 'wallet' +WHERE benefit_code = 'daily_coin_rebate'; + +UPDATE vip_level_benefits +SET execution_scope = 'notice' +WHERE benefit_code = 'online_global_notice'; + +-- 已经先上线过领取交易的环境补齐统一 VIP command guard;同 command 不能再被购买、发卡或另一返现复用。 +-- 显式限定当前 App 才能使用 wallet_transactions 的 (app_code,biz_type,created_at_ms) 索引; +-- 禁止在生产迁移中只按 biz_type 扫描完整钱包交易表。 +INSERT IGNORE INTO vip_command_locks ( + app_code, command_id, biz_type, request_hash, created_at_ms, updated_at_ms +) +SELECT app_code, command_id, biz_type, request_hash, created_at_ms, updated_at_ms +FROM wallet_transactions +WHERE app_code IN ('lalu', 'fami', 'huwaa', 'yumi') + AND biz_type = 'vip_daily_coin_rebate'; diff --git a/scripts/mysql/062_vip_user_settings.sql b/scripts/mysql/062_vip_user_settings.sql new file mode 100644 index 00000000..7b5f77d5 --- /dev/null +++ b/scripts/mysql/062_vip_user_settings.sql @@ -0,0 +1,15 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE hyapp_wallet; + +-- 用户级通知开关只保存偏好,不改变 VIP 资格或 effective_benefits;历史用户不批量补行, +-- 查询无记录时由 wallet-service 统一返回默认开启。 +CREATE TABLE IF NOT EXISTS user_vip_settings ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + user_id BIGINT NOT NULL COMMENT '用户 ID', + room_entry_notice_enabled BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否开启进房通知', + online_global_notice_enabled BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否开启上线全服通知', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户按 App 隔离的 VIP 功能开关'; diff --git a/scripts/mysql/063_google_paid_sync_retry.sql b/scripts/mysql/063_google_paid_sync_retry.sql new file mode 100644 index 00000000..52a8b7f4 --- /dev/null +++ b/scripts/mysql/063_google_paid_sync_retry.sql @@ -0,0 +1,7 @@ +-- Durable Google paid-detail retry state for wallet-service. +-- Existing paid_synced_at_ms=0 rows become immediately eligible for the 15-second worker. +ALTER TABLE payment_orders + ADD COLUMN paid_sync_attempt_count INT NOT NULL DEFAULT 0 COMMENT 'Google 实付同步连续失败次数' AFTER paid_synced_at_ms, + ADD COLUMN paid_sync_last_error VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'Google 实付同步最近错误' AFTER paid_sync_attempt_count, + ADD COLUMN paid_sync_next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT 'Google 实付同步下次重试时间,UTC epoch ms' AFTER paid_sync_last_error, + ADD INDEX idx_payment_orders_paid_sync (provider, paid_synced_at_ms, paid_sync_next_retry_at_ms, created_at_ms); diff --git a/scripts/mysql/064_manager_bd_leader_region_scope.sql b/scripts/mysql/064_manager_bd_leader_region_scope.sql new file mode 100644 index 00000000..e7a0f365 --- /dev/null +++ b/scripts/mysql/064_manager_bd_leader_region_scope.sql @@ -0,0 +1,13 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE hyapp_user; + +-- 区域邀请是经理个人的显式扩权;默认 0 保证现有和新建经理继续使用 App 基础范围。 +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'manager_profiles' AND COLUMN_NAME = 'can_add_bd_leader_in_region') = 0, + 'ALTER TABLE manager_profiles ADD COLUMN can_add_bd_leader_in_region TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''是否允许把 BD Leader 邀请范围从本国家扩展到经理所属区域'' AFTER can_add_bd_leader', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/scripts/mysql/065_manager_cross_region_scope.sql b/scripts/mysql/065_manager_cross_region_scope.sql new file mode 100644 index 00000000..10745911 --- /dev/null +++ b/scripts/mysql/065_manager_cross_region_scope.sql @@ -0,0 +1,23 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE hyapp_user; + +-- 通用跨区经理替代上一版 BD Leader 专用扩区开关;默认关闭,不扩大任何现有经理的数据范围。 +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'manager_profiles' AND COLUMN_NAME = 'can_manage_cross_region') = 0, + 'ALTER TABLE manager_profiles ADD COLUMN can_manage_cross_region TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''是否允许经理中心所有目标用户操作跨区域执行'' AFTER can_add_bd_leader', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 若 064 已落库,则继承显式开启值;未执行 064 的环境保持默认关闭。 +SET @copy_legacy := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'manager_profiles' AND COLUMN_NAME = 'can_add_bd_leader_in_region') > 0, + 'UPDATE manager_profiles SET can_manage_cross_region = can_add_bd_leader_in_region WHERE can_add_bd_leader_in_region = 1', + 'SELECT 1' +); +PREPARE stmt FROM @copy_legacy; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/server/admin/cmd/server/main.go b/server/admin/cmd/server/main.go index e99e59f0..779bf7ba 100644 --- a/server/admin/cmd/server/main.go +++ b/server/admin/cmd/server/main.go @@ -63,6 +63,7 @@ import ( menumodule "hyapp-admin-server/internal/modules/menu" opscentermodule "hyapp-admin-server/internal/modules/opscenter" paymentmodule "hyapp-admin-server/internal/modules/payment" + pointwithdrawalconfigmodule "hyapp-admin-server/internal/modules/pointwithdrawalconfig" policyconfigmodule "hyapp-admin-server/internal/modules/policyconfig" prettyidmodule "hyapp-admin-server/internal/modules/prettyid" rbacmodule "hyapp-admin-server/internal/modules/rbac" @@ -216,6 +217,13 @@ func main() { } // legacy 账单源的谷歌实付同步依赖 admin 库缓存表,store 建好后再注入;凭证异常只降级该 App,不阻断启动。 wireLegacyGooglePaidSync(financeBillSources, cfg.FinanceBillSources, store) + if cfg.FinanceGooglePaidSync.Enabled { + // Polling is process-owned rather than HTTP-owned: startup immediately drains one batch, then + // every 15 seconds prioritizes newest orders while gradually repairing the complete backlog. + googlePaidSyncWorker := paymentmodule.NewLegacyGooglePaidSyncWorker(financeBillSources, store, cfg.FinanceGooglePaidSync) + googlePaidSyncWorker.Start() + defer googlePaidSyncWorker.Close() + } if *runBootstrap || cfg.Bootstrap.Enabled { if err := store.Seed(cfg); err != nil { fatalRuntime("seed_database_failed", err) @@ -269,11 +277,23 @@ func main() { auth := service.NewAuthService(cfg.JWTSecret, cfg.AccessTokenTTL) auditHandler := auditmodule.New(store) countryRegionHandler := countryregionmodule.New(userclient.NewGRPC(userConn), userDB, store, roomClient, cfg, auditHandler) + appUserHandler := appusermodule.New( + userclient.NewGRPC(userConn), + activityclient.NewGRPC(activityConn), + sqlDB, + userDB, + walletDB, + cfg, + auditHandler, + appusermodule.WithWalletClient(walletclient.NewGRPC(walletConn)), + appusermodule.WithExportJobs(store, cfg), + ) var runner *jobrunner.Runner var jobStatus healthmodule.JobStatusProvider if cfg.Jobs.Enabled { runner = jobrunner.NewRunner(store, redisClient, cfg, jobrunner.WithHandler(countryregionmodule.CountryCodeRenameJobType, countryRegionHandler.Service().HandleCountryCodeRenameJob), + jobrunner.WithArtifactHandler(appusermodule.AppUserExportJobType, appUserHandler.HandleExportJob), ) runner.Start() defer runner.Close() @@ -319,13 +339,32 @@ func main() { financeapplicationmodule.WithWalletClient(walletclient.NewGRPC(walletConn)), ) // 社交 BI 与大屏共享同一个 dashboard 服务实例,保证外部源路由和统计口径一致。 - dashboardService := dashboardmodule.NewService(store, cfg, userclient.NewGRPC(userConn), dashboardmodule.WithExternalDashboardSources(dashboardExternalSources...)) + dashboardService := dashboardmodule.NewService( + store, + cfg, + userclient.NewGRPC(userConn), + // 用户画像从 userDB 读取已完成真实用户的总量、性别和最近一次成功登录版本; + // 当天新增与日活继续走 statistics-service,避免重算事件去重口径。 + dashboardmodule.WithUserDB(userDB), + dashboardmodule.WithExternalDashboardSources(dashboardExternalSources...), + ) appRegistryService := appregistrymodule.NewService(userDB) + // Finance 与 Social BI 必须复用同一 Handler 实例,确保普通充值、已核验币商订单和 H5 币商充值完全同口径。 + paymentHandler := paymentmodule.New( + walletclient.NewGRPC(walletConn), + userDB, + walletDB, + store, + auditHandler, + paymentmodule.WithMoneyRegionSources(moneyRegionSources...), + paymentmodule.WithRechargeBillSources(financeBillSources...), + ) databiService := databimodule.NewService( store, dashboardService, appRegistryService, userclient.NewGRPC(userConn), + databimodule.WithFinanceRechargeOverviewSource(paymentHandler), databimodule.WithLegacyApps(databiLegacyApps(cfg.DashboardExternalSources)...), databimodule.WithLegacyRegionCatalogs(databiLegacyRegionCatalogs(moneyRegionSources)...), ) @@ -338,7 +377,7 @@ func main() { AchievementConfig: achievementconfigmodule.New(activityclient.NewGRPC(activityConn), auditHandler), AppConfig: appconfigmodule.New(store, auditHandler), AppRegistry: appregistrymodule.New(userDB), - AppUser: appusermodule.New(userclient.NewGRPC(userConn), activityclient.NewGRPC(activityConn), sqlDB, userDB, walletDB, cfg, auditHandler), + AppUser: appUserHandler, CoinLedger: coinledgermodule.New(userDB, walletDB, sqlDB, walletclient.NewGRPC(walletConn), auditHandler), CountryRegion: countryRegionHandler, CPRelation: cprelationmodule.NewWithActivity(userDB, activityclient.NewGRPC(activityConn), auditHandler), @@ -359,40 +398,41 @@ func main() { gamemanagementmodule.WithRobotProfileSource(robotProfileSource), gamemanagementmodule.WithRobotAppearanceServices(walletclient.NewGRPC(walletConn), activityclient.NewGRPC(activityConn)), ), - GiftDiamond: giftdiamondmodule.New(walletDB, auditHandler), - Health: healthmodule.New(store, redisClient, jobStatus), - HostAgencyPolicy: hostagencypolicymodule.New(store, walletDB, auditHandler), - HostOrg: hostorgmodule.New(userclient.NewGRPC(userConn), walletclient.NewGRPC(walletConn), userDB, walletDB, sqlDB, auditHandler), - HostSalarySettlement: hostsalarysettlementmodule.New(walletDB, userDB, walletv1.NewWalletCronServiceClient(walletConn), cfg.WalletService.RequestTimeout, auditHandler), - HostWithdrawal: hostwithdrawalmodule.New(sqlDB, walletDB, userDB), - InviteActivityReward: inviteactivityrewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler), - Job: jobmodule.New(store, cfg, auditHandler), - LevelConfig: levelconfigmodule.New(activityclient.NewGRPC(activityConn), walletclient.NewGRPC(walletConn), auditHandler), - Menu: menumodule.New(store, auditHandler), - OpsCenter: opscentermodule.New(appRegistryService, luckyGiftHandler), - Payment: paymentmodule.New(walletclient.NewGRPC(walletConn), userDB, walletDB, store, auditHandler, paymentmodule.WithMoneyRegionSources(moneyRegionSources...), paymentmodule.WithRechargeBillSources(financeBillSources...)), - PolicyConfig: policyconfigmodule.New(sqlDB, walletDB, userDB, activityclient.NewGRPC(activityConn), auditHandler), - PrettyID: prettyidmodule.New(userclient.NewGRPC(userConn), auditHandler), - RBAC: rbacmodule.New(store, auditHandler), - RedPacket: redpacketmodule.New(walletclient.NewGRPC(walletConn), auditHandler), - Report: reportmodule.New(userDB, roomClient), - RegistrationReward: registrationrewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler), - RegionBlock: regionblockmodule.New(userDB, userclient.NewGRPC(userConn), auditHandler), - Resource: resourcemodule.New(walletclient.NewGRPC(walletConn), store, userDB, cfg.WalletService.RequestTimeout, auditHandler), - RiskConfig: riskconfigmodule.New(userclient.NewGRPC(userConn), auditHandler), - RoomAdmin: roomadminmodule.New(userDB, store, roomClient, robotClient, auditHandler), - RoomRocket: roomrocketmodule.New(roomClient, auditHandler), - RoomTurnoverReward: roomturnoverrewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler), - Search: searchmodule.New(store), - SevenDayCheckIn: sevendaycheckinmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler), - Team: teammodule.New(store, auditHandler), - TeamSalaryPolicy: teamsalarypolicymodule.New(store, auditHandler), - TeamSalarySettlement: teamSalarySettlementHandler, - Upload: uploadmodule.New(objectUploader, cfg.TencentCOS.ObjectPrefix, auditHandler), - UserLeaderboard: userleaderboardmodule.New(walletDB, userDB, roomClient), - VIPConfig: vipconfigmodule.New(walletclient.NewGRPC(walletConn), auditHandler), - WeeklyStar: weeklystarmodule.New(activityclient.NewGRPC(activityConn), auditHandler), - Wheel: wheelmodule.New(activityclient.NewGRPC(activityConn), cfg.ActivityService.RequestTimeout, auditHandler), + GiftDiamond: giftdiamondmodule.New(walletDB, auditHandler), + Health: healthmodule.New(store, redisClient, jobStatus), + HostAgencyPolicy: hostagencypolicymodule.New(store, walletDB, auditHandler), + HostOrg: hostorgmodule.New(userclient.NewGRPC(userConn), walletclient.NewGRPC(walletConn), userDB, walletDB, sqlDB, auditHandler), + HostSalarySettlement: hostsalarysettlementmodule.New(walletDB, userDB, walletv1.NewWalletCronServiceClient(walletConn), cfg.WalletService.RequestTimeout, auditHandler), + HostWithdrawal: hostwithdrawalmodule.New(sqlDB, walletDB, userDB), + InviteActivityReward: inviteactivityrewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler), + Job: jobmodule.New(store, cfg, auditHandler), + LevelConfig: levelconfigmodule.New(activityclient.NewGRPC(activityConn), walletclient.NewGRPC(walletConn), auditHandler), + Menu: menumodule.New(store, auditHandler), + OpsCenter: opscentermodule.New(appRegistryService, luckyGiftHandler), + Payment: paymentHandler, + PolicyConfig: policyconfigmodule.New(sqlDB, walletDB, userDB, activityclient.NewGRPC(activityConn), auditHandler), + PointWithdrawalConfig: pointwithdrawalconfigmodule.New(walletDB, userDB, auditHandler), + PrettyID: prettyidmodule.New(userclient.NewGRPC(userConn), auditHandler), + RBAC: rbacmodule.New(store, auditHandler), + RedPacket: redpacketmodule.New(walletclient.NewGRPC(walletConn), auditHandler), + Report: reportmodule.New(userDB, roomClient), + RegistrationReward: registrationrewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler), + RegionBlock: regionblockmodule.New(userDB, userclient.NewGRPC(userConn), auditHandler), + Resource: resourcemodule.New(walletclient.NewGRPC(walletConn), store, userDB, cfg.WalletService.RequestTimeout, auditHandler), + RiskConfig: riskconfigmodule.New(userclient.NewGRPC(userConn), auditHandler), + RoomAdmin: roomadminmodule.New(userDB, store, roomClient, robotClient, auditHandler), + RoomRocket: roomrocketmodule.New(roomClient, auditHandler), + RoomTurnoverReward: roomturnoverrewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler), + Search: searchmodule.New(store), + SevenDayCheckIn: sevendaycheckinmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler), + Team: teammodule.New(store, auditHandler), + TeamSalaryPolicy: teamsalarypolicymodule.New(store, auditHandler), + TeamSalarySettlement: teamSalarySettlementHandler, + Upload: uploadmodule.New(objectUploader, cfg.TencentCOS.ObjectPrefix, auditHandler), + UserLeaderboard: userleaderboardmodule.New(walletDB, userDB, roomClient), + VIPConfig: vipconfigmodule.New(walletclient.NewGRPC(walletConn), auditHandler), + WeeklyStar: weeklystarmodule.New(activityclient.NewGRPC(activityConn), auditHandler), + Wheel: wheelmodule.New(activityclient.NewGRPC(activityConn), cfg.ActivityService.RequestTimeout, auditHandler), } engine := router.New(cfg, auth, handlers) diff --git a/server/admin/configs/config.tencent.example.yaml b/server/admin/configs/config.tencent.example.yaml index 48aa95bd..11271075 100644 --- a/server/admin/configs/config.tencent.example.yaml +++ b/server/admin/configs/config.tencent.example.yaml @@ -55,6 +55,11 @@ finance_bill_sources: google_package_name: "com.chat.auu" google_service_account_json: | {"type":"service_account","project_id":"aslan-494908","private_key_id":"f3ab2df01b3f92b9f71f4b7d1ebdba3cff5ac93e","private_key":"-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDkCOxtBRVNg5CF\nIfgkT5gv1f2hSRnf6/MSTDYkgXy1sVSbLEXbsZGQdnsYSfAxNXURyJ0QXJOZV551\n+0XOlRIb+HLN++37olcFVm4GPLxQiVCCEbFOBwGPZTMi4RYgriDHEp2umY9Cns8Z\nHYlkYgbLxKLICpzyDJWDsyT3Tc4rBhRGv7+dulxSuPpF5AaLzbV/AjQTwee6qnV4\n8VEPHx/r+3IAJMVLt+VrgFwujIdzJGk8TIZKSSmZ4J6iN5dKgly1PyVu66bppbX9\nHkPDEFg8nxYK5F2/yjo7/VIFurfd5bGKGS3sqf0siRTXLsj0X/wUgt0RlWOpKPeX\nbZq9jzznAgMBAAECggEAASLUPrTMRt8VbLxfFps46GAaC+An21g7FUfA60yj2Onh\nwIYncPFBBuW4NkZEBpK8GxMTST4U1Co+FVtjnSRb+zyxIbqUFHFaGqI0GR7bV1Ff\nz84Two5BYTwBVbamXBJSAnviwjhsoMnWwUrG4POmEgTQRMvcvU33vri5Qewmz0sN\njXTNh/GIUxA+IbOUTaaTDBLSshp7JBOPjuF0ttsd/0zG1AFbaWWtZ1h8YCOhIw+g\np7/CdS3gO/uttgI8PiLj8AImlOcRz5FnFGTcHZqR7pk08W7Olm89ik3NZg+5IozP\nrJ8LOU7uQ7F50zpWRNvpBNaa4m35Ns6bOHTLEV4BXQKBgQD0q8hn1bddwC92RzMw\naBb0Qt0Najg2R/SDWaeSO6Tz3KTflGZXDFtalAOq5KXT47N7QdoIjlXGP50B5iV7\nK/I25f+GONcACPtnnLWyNvjq2OuMz0FIGEoUxY9DFP6aNkyNR5FiE2t2y2XiKmrV\nCRt+P4x/b/KTvdVI+caUSfXezQKBgQDul/F46Uj1evZZToA4XENXRN8oSb4I08cX\nBQK2WUs+KJsT9sWNaT1V5hQ8wXfuN2rhI0kqR0EURY3ycaA41rdLIeYqvAnmR5qC\nSO8HlC2+zQzjSrr7lyt8dcTughW8/9lCf+jANBNYBZrBN2ta3vlQ8eA8rFUThYnC\n8+PFo8MigwKBgFbbGJSL0MFONUsWsXxQpz1k8xYNDBFw78MlM5B87ezH+huIkd/6\n+f8opjinXJrgrVlnIiCBbr+m23TOH6YfDqggc9pRGTng9mZswi+WxjyQbuYYuQL/\n5GSFUXst28gg2IIa0uhvHmoYgH2OM0iXKBRkONsQgZui+zEhwjXoH4lNAoGAKVMJ\n0M5fA52Lg4ZUMO7R/xB/skOrdW3wwqzsflbS8G4qBfgs2URMCk+yW5+KvSi+C0aI\nSplSzUcKwd4qSQ3va0Twz6AH+umV+lDVjbN9hNmRDOEJp7/UGVdwh3ridvy9TYZH\n8tpSK2G1HxgRMQkDl6B9HSUgCySK6shBQB8QEi8CgYBAbUxCJn20RFmwU4bgRASe\ngoDGkN5Dw1SdauuWn+WySTyrsxdxvRcuyy4pQSDBadhnv24/30mza1ToeJeEsyJ/\nJuysBhWBjJKoYVC/qikgS6EuAnqlAKKg8jLIqERmNaFol0Squ03kTvMYx2aGa1dt\nwwMCwljNodGSgZRA3RlwOA==\n-----END PRIVATE KEY-----\n","client_email":"pay-purchase-verify@aslan-494908.iam.gserviceaccount.com","client_id":"100290296879529721706","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/pay-purchase-verify%40aslan-494908.iam.gserviceaccount.com","universe_domain":"googleapis.com"} +finance_google_paid_sync: + enabled: true + poll_interval: "15s" + batch_size: 100 + request_timeout: "2m" mysql_auto_migrate: false migrations: enabled: true @@ -102,7 +107,8 @@ game_service: request_timeout: "3s" statistics_service: base_url: "http://10.2.1.16:13010" - request_timeout: "3s" + # 漏斗冷查询允许短时索引页预热;正常查询应远低于该上限,超时只负责隔离异常请求。 + request_timeout: "10s" dashboard_external_sources: - enabled: true type: "mysql" diff --git a/server/admin/configs/config.yaml b/server/admin/configs/config.yaml index 67a5f5a3..5beed748 100644 --- a/server/admin/configs/config.yaml +++ b/server/admin/configs/config.yaml @@ -55,6 +55,11 @@ finance_bill_sources: google_package_name: "" google_service_account_json: "" google_service_account_file: "" +finance_google_paid_sync: + enabled: true + poll_interval: "15s" + batch_size: 100 + request_timeout: "2m" mysql_auto_migrate: true migrations: enabled: true @@ -101,7 +106,8 @@ game_service: request_timeout: "3s" statistics_service: base_url: "http://127.0.0.1:13010" - request_timeout: "3s" + # 漏斗冷查询允许短时索引页预热;正常查询应远低于该上限,超时只负责隔离异常请求。 + request_timeout: "10s" dashboard_external_sources: - enabled: false type: "mysql" diff --git a/server/admin/docs/权限管理.md b/server/admin/docs/权限管理.md index 376b4dbd..346448b7 100644 --- a/server/admin/docs/权限管理.md +++ b/server/admin/docs/权限管理.md @@ -217,6 +217,8 @@ | 权限码 | kind | 控制范围 | | --- | --- | --- | | `app-user:view` | `menu` | App 用户列表、详情 | +| `app-user:level` | `button` | 临时调整 App 用户富豪/魅力等级 | +| `app-user:export` | `button` | 按当前筛选条件异步导出 App 用户 | | `app-user:update` | `button` | 修改用户资料 | | `app-user:status` | `button` | 后台封禁、解封 | | `app-user:password` | `button` | 后台设置 App 用户密码 | diff --git a/server/admin/internal/cache/redis.go b/server/admin/internal/cache/redis.go index 49e2180e..94614641 100644 --- a/server/admin/internal/cache/redis.go +++ b/server/admin/internal/cache/redis.go @@ -70,6 +70,24 @@ return 0` }, nil } +// RefreshLock 只延长仍由当前 owner 持有的锁;任务 worker 续租 DB lease 时同步续租 Redis, +// 防止大导出超过初始 TTL 后被另一实例重复执行并覆盖产物。 +func (r *Redis) RefreshLock(ctx context.Context, key string, owner string, ttl time.Duration) (bool, error) { + if r == nil || r.client == nil { + return true, nil + } + if strings.TrimSpace(owner) == "" || ttl <= 0 { + return false, errors.New("lock owner and positive ttl are required") + } + const script = ` +if redis.call("GET", KEYS[1]) == ARGV[1] then + return redis.call("PEXPIRE", KEYS[1], ARGV[2]) +end +return 0` + result, err := r.client.Eval(ctx, script, []string{r.key(key)}, owner, ttl.Milliseconds()).Int64() + return result == 1, err +} + func (r *Redis) key(key string) string { if r.prefix == "" { return key diff --git a/server/admin/internal/config/config.go b/server/admin/internal/config/config.go index 24bc0638..aa23d4a0 100644 --- a/server/admin/internal/config/config.go +++ b/server/admin/internal/config/config.go @@ -13,17 +13,19 @@ import ( ) type Config struct { - ServiceName string `yaml:"service_name"` - NodeID string `yaml:"node_id"` - Environment string `yaml:"environment"` - Log logging.Config `yaml:"log"` - HTTPAddr string `yaml:"http_addr"` - MySQLDSN string `yaml:"mysql_dsn"` - UserMySQLDSN string `yaml:"user_mysql_dsn"` - WalletMySQLDSN string `yaml:"wallet_mysql_dsn"` - RobotProfileSource RobotProfileSourceConfig `yaml:"robot_profile_source"` - MoneyRegionSources []MoneyRegionSourceConfig `yaml:"money_region_sources"` - FinanceBillSources []FinanceBillSourceConfig `yaml:"finance_bill_sources"` + ServiceName string `yaml:"service_name"` + NodeID string `yaml:"node_id"` + Environment string `yaml:"environment"` + Log logging.Config `yaml:"log"` + HTTPAddr string `yaml:"http_addr"` + MySQLDSN string `yaml:"mysql_dsn"` + UserMySQLDSN string `yaml:"user_mysql_dsn"` + WalletMySQLDSN string `yaml:"wallet_mysql_dsn"` + RobotProfileSource RobotProfileSourceConfig `yaml:"robot_profile_source"` + MoneyRegionSources []MoneyRegionSourceConfig `yaml:"money_region_sources"` + FinanceBillSources []FinanceBillSourceConfig `yaml:"finance_bill_sources"` + // FinanceGooglePaidSync controls the durable polling loop that fills Google Orders API paid details for legacy bill sources. + FinanceGooglePaidSync FinanceGooglePaidSyncConfig `yaml:"finance_google_paid_sync"` MySQLAutoMigrate bool `yaml:"mysql_auto_migrate"` Migrations MigrationConfig `yaml:"migrations"` JWTSecret string `yaml:"jwt_secret"` @@ -173,6 +175,15 @@ type FinanceBillSourceConfig struct { GoogleServiceAccountFile string `yaml:"google_service_account_file"` } +// FinanceGooglePaidSyncConfig keeps legacy Google paid-detail polling independent from HTTP requests. +// New successful Mongo orders are picked every PollInterval; failed orders use persisted retry backoff. +type FinanceGooglePaidSyncConfig struct { + Enabled bool `yaml:"enabled"` + PollInterval time.Duration `yaml:"poll_interval"` + BatchSize int `yaml:"batch_size"` + RequestTimeout time.Duration `yaml:"request_timeout"` +} + type TencentCOSConfig struct { Enabled bool `yaml:"enabled"` SecretID string `yaml:"secret-id"` @@ -361,6 +372,12 @@ func Default() Config { RequestTimeout: 3 * time.Second, }, }, + FinanceGooglePaidSync: FinanceGooglePaidSyncConfig{ + Enabled: true, + PollInterval: 15 * time.Second, + BatchSize: 100, + RequestTimeout: 2 * time.Minute, + }, TencentCOS: TencentCOSConfig{ Enabled: false, ObjectPrefix: "admin", @@ -607,6 +624,19 @@ func (cfg *Config) Normalize() { } } cfg.applyFinanceBillSourceEnvOverrides() + // The sync feature is enabled by default; normalization only bounds operator-provided cadence and batch size. + if cfg.FinanceGooglePaidSync.PollInterval <= 0 { + cfg.FinanceGooglePaidSync.PollInterval = Default().FinanceGooglePaidSync.PollInterval + } + if cfg.FinanceGooglePaidSync.BatchSize <= 0 { + cfg.FinanceGooglePaidSync.BatchSize = Default().FinanceGooglePaidSync.BatchSize + } + if cfg.FinanceGooglePaidSync.BatchSize > 1000 { + cfg.FinanceGooglePaidSync.BatchSize = 1000 + } + if cfg.FinanceGooglePaidSync.RequestTimeout <= 0 { + cfg.FinanceGooglePaidSync.RequestTimeout = Default().FinanceGooglePaidSync.RequestTimeout + } cfg.TencentCOS.SecretID = strings.TrimSpace(cfg.TencentCOS.SecretID) cfg.TencentCOS.SecretKey = strings.TrimSpace(cfg.TencentCOS.SecretKey) cfg.TencentCOS.BucketName = strings.TrimSpace(cfg.TencentCOS.BucketName) diff --git a/server/admin/internal/integration/activityclient/client.go b/server/admin/internal/integration/activityclient/client.go index a36cc2f6..632cad45 100644 --- a/server/admin/internal/integration/activityclient/client.go +++ b/server/admin/internal/integration/activityclient/client.go @@ -324,6 +324,16 @@ func (c *GRPCClient) SetUserLevel(ctx context.Context, req *activityv1.SetUserLe return c.levelClient.SetUserLevel(ctx, req) } +// BatchGetUserLevelAdminProfiles 读取真实成长账户与有效 overlay,避免 admin-server 复制等级计算规则。 +func (c *GRPCClient) BatchGetUserLevelAdminProfiles(ctx context.Context, req *activityv1.BatchGetUserLevelAdminProfilesRequest) (*activityv1.BatchGetUserLevelAdminProfilesResponse, error) { + return c.growthClient.BatchGetUserLevelAdminProfiles(ctx, req) +} + +// AdjustTemporaryUserLevels 把财富和魅力两条轨道一次交给 activity-service 原子提交。 +func (c *GRPCClient) AdjustTemporaryUserLevels(ctx context.Context, req *activityv1.AdjustTemporaryUserLevelsRequest) (*activityv1.AdjustTemporaryUserLevelsResponse, error) { + return c.growthClient.AdjustTemporaryUserLevels(ctx, req) +} + func (c *GRPCClient) CreateInboxMessage(ctx context.Context, req *activityv1.CreateInboxMessageRequest) (*activityv1.CreateInboxMessageResponse, error) { return c.messageClient.CreateInboxMessage(ctx, req) } diff --git a/server/admin/internal/integration/googleorders/client.go b/server/admin/internal/integration/googleorders/client.go index 86ebc576..6f65b564 100644 --- a/server/admin/internal/integration/googleorders/client.go +++ b/server/admin/internal/integration/googleorders/client.go @@ -121,40 +121,71 @@ func (c *Client) GetOrder(ctx context.Context, orderID string) (Order, error) { if orderID == "" { return Order{}, errors.New("google order id is required") } - token, err := c.token(ctx) - if err != nil { - return Order{}, err - } endpoint := fmt.Sprintf("%s/androidpublisher/v3/applications/%s/orders/%s", defaultAPIBaseURL, url.PathEscape(c.packageName), url.PathEscape(orderID)) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) + body, err := c.getJSON(ctx, endpoint) if err != nil { return Order{}, err } - req.Header.Set("Authorization", "Bearer "+token) - resp, err := c.httpClient.Do(req) - if err != nil { - return Order{}, errors.New("google play api request failed") - } - defer resp.Body.Close() - body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) - if err != nil { - return Order{}, err - } - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - switch resp.StatusCode { - case http.StatusNotFound, http.StatusBadRequest: - return Order{}, errors.New("google order is not found") - case http.StatusUnauthorized, http.StatusForbidden: - return Order{}, errors.New("google play api is not authorized (需要查看财务数据权限)") - default: - return Order{}, fmt.Errorf("google play api returned %d", resp.StatusCode) - } - } var parsed orderResource if err := json.Unmarshal(body, &parsed); err != nil { return Order{}, err } + return orderFromResource(parsed), nil +} + +// GetOrders uses Orders batchGet for backlog repair: one request can fetch up to 1000 distinct orders. +// The caller falls back to single-order requests if Google rejects the whole batch because of one stale ID. +func (c *Client) GetOrders(ctx context.Context, orderIDs []string) (map[string]Order, error) { + if c == nil { + return nil, errors.New("google orders client is not configured") + } + cleaned := make([]string, 0, len(orderIDs)) + seen := make(map[string]struct{}, len(orderIDs)) + for _, orderID := range orderIDs { + orderID = strings.TrimSpace(orderID) + if orderID == "" { + continue + } + if _, ok := seen[orderID]; ok { + continue + } + seen[orderID] = struct{}{} + cleaned = append(cleaned, orderID) + } + if len(cleaned) == 0 { + return map[string]Order{}, nil + } + if len(cleaned) > 1000 { + return nil, errors.New("too many google order ids") + } + query := url.Values{} + for _, orderID := range cleaned { + query.Add("orderIds", orderID) + } + endpoint := fmt.Sprintf("%s/androidpublisher/v3/applications/%s/orders:batchGet?%s", + defaultAPIBaseURL, url.PathEscape(c.packageName), query.Encode()) + body, err := c.getJSON(ctx, endpoint) + if err != nil { + return nil, err + } + var parsed struct { + Orders []orderResource `json:"orders"` + } + if err := json.Unmarshal(body, &parsed); err != nil { + return nil, err + } + orders := make(map[string]Order, len(parsed.Orders)) + for _, item := range parsed.Orders { + order := orderFromResource(item) + if order.OrderID != "" { + orders[order.OrderID] = order + } + } + return orders, nil +} + +func orderFromResource(parsed orderResource) Order { order := Order{ OrderID: parsed.OrderID, State: parsed.State, @@ -167,7 +198,39 @@ func (c *Client) GetOrder(ctx context.Context, orderID string) (Order, error) { if order.CurrencyCode == "" { order.CurrencyCode = parsed.Tax.CurrencyCode } - return order, nil + return order +} + +func (c *Client) getJSON(ctx context.Context, endpoint string) ([]byte, error) { + token, err := c.token(ctx) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+token) + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, errors.New("google play api request failed") + } + defer resp.Body.Close() + body, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20)) + if err != nil { + return nil, err + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + switch resp.StatusCode { + case http.StatusNotFound, http.StatusBadRequest: + return nil, errors.New("google order is not found") + case http.StatusUnauthorized, http.StatusForbidden: + return nil, errors.New("google play api is not authorized (需要查看财务数据权限)") + default: + return nil, fmt.Errorf("google play api returned %d", resp.StatusCode) + } + } + return body, nil } func (c *Client) token(ctx context.Context) (string, error) { diff --git a/server/admin/internal/integration/userclient/client.go b/server/admin/internal/integration/userclient/client.go index d49c4b9d..ac43f207 100644 --- a/server/admin/internal/integration/userclient/client.go +++ b/server/admin/internal/integration/userclient/client.go @@ -2,6 +2,7 @@ package userclient import ( "context" + "fmt" "time" "hyapp-admin-server/internal/appctx" @@ -41,6 +42,7 @@ type Client interface { ReviewCoinSellerSubApplication(ctx context.Context, req ReviewCoinSellerSubApplicationRequest) (*CoinSellerSubApplicationReviewResult, error) GetCoinSellerProfile(ctx context.Context, req GetCoinSellerProfileRequest) (*CoinSellerProfile, error) GetUserRoleSummary(ctx context.Context, req GetUserRoleSummaryRequest) (*UserRoleSummary, error) + GetRoleScopePolicy(ctx context.Context, req GetRoleScopePolicyRequest) (*RoleScopePolicy, error) CreateAgency(ctx context.Context, req CreateAgencyRequest) (*CreateAgencyResult, error) AdminAddAgencyHost(ctx context.Context, req AdminAddAgencyHostRequest) (*CreateAgencyResult, error) CloseAgency(ctx context.Context, req CloseAgencyRequest) (*Agency, error) @@ -111,6 +113,19 @@ type ChangeUserCountryResult struct { RegionChanged bool `json:"regionChanged"` } +type GetRoleScopePolicyRequest struct { + RequestID string + Caller string + Scene string +} + +type RoleScopePolicy struct { + Scene string `json:"scene"` + BaseScope string `json:"baseScope"` + ExpandedScope string `json:"expandedScope"` + RegionExpansionConfigurable bool `json:"regionExpansionConfigurable"` +} + type SetUserStatusResult struct { User *User `json:"user"` RevokedSessionCount int64 `json:"revokedSessionCount"` @@ -379,6 +394,25 @@ func (c *GRPCClient) SetUserStatus(ctx context.Context, req SetUserStatusRequest }, nil } +// BatchGetUserAdminProfiles 汇总 user-service owner 的生日、注册设备、成功登录、身份和有效封禁事实。 +func (c *GRPCClient) BatchGetUserAdminProfiles(ctx context.Context, req *userv1.BatchGetUserAdminProfilesRequest) (*userv1.BatchGetUserAdminProfilesResponse, error) { + return c.client.BatchGetUserAdminProfiles(ctx, req) +} + +// AdminIssueUserAccessToken 透传后台按用户重签 access token 的 RPC; +// user-service 侧刻意不写 login_audit,审计由 admin 操作日志完成。 +func (c *GRPCClient) AdminIssueUserAccessToken(ctx context.Context, req *userv1.AdminIssueUserAccessTokenRequest) (*userv1.AdminIssueUserAccessTokenResponse, error) { + return c.client.AdminIssueUserAccessToken(ctx, req) +} + +func (c *GRPCClient) AdminBanUser(ctx context.Context, req *userv1.AdminBanUserRequest) (*userv1.AdminBanUserResponse, error) { + return c.client.AdminBanUser(ctx, req) +} + +func (c *GRPCClient) AdminUnbanUser(ctx context.Context, req *userv1.AdminUnbanUserRequest) (*userv1.AdminUnbanUserResponse, error) { + return c.client.AdminUnbanUser(ctx, req) +} + func userStatusToProto(status string) userv1.UserStatus { switch status { case "active": @@ -804,6 +838,26 @@ func (c *GRPCClient) CreateBDLeader(ctx context.Context, req CreateBDLeaderReque return fromProtoBDProfile(resp.GetBdProfile()), nil } +func (c *GRPCClient) GetRoleScopePolicy(ctx context.Context, req GetRoleScopePolicyRequest) (*RoleScopePolicy, error) { + resp, err := c.hostClient.GetRoleScopePolicy(ctx, &userv1.GetRoleScopePolicyRequest{ + Meta: requestMeta(ctx, req.RequestID, req.Caller), + Scene: req.Scene, + }) + if err != nil { + return nil, err + } + policy := resp.GetPolicy() + if policy == nil { + return nil, fmt.Errorf("role scope policy missing") + } + return &RoleScopePolicy{ + Scene: policy.GetScene(), + BaseScope: policy.GetBaseScope(), + ExpandedScope: policy.GetExpandedScope(), + RegionExpansionConfigurable: policy.GetRegionExpansionConfigurable(), + }, nil +} + func (c *GRPCClient) CreateBD(ctx context.Context, req CreateBDRequest) (*BDProfile, error) { resp, err := c.hostAdminClient.CreateBD(ctx, &userv1.CreateBDRequest{ Meta: requestMeta(ctx, req.RequestID, req.Caller), diff --git a/server/admin/internal/integration/walletclient/client.go b/server/admin/internal/integration/walletclient/client.go index b78ff76e..bbb5bb10 100644 --- a/server/admin/internal/integration/walletclient/client.go +++ b/server/admin/internal/integration/walletclient/client.go @@ -45,6 +45,7 @@ type Client interface { ReleaseSalaryWithdrawal(ctx context.Context, req *walletv1.ReleaseSalaryWithdrawalRequest) (*walletv1.ReleaseSalaryWithdrawalResponse, error) ListRechargeBills(ctx context.Context, req *walletv1.ListRechargeBillsRequest) (*walletv1.ListRechargeBillsResponse, error) GetRechargeBillSummary(ctx context.Context, req *walletv1.GetRechargeBillSummaryRequest) (*walletv1.GetRechargeBillSummaryResponse, error) + BatchGetUserRechargeStats(ctx context.Context, req *walletv1.BatchGetUserRechargeStatsRequest) (*walletv1.BatchGetUserRechargeStatsResponse, error) GetRechargeBillOverview(ctx context.Context, req *walletv1.GetRechargeBillOverviewRequest) (*walletv1.GetRechargeBillOverviewResponse, error) RefreshGooglePaymentPrices(ctx context.Context, req *walletv1.RefreshGooglePaymentPricesRequest) (*walletv1.RefreshGooglePaymentPricesResponse, error) ListThirdPartyPaymentChannels(ctx context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error) @@ -59,9 +60,12 @@ type Client interface { CreateRechargeProduct(ctx context.Context, req *walletv1.CreateRechargeProductRequest) (*walletv1.RechargeProductResponse, error) UpdateRechargeProduct(ctx context.Context, req *walletv1.UpdateRechargeProductRequest) (*walletv1.RechargeProductResponse, error) DeleteRechargeProduct(ctx context.Context, req *walletv1.DeleteRechargeProductRequest) (*walletv1.DeleteRechargeProductResponse, error) + GetVipProgramConfig(ctx context.Context, req *walletv1.GetVipProgramConfigRequest) (*walletv1.GetVipProgramConfigResponse, error) + UpdateAdminVipProgramConfig(ctx context.Context, req *walletv1.UpdateAdminVipProgramConfigRequest) (*walletv1.UpdateAdminVipProgramConfigResponse, error) ListAdminVipLevels(ctx context.Context, req *walletv1.ListAdminVipLevelsRequest) (*walletv1.ListAdminVipLevelsResponse, error) UpdateAdminVipLevels(ctx context.Context, req *walletv1.UpdateAdminVipLevelsRequest) (*walletv1.UpdateAdminVipLevelsResponse, error) GrantVip(ctx context.Context, req *walletv1.GrantVipRequest) (*walletv1.GrantVipResponse, error) + GrantVipTrialCard(ctx context.Context, req *walletv1.GrantVipTrialCardRequest) (*walletv1.GrantVipTrialCardResponse, error) GetRedPacketConfig(ctx context.Context, req *walletv1.GetRedPacketConfigRequest) (*walletv1.GetRedPacketConfigResponse, error) UpdateRedPacketConfig(ctx context.Context, req *walletv1.UpdateRedPacketConfigRequest) (*walletv1.UpdateRedPacketConfigResponse, error) ListRedPackets(ctx context.Context, req *walletv1.ListRedPacketsRequest) (*walletv1.ListRedPacketsResponse, error) @@ -225,6 +229,10 @@ func (c *GRPCClient) GetRechargeBillSummary(ctx context.Context, req *walletv1.G return c.client.GetRechargeBillSummary(ctx, req) } +func (c *GRPCClient) BatchGetUserRechargeStats(ctx context.Context, req *walletv1.BatchGetUserRechargeStatsRequest) (*walletv1.BatchGetUserRechargeStatsResponse, error) { + return c.client.BatchGetUserRechargeStats(ctx, req) +} + func (c *GRPCClient) GetRechargeBillOverview(ctx context.Context, req *walletv1.GetRechargeBillOverviewRequest) (*walletv1.GetRechargeBillOverviewResponse, error) { return c.client.GetRechargeBillOverview(ctx, req) } @@ -281,6 +289,14 @@ func (c *GRPCClient) DeleteRechargeProduct(ctx context.Context, req *walletv1.De return c.client.DeleteRechargeProduct(ctx, req) } +func (c *GRPCClient) GetVipProgramConfig(ctx context.Context, req *walletv1.GetVipProgramConfigRequest) (*walletv1.GetVipProgramConfigResponse, error) { + return c.client.GetVipProgramConfig(ctx, req) +} + +func (c *GRPCClient) UpdateAdminVipProgramConfig(ctx context.Context, req *walletv1.UpdateAdminVipProgramConfigRequest) (*walletv1.UpdateAdminVipProgramConfigResponse, error) { + return c.client.UpdateAdminVipProgramConfig(ctx, req) +} + func (c *GRPCClient) ListAdminVipLevels(ctx context.Context, req *walletv1.ListAdminVipLevelsRequest) (*walletv1.ListAdminVipLevelsResponse, error) { return c.client.ListAdminVipLevels(ctx, req) } @@ -293,6 +309,10 @@ func (c *GRPCClient) GrantVip(ctx context.Context, req *walletv1.GrantVipRequest return c.client.GrantVip(ctx, req) } +func (c *GRPCClient) GrantVipTrialCard(ctx context.Context, req *walletv1.GrantVipTrialCardRequest) (*walletv1.GrantVipTrialCardResponse, error) { + return c.client.GrantVipTrialCard(ctx, req) +} + func (c *GRPCClient) GetRedPacketConfig(ctx context.Context, req *walletv1.GetRedPacketConfigRequest) (*walletv1.GetRedPacketConfigResponse, error) { return c.client.GetRedPacketConfig(ctx, req) } diff --git a/server/admin/internal/job/runner.go b/server/admin/internal/job/runner.go index 52dde471..fe60e205 100644 --- a/server/admin/internal/job/runner.go +++ b/server/admin/internal/job/runner.go @@ -26,29 +26,35 @@ const userExportJobType = "user-export" type HandlerFunc func(ctx context.Context, job *model.AdminJob) (string, error) +// ArtifactHandlerFunc 供需要生成可下载文件的业务任务使用;artifactPath 必须位于 runner 配置的导出目录内。 +type ArtifactHandlerFunc func(ctx context.Context, job *model.AdminJob) (resultJSON string, artifactPath string, err error) + type Option func(*Runner) type Store interface { LeaseNextJob(workerID string, lease time.Duration, maxAttempts int) (*model.AdminJob, error) - CompleteJobWithArtifact(id uint, resultJSON string, artifactPath string) error - FailJobAttempt(id uint, message string) error - ReleaseJobLease(id uint) error + RenewJobLease(id uint, workerID string, lease time.Duration) error + CompleteJobWithArtifact(id uint, workerID string, resultJSON string, artifactPath string) error + FailJobAttempt(id uint, workerID string, message string) error + ReleaseJobLease(id uint, workerID string) error ExportUsers(options repository.ListOptions) ([]model.User, error) } type Locker interface { TryLock(ctx context.Context, key string, owner string, ttl time.Duration) (bool, cache.UnlockFunc, error) + RefreshLock(ctx context.Context, key string, owner string, ttl time.Duration) (bool, error) } type Runner struct { - artifactDir string - interval time.Duration - leaseTTL time.Duration - locker Locker - handlers map[string]HandlerFunc - maxAttempts int - store Store - workerID string + artifactDir string + interval time.Duration + leaseTTL time.Duration + locker Locker + handlers map[string]HandlerFunc + artifactHandlers map[string]ArtifactHandlerFunc + maxAttempts int + store Store + workerID string closeOnce sync.Once done chan struct{} @@ -65,6 +71,17 @@ func WithHandler(jobType string, handler HandlerFunc) Option { } } +// WithArtifactHandler 注册带文件产物的任务,避免业务模块绕过统一 job lease、重试和下载校验。 +func WithArtifactHandler(jobType string, handler ArtifactHandlerFunc) Option { + return func(r *Runner) { + jobType = strings.TrimSpace(jobType) + if jobType == "" || handler == nil { + return + } + r.artifactHandlers[jobType] = handler + } +} + func NewRunner(store Store, locker Locker, cfg config.Config, opts ...Option) *Runner { interval := cfg.Jobs.WorkerInterval if interval <= 0 { @@ -87,16 +104,17 @@ func NewRunner(store Store, locker Locker, cfg config.Config, opts ...Option) *R workerID = "admin-job-worker" } runner := &Runner{ - artifactDir: artifactDir, - interval: interval, - leaseTTL: leaseTTL, - locker: locker, - handlers: make(map[string]HandlerFunc), - maxAttempts: maxAttempts, - store: store, - workerID: workerID, - done: make(chan struct{}), - stop: make(chan struct{}), + artifactDir: artifactDir, + interval: interval, + leaseTTL: leaseTTL, + locker: locker, + handlers: make(map[string]HandlerFunc), + artifactHandlers: make(map[string]ArtifactHandlerFunc), + maxAttempts: maxAttempts, + store: store, + workerID: workerID, + done: make(chan struct{}), + stop: make(chan struct{}), } for _, opt := range opts { if opt != nil { @@ -156,19 +174,69 @@ func (r *Runner) runOnce(ctx context.Context) { locked, unlock, err := r.lock(ctx, job) if err != nil { slog.Error("job_lock_failed", "worker_id", r.workerID, "job_id", job.ID, "error", err.Error()) - _ = r.store.ReleaseJobLease(job.ID) + _ = r.store.ReleaseJobLease(job.ID, r.workerID) return } if !locked { - _ = r.store.ReleaseJobLease(job.ID) + _ = r.store.ReleaseJobLease(job.ID, r.workerID) return } - err = r.handle(ctx, job) + err = r.handleWithHeartbeat(ctx, job) if unlock != nil { _ = unlock(ctx) } if err != nil { - _ = r.store.FailJobAttempt(job.ID, err.Error()) + _ = r.store.FailJobAttempt(job.ID, r.workerID, err.Error()) + } + } +} + +func (r *Runner) handleWithHeartbeat(ctx context.Context, job *model.AdminJob) error { + jobCtx, cancel := context.WithCancel(ctx) + defer cancel() + heartbeatResult := make(chan error, 1) + go r.heartbeat(jobCtx, cancel, job, heartbeatResult) + + handleErr := r.handle(jobCtx, job) + cancel() + heartbeatErr := <-heartbeatResult + if handleErr != nil { + return handleErr + } + return heartbeatErr +} + +func (r *Runner) heartbeat(ctx context.Context, cancel context.CancelFunc, job *model.AdminJob, result chan<- error) { + interval := r.leaseTTL / 3 + if interval < 100*time.Millisecond { + interval = 100 * time.Millisecond + } + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + result <- nil + return + case <-ticker.C: + if err := r.store.RenewJobLease(job.ID, r.workerID, r.leaseTTL); err != nil { + cancel() + result <- fmt.Errorf("renew job lease: %w", err) + return + } + if r.locker != nil { + refreshed, err := r.locker.RefreshLock(ctx, fmt.Sprintf("jobs:%d", job.ID), r.workerID, r.leaseTTL) + if err != nil { + cancel() + result <- fmt.Errorf("refresh job lock: %w", err) + return + } + if !refreshed { + cancel() + result <- errors.New("job distributed lock is no longer owned by worker") + return + } + } } } } @@ -178,6 +246,13 @@ func (r *Runner) handle(ctx context.Context, job *model.AdminJob) error { case userExportJobType: return r.exportAdminUsers(ctx, job) default: + if handler := r.artifactHandlers[strings.TrimSpace(job.Type)]; handler != nil { + resultJSON, artifactPath, err := handler(ctx, job) + if err != nil { + return err + } + return r.store.CompleteJobWithArtifact(job.ID, r.workerID, resultJSON, artifactPath) + } handler := r.handlers[strings.TrimSpace(job.Type)] if handler == nil { return fmt.Errorf("unsupported job type: %s", job.Type) @@ -186,7 +261,7 @@ func (r *Runner) handle(ctx context.Context, job *model.AdminJob) error { if err != nil { return err } - return r.store.CompleteJobWithArtifact(job.ID, resultJSON, "") + return r.store.CompleteJobWithArtifact(job.ID, r.workerID, resultJSON, "") } } @@ -212,7 +287,7 @@ func (r *Runner) exportAdminUsers(_ context.Context, job *model.AdminJob) error if err != nil { return err } - return r.store.CompleteJobWithArtifact(job.ID, string(result), artifactPath) + return r.store.CompleteJobWithArtifact(job.ID, r.workerID, string(result), artifactPath) } func (r *Runner) lock(ctx context.Context, job *model.AdminJob) (bool, cache.UnlockFunc, error) { diff --git a/server/admin/internal/job/runner_test.go b/server/admin/internal/job/runner_test.go new file mode 100644 index 00000000..4c279f87 --- /dev/null +++ b/server/admin/internal/job/runner_test.go @@ -0,0 +1,115 @@ +package job + +import ( + "context" + "errors" + "sync" + "testing" + "time" + + "hyapp-admin-server/internal/cache" + "hyapp-admin-server/internal/config" + "hyapp-admin-server/internal/model" + "hyapp-admin-server/internal/repository" +) + +func TestArtifactJobRenewsDatabaseAndDistributedLeases(t *testing.T) { + store := &heartbeatStore{} + locker := &heartbeatLocker{} + runner := NewRunner(store, locker, config.Config{ + NodeID: "worker-heartbeat", + Jobs: config.JobsConfig{LeaseTTL: 150 * time.Millisecond}, + }, WithArtifactHandler("slow-export", func(ctx context.Context, _ *model.AdminJob) (string, string, error) { + select { + case <-ctx.Done(): + return "", "", ctx.Err() + case <-time.After(350 * time.Millisecond): + return `{"rows":1}`, "slow.csv", nil + } + })) + + if err := runner.handleWithHeartbeat(context.Background(), &model.AdminJob{ID: 91, Type: "slow-export"}); err != nil { + t.Fatalf("slow artifact handler failed: %v", err) + } + store.mu.Lock() + renews, completes, completedBy := store.renews, store.completes, store.completedBy + store.mu.Unlock() + locker.mu.Lock() + refreshes := locker.refreshes + locker.mu.Unlock() + if renews < 2 || refreshes < 2 || completes != 1 || completedBy != "worker-heartbeat" { + t.Fatalf("long job lease was not kept alive: renews=%d refreshes=%d completes=%d worker=%q", renews, refreshes, completes, completedBy) + } +} + +func TestArtifactJobCancelsWhenDistributedLeaseIsLost(t *testing.T) { + store := &heartbeatStore{} + locker := &heartbeatLocker{loseOnRefresh: true} + runner := NewRunner(store, locker, config.Config{ + NodeID: "worker-lost-lock", + Jobs: config.JobsConfig{LeaseTTL: 150 * time.Millisecond}, + }, WithArtifactHandler("blocked-export", func(ctx context.Context, _ *model.AdminJob) (string, string, error) { + <-ctx.Done() + return "", "", ctx.Err() + })) + + err := runner.handleWithHeartbeat(context.Background(), &model.AdminJob{ID: 92, Type: "blocked-export"}) + if !errors.Is(err, context.Canceled) { + t.Fatalf("lost distributed lock must cancel handler, err=%v", err) + } + store.mu.Lock() + completes := store.completes + store.mu.Unlock() + if completes != 0 { + t.Fatalf("worker that lost its lease must not complete the job: completes=%d", completes) + } +} + +type heartbeatStore struct { + mu sync.Mutex + renews int + completes int + completedBy string +} + +func (s *heartbeatStore) LeaseNextJob(string, time.Duration, int) (*model.AdminJob, error) { + return nil, errors.New("not used") +} + +func (s *heartbeatStore) RenewJobLease(_ uint, _ string, _ time.Duration) error { + s.mu.Lock() + s.renews++ + s.mu.Unlock() + return nil +} + +func (s *heartbeatStore) CompleteJobWithArtifact(_ uint, workerID string, _, _ string) error { + s.mu.Lock() + s.completes++ + s.completedBy = workerID + s.mu.Unlock() + return nil +} + +func (s *heartbeatStore) FailJobAttempt(uint, string, string) error { return nil } +func (s *heartbeatStore) ReleaseJobLease(uint, string) error { return nil } +func (s *heartbeatStore) ExportUsers(repository.ListOptions) ([]model.User, error) { + return nil, nil +} + +type heartbeatLocker struct { + mu sync.Mutex + refreshes int + loseOnRefresh bool +} + +func (l *heartbeatLocker) TryLock(context.Context, string, string, time.Duration) (bool, cache.UnlockFunc, error) { + return true, func(context.Context) error { return nil }, nil +} + +func (l *heartbeatLocker) RefreshLock(context.Context, string, string, time.Duration) (bool, error) { + l.mu.Lock() + defer l.mu.Unlock() + l.refreshes++ + return !l.loseOnRefresh, nil +} diff --git a/server/admin/internal/model/models.go b/server/admin/internal/model/models.go index ff2c2ad7..cc2d3b04 100644 --- a/server/admin/internal/model/models.go +++ b/server/admin/internal/model/models.go @@ -125,10 +125,12 @@ func (Menu) TableName() string { type AppConfig struct { ID uint `gorm:"primaryKey" json:"id"` - Group string `gorm:"size:64;index:idx_admin_app_config_group_key,unique;not null" json:"group"` - Key string `gorm:"size:100;index:idx_admin_app_config_group_key,unique;not null" json:"key"` + AppCode string `gorm:"size:32;uniqueIndex:uk_admin_app_config_app_group_key;index:idx_admin_app_configs_app_group;not null" json:"appCode"` + Group string `gorm:"size:64;uniqueIndex:uk_admin_app_config_app_group_key;index:idx_admin_app_configs_app_group;not null" json:"group"` + Key string `gorm:"size:100;uniqueIndex:uk_admin_app_config_app_group_key;not null" json:"key"` Value string `gorm:"type:text" json:"value"` Description string `gorm:"size:255" json:"description"` + IsDeleted bool `gorm:"column:is_deleted;not null;default:false" json:"-"` CreatedAtMS int64 `gorm:"column:created_at_ms;autoCreateTime:milli" json:"createdAtMs"` UpdatedAtMS int64 `gorm:"column:updated_at_ms;autoUpdateTime:milli" json:"updatedAtMs"` } @@ -549,6 +551,22 @@ func (LegacyGooglePaidDetail) TableName() string { return "admin_legacy_google_paid_details" } +// LegacyGooglePaidSyncAttempt persists retry state separately from paid details. +// A row here never means "synced"; successful synchronization deletes it after the paid-detail upsert commits. +type LegacyGooglePaidSyncAttempt struct { + AppCode string `gorm:"size:32;primaryKey" json:"appCode"` + TransactionID string `gorm:"size:96;primaryKey;column:transaction_id" json:"transactionId"` + AttemptCount int `gorm:"not null;default:0" json:"attemptCount"` + LastError string `gorm:"size:512;not null;default:''" json:"lastError"` + NextRetryAtMS int64 `gorm:"column:next_retry_at_ms;not null;default:0;index:idx_legacy_google_paid_retry,priority:2" json:"nextRetryAtMs"` + CreatedAtMS int64 `gorm:"column:created_at_ms;autoCreateTime:milli" json:"createdAtMs"` + UpdatedAtMS int64 `gorm:"column:updated_at_ms;autoUpdateTime:milli;index:idx_legacy_google_paid_retry,priority:3" json:"updatedAtMs"` +} + +func (LegacyGooglePaidSyncAttempt) TableName() string { + return "admin_legacy_google_paid_sync_attempts" +} + type FinanceApplication struct { ID uint `gorm:"primaryKey" json:"id"` AppCode string `gorm:"size:32;index:idx_admin_finance_app_app_status;not null" json:"appCode"` @@ -628,6 +646,14 @@ func (CoinSellerRechargeOrder) TableName() string { return "admin_coin_seller_recharge_orders" } +// BusinessTimeMS 是财务归档和统计切日使用的订单业务时间;历史订单未保存 order_date_ms 时回退到真实录入时间。 +func (order CoinSellerRechargeOrder) BusinessTimeMS() int64 { + if order.OrderDateMS > 0 { + return order.OrderDateMS + } + return order.CreatedAtMS +} + type UserWithdrawalApplication struct { ID uint `gorm:"primaryKey" json:"id"` AppCode string `gorm:"size:32;index:idx_admin_withdrawal_app_status_time;not null" json:"appCode"` @@ -635,6 +661,11 @@ type UserWithdrawalApplication struct { SalaryAssetType string `gorm:"size:64;not null;default:''" json:"salaryAssetType"` WithdrawAmount string `gorm:"type:decimal(18,2);not null;default:0.00" json:"withdrawAmount"` WithdrawAmountMinor int64 `gorm:"not null;default:0" json:"withdrawAmountMinor"` + PointFeeAmount int64 `gorm:"not null;default:0" json:"pointFeeAmount"` + PointNetAmount int64 `gorm:"not null;default:0" json:"pointNetAmount"` + PointsPerUSD int64 `gorm:"not null;default:100000" json:"pointsPerUsd"` + PointFeeBPS int32 `gorm:"not null;default:500" json:"pointFeeBps"` + PointPolicyInstance string `gorm:"column:point_policy_instance_code;size:96;not null;default:''" json:"pointPolicyInstanceCode"` WithdrawMethod string `gorm:"size:64;index:idx_admin_withdrawal_method;not null;default:''" json:"withdrawMethod"` WithdrawAddress string `gorm:"size:255;not null;default:''" json:"withdrawAddress"` FreezeCommandID string `gorm:"size:128;not null;default:''" json:"freezeCommandId"` diff --git a/server/admin/internal/modules/appconfig/gift_combo.go b/server/admin/internal/modules/appconfig/gift_combo.go new file mode 100644 index 00000000..627818f2 --- /dev/null +++ b/server/admin/internal/modules/appconfig/gift_combo.go @@ -0,0 +1,224 @@ +package appconfig + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + + "hyapp-admin-server/internal/appctx" + "hyapp-admin-server/internal/model" + "hyapp-admin-server/internal/modules/shared" + "hyapp-admin-server/internal/response" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +const ( + giftComboConfigGroup = "room-gift-combo" + giftComboConfigKey = "default" +) + +// GiftComboConfigRequest is intentionally a full replacement document. Operators can inspect the +// current normalized response before updating it, and enabled=false is therefore an unambiguous +// one-click kill switch rather than an omitted boolean accidentally inheriting true. +type GiftComboConfigRequest struct { + Enabled bool `json:"enabled"` + APIVersion int32 `json:"apiVersion"` + WindowMS int32 `json:"windowMs"` + MaxGiftCountPerBatch int32 `json:"maxGiftCountPerBatch"` + MaxGiftUnitsPerBatch int32 `json:"maxGiftUnitsPerBatch"` + MaxInFlight int32 `json:"maxInFlight"` + MaxPendingBatches int32 `json:"maxPendingBatches"` + RetryMaxAttempts int32 `json:"retryMaxAttempts"` + RetryBaseDelayMS int32 `json:"retryBaseDelayMs"` + RolloutBasisPoints int32 `json:"rolloutBasisPoints"` +} + +type storedGiftComboConfig struct { + Enabled bool `json:"enabled"` + APIVersion int32 `json:"api_version"` + WindowMS int32 `json:"window_ms"` + MaxGiftCountPerBatch int32 `json:"max_gift_count_per_batch"` + MaxGiftUnitsPerBatch int32 `json:"max_gift_units_per_batch"` + MaxInFlight int32 `json:"max_in_flight"` + MaxPendingBatches int32 `json:"max_pending_batches"` + RetryMaxAttempts int32 `json:"retry_max_attempts"` + RetryBaseDelayMS int32 `json:"retry_base_delay_ms"` + RolloutBasisPoints int32 `json:"rollout_basis_points"` +} + +type GiftComboConfigDTO struct { + GiftComboConfigRequest + AppCode string `json:"appCode"` + UpdatedAtMS int64 `json:"updatedAtMs"` +} + +func defaultStoredGiftComboConfig() storedGiftComboConfig { + return storedGiftComboConfig{ + Enabled: true, + APIVersion: 2, + WindowMS: 300, + MaxGiftCountPerBatch: 999, + MaxGiftUnitsPerBatch: 5_000, + MaxInFlight: 2, + MaxPendingBatches: 20, + RetryMaxAttempts: 5, + RetryBaseDelayMS: 250, + // 新 App 没有配置行时只展示安全的 0% 灰度,必须由管理员显式放量。 + RolloutBasisPoints: 0, + } +} + +func normalizeStoredGiftComboConfig(config storedGiftComboConfig) (storedGiftComboConfig, error) { + if config.APIVersion == 0 { + config.APIVersion = 2 + } + if config.APIVersion != 2 { + // 后台只允许调整微批窗口、背压和灰度;接口版本不是运营开关,避免把当前 Flutter + // 从具备完整结算结果重放的 V2 静默切回 V1。 + return storedGiftComboConfig{}, errors.New("apiVersion must be 2") + } + if config.WindowMS < 100 || config.WindowMS > 1_000 { + return storedGiftComboConfig{}, errors.New("windowMs must be between 100 and 1000") + } + if config.MaxGiftCountPerBatch < 1 || config.MaxGiftCountPerBatch > 999 { + return storedGiftComboConfig{}, errors.New("maxGiftCountPerBatch must be between 1 and 999") + } + if config.MaxGiftUnitsPerBatch < 1 || config.MaxGiftUnitsPerBatch > 5_000 { + return storedGiftComboConfig{}, errors.New("maxGiftUnitsPerBatch must be between 1 and 5000") + } + if config.MaxInFlight < 1 || config.MaxInFlight > 2 { + return storedGiftComboConfig{}, errors.New("maxInFlight must be between 1 and 2") + } + if config.MaxPendingBatches < 1 || config.MaxPendingBatches > 50 { + return storedGiftComboConfig{}, errors.New("maxPendingBatches must be between 1 and 50") + } + if config.MaxPendingBatches < config.MaxInFlight { + // The admin contract must reject policies Flutter cannot execute literally; normalizing this + // mismatch on-device would make an audited configuration differ from production behavior. + return storedGiftComboConfig{}, errors.New("maxPendingBatches must be greater than or equal to maxInFlight") + } + if config.RetryMaxAttempts < 1 || config.RetryMaxAttempts > 10 { + return storedGiftComboConfig{}, errors.New("retryMaxAttempts must be between 1 and 10") + } + if config.RetryBaseDelayMS < 100 || config.RetryBaseDelayMS > 5_000 { + return storedGiftComboConfig{}, errors.New("retryBaseDelayMs must be between 100 and 5000") + } + if config.RolloutBasisPoints < 0 || config.RolloutBasisPoints > 10_000 { + return storedGiftComboConfig{}, errors.New("rolloutBasisPoints must be between 0 and 10000") + } + return config, nil +} + +func normalizeStoredGiftComboConfigForRead(config storedGiftComboConfig) (storedGiftComboConfig, error) { + if config.APIVersion == 1 { + // 兼容早期已落库的灰度行,但只迁移存储标记,不恢复客户端 V1 行为。管理员下一次 + // 保存会写回 2;Update 入口仍通过严格校验拒绝新建 apiVersion=1 配置。 + config.APIVersion = 2 + } + return normalizeStoredGiftComboConfig(config) +} + +func storedGiftComboConfigFromRequest(request GiftComboConfigRequest) (storedGiftComboConfig, error) { + return normalizeStoredGiftComboConfig(storedGiftComboConfig{ + Enabled: request.Enabled, + APIVersion: request.APIVersion, + WindowMS: request.WindowMS, + MaxGiftCountPerBatch: request.MaxGiftCountPerBatch, + MaxGiftUnitsPerBatch: request.MaxGiftUnitsPerBatch, + MaxInFlight: request.MaxInFlight, + MaxPendingBatches: request.MaxPendingBatches, + RetryMaxAttempts: request.RetryMaxAttempts, + RetryBaseDelayMS: request.RetryBaseDelayMS, + RolloutBasisPoints: request.RolloutBasisPoints, + }) +} + +func giftComboConfigDTO(appCode string, config storedGiftComboConfig, updatedAtMS int64) GiftComboConfigDTO { + return GiftComboConfigDTO{ + AppCode: appctx.Normalize(appCode), + UpdatedAtMS: updatedAtMS, + GiftComboConfigRequest: GiftComboConfigRequest{ + Enabled: config.Enabled, + APIVersion: config.APIVersion, + WindowMS: config.WindowMS, + MaxGiftCountPerBatch: config.MaxGiftCountPerBatch, + MaxGiftUnitsPerBatch: config.MaxGiftUnitsPerBatch, + MaxInFlight: config.MaxInFlight, + MaxPendingBatches: config.MaxPendingBatches, + RetryMaxAttempts: config.RetryMaxAttempts, + RetryBaseDelayMS: config.RetryBaseDelayMS, + RolloutBasisPoints: config.RolloutBasisPoints, + }, + } +} + +func (s *AppConfigService) GetGiftComboConfig(appCode string) (GiftComboConfigDTO, error) { + appCode = appctx.Normalize(appCode) + item, err := s.store.GetScopedAppConfig(appCode, giftComboConfigGroup, giftComboConfigKey) + if errors.Is(err, gorm.ErrRecordNotFound) { + return giftComboConfigDTO(appCode, defaultStoredGiftComboConfig(), 0), nil + } + if err != nil { + return GiftComboConfigDTO{}, err + } + config := defaultStoredGiftComboConfig() + if err := json.Unmarshal([]byte(strings.TrimSpace(item.Value)), &config); err != nil { + return GiftComboConfigDTO{}, fmt.Errorf("stored gift combo config is invalid: %w", err) + } + config, err = normalizeStoredGiftComboConfigForRead(config) + if err != nil { + return GiftComboConfigDTO{}, err + } + return giftComboConfigDTO(appCode, config, item.UpdatedAtMS), nil +} + +func (s *AppConfigService) UpdateGiftComboConfig(appCode string, request GiftComboConfigRequest) (GiftComboConfigDTO, error) { + appCode = appctx.Normalize(appCode) + config, err := storedGiftComboConfigFromRequest(request) + if err != nil { + return GiftComboConfigDTO{}, err + } + raw, err := json.Marshal(config) + if err != nil { + return GiftComboConfigDTO{}, err + } + // UpsertScopedAppConfigs writes only the selected app_code, so a rollout or kill switch for one App + // cannot unexpectedly alter every tenant sharing this admin database. + if err := s.store.UpsertScopedAppConfigs(appCode, []model.AppConfig{{ + AppCode: appCode, + Group: giftComboConfigGroup, + Key: giftComboConfigKey, + Value: string(raw), + Description: "Flutter room gift combo batching policy", + }}); err != nil { + return GiftComboConfigDTO{}, err + } + return s.GetGiftComboConfig(appCode) +} + +func (h *Handler) GetGiftComboConfig(c *gin.Context) { + item, err := h.service.GetGiftComboConfig(appctx.FromContext(c.Request.Context())) + if err != nil { + response.ServerError(c, "获取送礼连击配置失败") + return + } + response.OK(c, item) +} + +func (h *Handler) UpdateGiftComboConfig(c *gin.Context) { + var request GiftComboConfigRequest + if err := c.ShouldBindJSON(&request); err != nil { + response.BadRequest(c, "参数不正确") + return + } + item, err := h.service.UpdateGiftComboConfig(appctx.FromContext(c.Request.Context()), request) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + shared.OperationLog(c, h.audit, "update-room-gift-combo", "admin_app_configs", "success", fmt.Sprintf("app_code=%s enabled=%t rollout_bps=%d", item.AppCode, item.Enabled, item.RolloutBasisPoints)) + response.OK(c, item) +} diff --git a/server/admin/internal/modules/appconfig/gift_combo_test.go b/server/admin/internal/modules/appconfig/gift_combo_test.go new file mode 100644 index 00000000..8e2cdfe4 --- /dev/null +++ b/server/admin/internal/modules/appconfig/gift_combo_test.go @@ -0,0 +1,60 @@ +package appconfig + +import "testing" + +func TestStoredGiftComboConfigFromRequestPreservesExplicitKillSwitch(t *testing.T) { + config, err := storedGiftComboConfigFromRequest(GiftComboConfigRequest{ + Enabled: false, + APIVersion: 2, + WindowMS: 300, + MaxGiftCountPerBatch: 999, + MaxGiftUnitsPerBatch: 5_000, + MaxInFlight: 2, + MaxPendingBatches: 20, + RetryMaxAttempts: 5, + RetryBaseDelayMS: 250, + RolloutBasisPoints: 5_000, + }) + if err != nil { + t.Fatalf("storedGiftComboConfigFromRequest failed: %v", err) + } + if config.Enabled || config.RolloutBasisPoints != 5_000 { + t.Fatalf("explicit kill switch changed during normalization: %+v", config) + } +} + +func TestNormalizeStoredGiftComboConfigRejectsQueueWithoutBackpressure(t *testing.T) { + config := defaultStoredGiftComboConfig() + config.APIVersion = 1 + if _, err := normalizeStoredGiftComboConfig(config); err == nil { + t.Fatal("apiVersion 1 must not turn the current Flutter back to the legacy HTTP contract") + } + config = defaultStoredGiftComboConfig() + config.MaxInFlight = 3 + if _, err := normalizeStoredGiftComboConfig(config); err == nil { + t.Fatal("maxInFlight above the server contract must be rejected") + } + config = defaultStoredGiftComboConfig() + config.MaxPendingBatches = 0 + if _, err := normalizeStoredGiftComboConfig(config); err == nil { + t.Fatal("unbounded/empty pending queue policy must be rejected") + } + config = defaultStoredGiftComboConfig() + config.MaxInFlight = 2 + config.MaxPendingBatches = 1 + if _, err := normalizeStoredGiftComboConfig(config); err == nil { + t.Fatal("pending queue smaller than in-flight concurrency must be rejected") + } +} + +func TestNormalizeStoredGiftComboConfigForReadPromotesLegacyVersionMarker(t *testing.T) { + config := defaultStoredGiftComboConfig() + config.APIVersion = 1 + resolved, err := normalizeStoredGiftComboConfigForRead(config) + if err != nil { + t.Fatalf("legacy stored config must remain readable: %v", err) + } + if resolved.APIVersion != 2 { + t.Fatalf("legacy stored marker was not promoted to V2: %+v", resolved) + } +} diff --git a/server/admin/internal/modules/appconfig/handler.go b/server/admin/internal/modules/appconfig/handler.go index 525b32b7..b049e7fb 100644 --- a/server/admin/internal/modules/appconfig/handler.go +++ b/server/admin/internal/modules/appconfig/handler.go @@ -23,7 +23,8 @@ func New(store *repository.Store, audit shared.OperationLogger) *Handler { } func (h *Handler) ListH5Links(c *gin.Context) { - items, err := h.service.ListH5Links() + appCode := appctx.FromContext(c.Request.Context()) + items, err := h.service.ListH5Links(appCode) if err != nil { response.ServerError(c, "获取 H5 配置失败") return @@ -38,12 +39,13 @@ func (h *Handler) UpdateH5Links(c *gin.Context) { return } - items, err := h.service.UpdateH5Links(request) + appCode := appctx.FromContext(c.Request.Context()) + items, err := h.service.UpdateH5Links(appCode, request) if err != nil { response.BadRequest(c, err.Error()) return } - shared.OperationLog(c, h.audit, "update-app-h5-links", "admin_app_configs", "success", fmt.Sprintf("%d h5 links", len(request.Items))) + shared.OperationLog(c, h.audit, "update-app-h5-links", "admin_app_configs", "success", fmt.Sprintf("app_code=%s %d h5 links", appCode, len(request.Items))) response.OK(c, gin.H{"items": items, "total": len(items)}) } @@ -53,12 +55,13 @@ func (h *Handler) CreateH5Link(c *gin.Context) { response.BadRequest(c, "参数不正确") return } - item, err := h.service.CreateH5Link(request) + appCode := appctx.FromContext(c.Request.Context()) + item, err := h.service.CreateH5Link(appCode, request) if err != nil { response.BadRequest(c, err.Error()) return } - shared.OperationLog(c, h.audit, "create-app-h5-link", "admin_app_configs", "success", fmt.Sprintf("h5_key=%s", item.Key)) + shared.OperationLog(c, h.audit, "create-app-h5-link", "admin_app_configs", "success", fmt.Sprintf("app_code=%s h5_key=%s", appCode, item.Key)) response.Created(c, item) } @@ -73,12 +76,13 @@ func (h *Handler) UpdateH5Link(c *gin.Context) { response.BadRequest(c, "参数不正确") return } - item, err := h.service.UpdateH5Link(key, request) + appCode := appctx.FromContext(c.Request.Context()) + item, err := h.service.UpdateH5Link(appCode, key, request) if err != nil { response.BadRequest(c, err.Error()) return } - shared.OperationLog(c, h.audit, "update-app-h5-link", "admin_app_configs", "success", fmt.Sprintf("h5_key=%s", item.Key)) + shared.OperationLog(c, h.audit, "update-app-h5-link", "admin_app_configs", "success", fmt.Sprintf("app_code=%s h5_key=%s", appCode, item.Key)) response.OK(c, item) } @@ -88,11 +92,12 @@ func (h *Handler) DeleteH5Link(c *gin.Context) { response.BadRequest(c, "h5 config key is invalid") return } - if err := h.service.DeleteH5Link(key); err != nil { + appCode := appctx.FromContext(c.Request.Context()) + if err := h.service.DeleteH5Link(appCode, key); err != nil { response.BadRequest(c, err.Error()) return } - shared.OperationLog(c, h.audit, "delete-app-h5-link", "admin_app_configs", "success", fmt.Sprintf("h5_key=%s", key)) + shared.OperationLog(c, h.audit, "delete-app-h5-link", "admin_app_configs", "success", fmt.Sprintf("app_code=%s h5_key=%s", appCode, key)) response.OK(c, gin.H{"deleted": true}) } diff --git a/server/admin/internal/modules/appconfig/routes.go b/server/admin/internal/modules/appconfig/routes.go index 071421c9..f2d5106d 100644 --- a/server/admin/internal/modules/appconfig/routes.go +++ b/server/admin/internal/modules/appconfig/routes.go @@ -16,6 +16,8 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { protected.PUT("/admin/app-config/h5-links", middleware.RequirePermission("app-config:update"), h.UpdateH5Links) protected.PUT("/admin/app-config/h5-links/:key", middleware.RequirePermission("app-config:update"), h.UpdateH5Link) protected.DELETE("/admin/app-config/h5-links/:key", middleware.RequirePermission("app-config:update"), h.DeleteH5Link) + protected.GET("/admin/app-config/gift-combo", middleware.RequirePermission("app-config:view"), h.GetGiftComboConfig) + protected.PUT("/admin/app-config/gift-combo", middleware.RequirePermission("app-config:update"), h.UpdateGiftComboConfig) protected.GET("/admin/app-config/explore-tabs", middleware.RequirePermission("app-config:view"), h.ListExploreTabs) protected.POST("/admin/app-config/explore-tabs", middleware.RequirePermission("app-config:update"), h.CreateExploreTab) diff --git a/server/admin/internal/modules/appconfig/service.go b/server/admin/internal/modules/appconfig/service.go index 5f39c0b4..9d2c3b67 100644 --- a/server/admin/internal/modules/appconfig/service.go +++ b/server/admin/internal/modules/appconfig/service.go @@ -11,6 +11,8 @@ import ( "hyapp-admin-server/internal/appctx" "hyapp-admin-server/internal/model" "hyapp-admin-server/internal/repository" + + "gorm.io/gorm" ) const h5LinkGroup = "h5-links" @@ -35,6 +37,7 @@ type AppConfigService struct { } type H5Link struct { + AppCode string `json:"appCode"` Key string `json:"key"` Label string `json:"label"` URL string `json:"url"` @@ -126,8 +129,10 @@ func NewService(store *repository.Store) *AppConfigService { return &AppConfigService{store: store} } -func (s *AppConfigService) ListH5Links() ([]H5Link, error) { - configs, err := s.store.ListAppConfigs(h5LinkGroup) +func (s *AppConfigService) ListH5Links(appCode string) ([]H5Link, error) { + appCode = appctx.Normalize(appCode) + // H5 是 App 品牌和运营内容的一部分,未配置时必须返回空列表,不能继承其他 App 的历史链接。 + configs, err := s.store.ListOwnedAppConfigs(appCode, h5LinkGroup) if err != nil { return nil, err } @@ -139,7 +144,8 @@ func (s *AppConfigService) ListH5Links() ([]H5Link, error) { return items, nil } -func (s *AppConfigService) UpdateH5Links(request updateH5LinksRequest) ([]H5Link, error) { +func (s *AppConfigService) UpdateH5Links(appCode string, request updateH5LinksRequest) ([]H5Link, error) { + appCode = appctx.Normalize(appCode) if len(request.Items) == 0 { return nil, errors.New("items are required") } @@ -147,7 +153,7 @@ func (s *AppConfigService) UpdateH5Links(request updateH5LinksRequest) ([]H5Link items := make([]model.AppConfig, 0, len(request.Items)) seen := make(map[string]struct{}, len(request.Items)) for _, item := range request.Items { - config, err := h5LinkModelFromPayload(item) + config, err := h5LinkModelFromPayload(appCode, item) if err != nil { return nil, err } @@ -159,43 +165,62 @@ func (s *AppConfigService) UpdateH5Links(request updateH5LinksRequest) ([]H5Link items = append(items, config) } - if err := s.store.UpsertAppConfigs(items); err != nil { + if err := s.store.UpsertScopedAppConfigs(appCode, items); err != nil { return nil, err } - return s.ListH5Links() + return s.ListH5Links(appCode) } -func (s *AppConfigService) CreateH5Link(req h5LinkPayload) (H5Link, error) { - item, err := h5LinkModelFromPayload(req) +func (s *AppConfigService) CreateH5Link(appCode string, req h5LinkPayload) (H5Link, error) { + appCode = appctx.Normalize(appCode) + item, err := h5LinkModelFromPayload(appCode, req) if err != nil { return H5Link{}, err } - if err := s.store.CreateAppConfig(&item); err != nil { + if _, err := s.store.GetOwnedAppConfig(appCode, h5LinkGroup, item.Key); err == nil { + return H5Link{}, fmt.Errorf("h5 link key already exists: %s", item.Key) + } else if !errors.Is(err, gorm.ErrRecordNotFound) { return H5Link{}, err } - return h5LinkFromModel(item), nil + // upsert 只以三元唯一键写当前 App,重试不会触碰其他 App 的同名 H5 key。 + if err := s.store.UpsertScopedAppConfigs(appCode, []model.AppConfig{item}); err != nil { + return H5Link{}, err + } + stored, err := s.store.GetOwnedAppConfig(appCode, h5LinkGroup, item.Key) + if err != nil { + return H5Link{}, err + } + return h5LinkFromModel(stored), nil } -func (s *AppConfigService) UpdateH5Link(key string, req h5LinkPayload) (H5Link, error) { - item, err := s.store.GetAppConfig(h5LinkGroup, key) +func (s *AppConfigService) UpdateH5Link(appCode string, key string, req h5LinkPayload) (H5Link, error) { + appCode = appctx.Normalize(appCode) + item, err := s.store.GetOwnedAppConfig(appCode, h5LinkGroup, key) if err != nil { return H5Link{}, err } req.Key = item.Key - updated, err := h5LinkModelFromPayload(req) + updated, err := h5LinkModelFromPayload(appCode, req) if err != nil { return H5Link{}, err } - item.Value = updated.Value - item.Description = updated.Description - if err := s.store.UpdateAppConfig(&item); err != nil { + // 请求作用域决定 app_code;请求体只能更新当前 App 已存在的 key。 + if err := s.store.UpsertScopedAppConfigs(appCode, []model.AppConfig{updated}); err != nil { return H5Link{}, err } - return h5LinkFromModel(item), nil + stored, err := s.store.GetOwnedAppConfig(appCode, h5LinkGroup, updated.Key) + if err != nil { + return H5Link{}, err + } + return h5LinkFromModel(stored), nil } -func (s *AppConfigService) DeleteH5Link(key string) error { - return s.store.DeleteAppConfig(h5LinkGroup, key) +func (s *AppConfigService) DeleteH5Link(appCode string, key string) error { + appCode = appctx.Normalize(appCode) + if _, err := s.store.GetOwnedAppConfig(appCode, h5LinkGroup, key); err != nil { + return err + } + return s.store.DeleteOwnedAppConfig(appCode, h5LinkGroup, key) } func (s *AppConfigService) ListBanners(appCode string, options repository.AppBannerListOptions) ([]AppBanner, error) { @@ -517,7 +542,7 @@ func validateH5URL(value string) error { return nil } -func h5LinkModelFromPayload(req h5LinkPayload) (model.AppConfig, error) { +func h5LinkModelFromPayload(appCode string, req h5LinkPayload) (model.AppConfig, error) { key := strings.TrimSpace(req.Key) label := strings.TrimSpace(req.Label) url := strings.TrimSpace(req.URL) @@ -534,6 +559,7 @@ func h5LinkModelFromPayload(req h5LinkPayload) (model.AppConfig, error) { return model.AppConfig{}, err } return model.AppConfig{ + AppCode: appctx.Normalize(appCode), Group: h5LinkGroup, Key: key, Value: url, @@ -564,6 +590,7 @@ func h5LinkFromModel(config model.AppConfig) H5Link { label = config.Key } return H5Link{ + AppCode: appctx.Normalize(config.AppCode), Key: config.Key, Label: label, URL: config.Value, diff --git a/server/admin/internal/modules/appconfig/service_test.go b/server/admin/internal/modules/appconfig/service_test.go index ca1726fb..d4aa23e7 100644 --- a/server/admin/internal/modules/appconfig/service_test.go +++ b/server/admin/internal/modules/appconfig/service_test.go @@ -1,14 +1,59 @@ package appconfig import ( + "os" + "strings" "testing" "time" "hyapp-admin-server/internal/model" ) +func TestH5AppScopeSchemaMigrationCreatesScopedKeys(t *testing.T) { + body, err := os.ReadFile("../../../migrations/086_app_config_app_scope.sql") + if err != nil { + t.Fatalf("read h5 app scope migration failed: %v", err) + } + sqlText := string(body) + for _, snippet := range []string{ + "app_code VARCHAR(32) NOT NULL DEFAULT ''", + "is_deleted BOOLEAN NOT NULL DEFAULT FALSE", + "DROP INDEX uk_admin_app_configs_group_key", + "uk_admin_app_config_app_group_key (app_code, `group`, `key`)", + } { + if !strings.Contains(sqlText, snippet) { + t.Fatalf("h5 app scope migration missing %q", snippet) + } + } + // 086 只负责补齐通用 scoped 存储结构;091 再按 H5 的强隔离边界迁走历史空作用域数据。 + for _, appCode := range []string{"huwaa", "fami", "yumi", "aslan"} { + if strings.Contains(sqlText, "'"+appCode+"'") { + t.Fatalf("migration must not hard-code app_code %q", appCode) + } + } +} + +func TestH5StrictAppScopeMigrationMovesLegacyLinksToDefaultApp(t *testing.T) { + body, err := os.ReadFile("../../../migrations/091_h5_config_strict_app_scope.sql") + if err != nil { + t.Fatalf("read strict h5 app scope migration failed: %v", err) + } + sqlText := string(body) + for _, snippet := range []string{ + "scoped.app_code = 'lalu'", + "legacy.app_code = ''", + "legacy.`group` = 'h5-links'", + "SET app_code = 'lalu'", + "`group` = 'h5-links'", + } { + if !strings.Contains(sqlText, snippet) { + t.Fatalf("strict h5 app scope migration missing %q", snippet) + } + } +} + func TestH5LinkModelFromPayloadValidatesDynamicConfig(t *testing.T) { - item, err := h5LinkModelFromPayload(h5LinkPayload{ + item, err := h5LinkModelFromPayload(" YUMI ", h5LinkPayload{ Key: "host-center.v2", Label: "Host Center", URL: "https://h5.example.com/host", @@ -16,13 +61,13 @@ func TestH5LinkModelFromPayloadValidatesDynamicConfig(t *testing.T) { if err != nil { t.Fatalf("h5 config should be valid: %v", err) } - if item.Group != h5LinkGroup || item.Key != "host-center.v2" || item.Description != "Host Center" || item.Value != "https://h5.example.com/host" { + if item.AppCode != "yumi" || item.Group != h5LinkGroup || item.Key != "host-center.v2" || item.Description != "Host Center" || item.Value != "https://h5.example.com/host" { t.Fatalf("h5 config model mismatch: %+v", item) } } func TestH5LinkModelFromPayloadRejectsInvalidKey(t *testing.T) { - _, err := h5LinkModelFromPayload(h5LinkPayload{ + _, err := h5LinkModelFromPayload("lalu", h5LinkPayload{ Key: "host center", Label: "Host Center", URL: "https://h5.example.com/host", diff --git a/server/admin/internal/modules/appuser/detail_extras.go b/server/admin/internal/modules/appuser/detail_extras.go new file mode 100644 index 00000000..9014dde8 --- /dev/null +++ b/server/admin/internal/modules/appuser/detail_extras.go @@ -0,0 +1,231 @@ +package appuser + +import ( + "context" + "database/sql" + "errors" + "fmt" + + "hyapp-admin-server/internal/appctx" + userv1 "hyapp.local/api/proto/user/v1" + + mysqlDriver "github.com/go-sql-driver/mysql" +) + +// AppUserThirdPartyIdentity 暴露三方绑定 openid 事实:openid = third_party_identities.provider_subject。 +type AppUserThirdPartyIdentity struct { + Provider string `json:"provider"` + OpenID string `json:"openid"` + UnionID string `json:"unionId"` + CreatedAtMs int64 `json:"createdAtMs"` +} + +// AppUserDevice 是设备 tab 的一行设备档案,来自 user_devices 认证成功聚合。 +type AppUserDevice struct { + DeviceID string `json:"deviceId"` + Platform string `json:"platform"` + DeviceModel string `json:"deviceModel"` + Manufacturer string `json:"manufacturer"` + OSVersion string `json:"osVersion"` + IMEI string `json:"imei"` + AppVersion string `json:"appVersion"` + BuildNumber string `json:"buildNumber"` + FirstSeenAtMs int64 `json:"firstSeenAtMs"` + LastSeenAtMs int64 `json:"lastSeenAtMs"` +} + +// AppUserAccessToken 是后台按需重签的 access token 结果;不含 refresh token,取用行为已写操作日志。 +type AppUserAccessToken struct { + AccessToken string `json:"accessToken"` + TokenType string `json:"tokenType"` + ExpiresInSec int64 `json:"expiresInSec"` + SessionID string `json:"sessionId"` + DeviceID string `json:"deviceId"` + SessionLastHeartbeatAtMs int64 `json:"sessionLastHeartbeatAtMs"` +} + +// accessTokenIssuerClient 只暴露后台重签 token 的 RPC;与 userAdminProjectionClient 相同, +// 通过类型断言探测能力,避免为单一后台功能扩大 userclient.Client 主契约。 +type accessTokenIssuerClient interface { + AdminIssueUserAccessToken(context.Context, *userv1.AdminIssueUserAccessTokenRequest) (*userv1.AdminIssueUserAccessTokenResponse, error) +} + +// loginAuditSnapshotSQL 按单个 login_type 等值命中 idx_login_audit_latest_success 后取最新一条。 +// 不能对多个 login_type 用 IN + 全局 ORDER BY:在线用户 access_resign 行每天数千条,索引范围必须逐类型收敛。 +const loginAuditSnapshotSQL = ` + (SELECT COALESCE(audit.client_ip, '') AS client_ip, audit.ip_country_code, audit.app_version, audit.build_number, audit.created_at_ms, audit.id + FROM login_audit AS audit + WHERE audit.app_code = ? AND audit.user_id = ? AND audit.result = 'success' AND audit.blocked = 0 AND audit.login_type = ? + ORDER BY audit.created_at_ms DESC, audit.id DESC + LIMIT 1)` + +// realLoginTypes 与 dashboard 用户版本统计口径一致:只有真实登录/续期链路携带客户端版本头。 +// access_resign 是 25s 心跳重签,app_version 恒为空,只用于 IP 新鲜度。 +var realLoginTypes = []string{"password", "third_party", "refresh"} + +// ipLoginTypes 在真实登录之外追加 access_resign:在线用户的最新 IP 由心跳审计行提供(≤25s 旧)。 +var ipLoginTypes = []string{"password", "third_party", "refresh", "access_resign"} + +// fillLoginSnapshots 填充详情页的最新登录 IP 和当前 App 版本快照,两者口径不同必须分开取: +// IP 含 access_resign(新鲜度优先),版本排除 access_resign(心跳行版本恒为空,会把口径打回 unknown)。 +func (s *Service) fillLoginSnapshots(ctx context.Context, item *AppUser, userID int64) error { + if s.userDB == nil || item == nil { + return nil + } + appCode := appctx.FromContext(ctx) + + ip, ipCountry, _, _, ipAtMs, err := s.queryLatestLoginAudit(ctx, appCode, userID, ipLoginTypes) + if err != nil { + return err + } + item.LastLoginIP = ip + item.LastLoginIPCountryCode = ipCountry + item.LastLoginIPAtMs = ipAtMs + + _, _, appVersion, buildNumber, versionAtMs, err := s.queryLatestLoginAudit(ctx, appCode, userID, realLoginTypes) + if err != nil { + return err + } + item.AppVersion = appVersion + item.BuildNumber = buildNumber + item.AppVersionAtMs = versionAtMs + return nil +} + +// queryLatestLoginAudit 对给定登录类型集合做“每类型 LIMIT 1 + UNION ALL 再取最新”的单用户查询。 +// 没有任何成功登录时返回零值而不是错误:老用户/被清审计的用户详情页仍要能打开。 +func (s *Service) queryLatestLoginAudit(ctx context.Context, appCode string, userID int64, loginTypes []string) (ip string, ipCountry string, appVersion string, buildNumber string, createdAtMs int64, err error) { + if len(loginTypes) == 0 { + return "", "", "", "", 0, nil + } + querySQL := "" + args := make([]any, 0, len(loginTypes)*3) + for i, loginType := range loginTypes { + if i > 0 { + querySQL += "\n\tUNION ALL\n" + } + querySQL += loginAuditSnapshotSQL + args = append(args, appCode, userID, loginType) + } + // UNION ALL 的列名取第一个子查询的别名;外层只按 (created_at_ms, id) 在最多 len(loginTypes) 条候选里挑最新。 + querySQL = ` + SELECT candidate.client_ip, candidate.ip_country_code, candidate.app_version, candidate.build_number, candidate.created_at_ms + FROM (` + querySQL + ` + ) AS candidate + ORDER BY candidate.created_at_ms DESC, candidate.id DESC + LIMIT 1` + + scanErr := s.userDB.QueryRowContext(ctx, querySQL, args...).Scan(&ip, &ipCountry, &appVersion, &buildNumber, &createdAtMs) + if errors.Is(scanErr, sql.ErrNoRows) { + return "", "", "", "", 0, nil + } + if scanErr != nil { + return "", "", "", "", 0, scanErr + } + return ip, ipCountry, appVersion, buildNumber, createdAtMs, nil +} + +// fillThirdPartyIdentities 读取用户全部三方绑定;idx_third_party_user_id(app_code, user_id) 覆盖查询。 +func (s *Service) fillThirdPartyIdentities(ctx context.Context, item *AppUser, userID int64) error { + if s.userDB == nil || item == nil { + return nil + } + rows, err := s.userDB.QueryContext(ctx, ` + SELECT provider, provider_subject, COALESCE(provider_union_id, ''), created_at_ms + FROM third_party_identities + WHERE app_code = ? AND user_id = ? + ORDER BY provider ASC, created_at_ms ASC + `, appctx.FromContext(ctx), userID) + if err != nil { + return err + } + defer rows.Close() + + identities := make([]AppUserThirdPartyIdentity, 0, 2) + for rows.Next() { + var identity AppUserThirdPartyIdentity + if err := rows.Scan(&identity.Provider, &identity.OpenID, &identity.UnionID, &identity.CreatedAtMs); err != nil { + return err + } + identities = append(identities, identity) + } + item.ThirdPartyIdentities = identities + return rows.Err() +} + +// fillDevices 读取设备档案。user_devices 由 user-service 迁移 013 引入,线上 DDL 人工执行, +// 表尚未创建(Error 1146)时按空列表处理,不能让整个用户详情 500。 +func (s *Service) fillDevices(ctx context.Context, item *AppUser, userID int64) error { + if s.userDB == nil || item == nil { + return nil + } + rows, err := s.userDB.QueryContext(ctx, ` + SELECT device_id, platform, device_model, manufacturer, os_version, imei, app_version, build_number, first_seen_at_ms, last_seen_at_ms + FROM user_devices + WHERE app_code = ? AND user_id = ? + ORDER BY last_seen_at_ms DESC + LIMIT 20 + `, appctx.FromContext(ctx), userID) + if err != nil { + var mysqlErr *mysqlDriver.MySQLError + if errors.As(err, &mysqlErr) && mysqlErr.Number == 1146 { + item.Devices = []AppUserDevice{} + return nil + } + return err + } + defer rows.Close() + + devices := make([]AppUserDevice, 0, 4) + for rows.Next() { + var device AppUserDevice + if err := rows.Scan( + &device.DeviceID, + &device.Platform, + &device.DeviceModel, + &device.Manufacturer, + &device.OSVersion, + &device.IMEI, + &device.AppVersion, + &device.BuildNumber, + &device.FirstSeenAtMs, + &device.LastSeenAtMs, + ); err != nil { + return err + } + devices = append(devices, device) + } + item.Devices = devices + return rows.Err() +} + +// IssueAccessToken 调 user-service 按用户最新 active session 重签 access token。 +// NotFound(无活跃会话)原样向上抛,由 handler 映射成前端可直接展示的 404 文案。 +func (s *Service) IssueAccessToken(ctx context.Context, userID int64) (AppUserAccessToken, error) { + if s.tokenIssuerClient == nil { + return AppUserAccessToken{}, fmt.Errorf("user client is not configured") + } + nowMs := nowMillis() + appCode := appctx.FromContext(ctx) + resp, err := s.tokenIssuerClient.AdminIssueUserAccessToken(ctx, &userv1.AdminIssueUserAccessTokenRequest{ + Meta: &userv1.RequestMeta{ + RequestId: fmt.Sprintf("admin-app-user-token-%d", nowMs), + Caller: "hyapp-admin-server", + SentAtMs: nowMs, + AppCode: appCode, + }, + AppCode: appCode, + UserId: userID, + }) + if err != nil { + return AppUserAccessToken{}, err + } + return AppUserAccessToken{ + AccessToken: resp.GetAccessToken(), + TokenType: resp.GetTokenType(), + ExpiresInSec: resp.GetExpiresInSec(), + SessionID: resp.GetSessionId(), + DeviceID: resp.GetDeviceId(), + SessionLastHeartbeatAtMs: resp.GetSessionLastHeartbeatAtMs(), + }, nil +} diff --git a/server/admin/internal/modules/appuser/detail_extras_test.go b/server/admin/internal/modules/appuser/detail_extras_test.go new file mode 100644 index 00000000..51d912ff --- /dev/null +++ b/server/admin/internal/modules/appuser/detail_extras_test.go @@ -0,0 +1,175 @@ +package appuser + +import ( + "context" + "errors" + "testing" + + userv1 "hyapp.local/api/proto/user/v1" + + "github.com/DATA-DOG/go-sqlmock" + mysqlDriver "github.com/go-sql-driver/mysql" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func TestFillLoginSnapshotsSplitsIPAndVersionScopes(t *testing.T) { + // IP 口径包含 access_resign(在线用户 25s 心跳行提供最新 IP),版本口径必须排除它(心跳行版本恒为空)。 + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherRegexp)) + if err != nil { + t.Fatalf("sqlmock failed: %v", err) + } + defer db.Close() + service := &Service{userDB: db} + + columns := []string{"client_ip", "ip_country_code", "app_version", "build_number", "created_at_ms"} + // 第一条查询(IP 口径)带 access_resign 参数;第二条(版本口径)只有三种真实登录类型。 + mock.ExpectQuery(`FROM \(`). + WithArgs("lalu", int64(42), "password", "lalu", int64(42), "third_party", "lalu", int64(42), "refresh", "lalu", int64(42), "access_resign"). + WillReturnRows(sqlmock.NewRows(columns).AddRow("203.0.113.9", "SA", "", "", int64(9000))) + mock.ExpectQuery(`FROM \(`). + WithArgs("lalu", int64(42), "password", "lalu", int64(42), "third_party", "lalu", int64(42), "refresh"). + WillReturnRows(sqlmock.NewRows(columns).AddRow("198.51.100.7", "SA", "1.4.2", "142", int64(7000))) + + item := AppUser{} + if err := service.fillLoginSnapshots(context.Background(), &item, 42); err != nil { + t.Fatalf("fillLoginSnapshots failed: %v", err) + } + if item.LastLoginIP != "203.0.113.9" || item.LastLoginIPCountryCode != "SA" || item.LastLoginIPAtMs != 9000 { + t.Fatalf("last login ip snapshot mismatch: %+v", item) + } + if item.AppVersion != "1.4.2" || item.BuildNumber != "142" || item.AppVersionAtMs != 7000 { + t.Fatalf("app version snapshot mismatch: %+v", item) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("unmet expectations: %v", err) + } +} + +func TestFillLoginSnapshotsToleratesUserWithoutAudit(t *testing.T) { + // 老用户或审计被清理时详情页仍要能打开,各字段落零值而不是 500。 + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherRegexp)) + if err != nil { + t.Fatalf("sqlmock failed: %v", err) + } + defer db.Close() + service := &Service{userDB: db} + + columns := []string{"client_ip", "ip_country_code", "app_version", "build_number", "created_at_ms"} + mock.ExpectQuery(`FROM \(`).WillReturnRows(sqlmock.NewRows(columns)) + mock.ExpectQuery(`FROM \(`).WillReturnRows(sqlmock.NewRows(columns)) + + item := AppUser{} + if err := service.fillLoginSnapshots(context.Background(), &item, 43); err != nil { + t.Fatalf("fillLoginSnapshots failed: %v", err) + } + if item.LastLoginIP != "" || item.LastLoginIPAtMs != 0 || item.AppVersion != "" || item.AppVersionAtMs != 0 { + t.Fatalf("empty audit must produce zero values: %+v", item) + } +} + +func TestFillDevicesToleratesMissingTable(t *testing.T) { + // user_devices 由人工执行 DDL 引入;表未创建(Error 1146)时详情页按空列表展示,不能整页失败。 + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherRegexp)) + if err != nil { + t.Fatalf("sqlmock failed: %v", err) + } + defer db.Close() + service := &Service{userDB: db} + + mock.ExpectQuery(`FROM user_devices`).WillReturnError(&mysqlDriver.MySQLError{Number: 1146, Message: "Table 'hyapp_user.user_devices' doesn't exist"}) + + item := AppUser{} + if err := service.fillDevices(context.Background(), &item, 42); err != nil { + t.Fatalf("fillDevices must tolerate missing table: %v", err) + } + if item.Devices == nil || len(item.Devices) != 0 { + t.Fatalf("missing table must yield empty device list: %#v", item.Devices) + } + + // 其他数据库错误必须继续上抛,不能被 1146 分支吞掉。 + mock.ExpectQuery(`FROM user_devices`).WillReturnError(&mysqlDriver.MySQLError{Number: 1054, Message: "Unknown column"}) + if err := service.fillDevices(context.Background(), &item, 42); err == nil { + t.Fatalf("non-1146 errors must propagate") + } +} + +func TestFillDevicesScansRowsInLastSeenOrder(t *testing.T) { + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherRegexp)) + if err != nil { + t.Fatalf("sqlmock failed: %v", err) + } + defer db.Close() + service := &Service{userDB: db} + + rows := sqlmock.NewRows([]string{"device_id", "platform", "device_model", "manufacturer", "os_version", "imei", "app_version", "build_number", "first_seen_at_ms", "last_seen_at_ms"}). + AddRow("android_abc", "android", "Redmi Note 8 Pro", "Xiaomi", "13", "", "1.4.2", "142", int64(1000), int64(9000)). + AddRow("ios_def", "ios", "iPhone17,2", "Apple", "18.1", "", "1.4.0", "140", int64(500), int64(8000)) + mock.ExpectQuery(`FROM user_devices`).WithArgs("lalu", int64(42)).WillReturnRows(rows) + + item := AppUser{} + if err := service.fillDevices(context.Background(), &item, 42); err != nil { + t.Fatalf("fillDevices failed: %v", err) + } + if len(item.Devices) != 2 || item.Devices[0].DeviceID != "android_abc" || item.Devices[1].Manufacturer != "Apple" { + t.Fatalf("device rows mismatch: %#v", item.Devices) + } + if item.Devices[0].LastSeenAtMs != 9000 || item.Devices[0].FirstSeenAtMs != 1000 { + t.Fatalf("device timestamps mismatch: %#v", item.Devices[0]) + } +} + +type fakeTokenIssuerClient struct { + response *userv1.AdminIssueUserAccessTokenResponse + err error + lastReq *userv1.AdminIssueUserAccessTokenRequest +} + +func (f *fakeTokenIssuerClient) AdminIssueUserAccessToken(_ context.Context, req *userv1.AdminIssueUserAccessTokenRequest) (*userv1.AdminIssueUserAccessTokenResponse, error) { + f.lastReq = req + if f.err != nil { + return nil, f.err + } + return f.response, nil +} + +func TestIssueAccessTokenMapsResponseAndAppCode(t *testing.T) { + fake := &fakeTokenIssuerClient{response: &userv1.AdminIssueUserAccessTokenResponse{ + AccessToken: "jwt-token", + TokenType: "Bearer", + ExpiresInSec: 3600, + SessionId: "sess_1", + DeviceId: "android_abc", + SessionLastHeartbeatAtMs: 123456, + }} + service := &Service{tokenIssuerClient: fake} + + result, err := service.IssueAccessToken(context.Background(), 42) + if err != nil { + t.Fatalf("IssueAccessToken failed: %v", err) + } + if result.AccessToken != "jwt-token" || result.TokenType != "Bearer" || result.ExpiresInSec != 3600 || + result.SessionID != "sess_1" || result.DeviceID != "android_abc" || result.SessionLastHeartbeatAtMs != 123456 { + t.Fatalf("token result mismatch: %+v", result) + } + // app_code 必须同时写入显式字段和 meta,user-service 端优先消费显式字段。 + if fake.lastReq.GetUserId() != 42 || fake.lastReq.GetAppCode() != "lalu" || fake.lastReq.GetMeta().GetAppCode() != "lalu" { + t.Fatalf("request app scope mismatch: %+v", fake.lastReq) + } +} + +func TestIssueAccessTokenPropagatesNotFound(t *testing.T) { + // 无活跃会话时 user-service 返回 NotFound;service 层原样透传,由 handler 映射成 404 中文文案。 + fake := &fakeTokenIssuerClient{err: status.Error(codes.NotFound, "active session not found")} + service := &Service{tokenIssuerClient: fake} + + _, err := service.IssueAccessToken(context.Background(), 42) + if grpcStatus, ok := status.FromError(err); !ok || grpcStatus.Code() != codes.NotFound { + t.Fatalf("expected NotFound passthrough, got %v", err) + } + + var missing *Service = &Service{} + if _, err := missing.IssueAccessToken(context.Background(), 42); err == nil || errors.Is(err, nil) { + t.Fatalf("nil token client must fail closed") + } +} diff --git a/server/admin/internal/modules/appuser/export.go b/server/admin/internal/modules/appuser/export.go new file mode 100644 index 00000000..24def26a --- /dev/null +++ b/server/admin/internal/modules/appuser/export.go @@ -0,0 +1,271 @@ +package appuser + +import ( + "context" + "encoding/csv" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "hyapp-admin-server/internal/appctx" + "hyapp-admin-server/internal/model" + "hyapp-admin-server/internal/modules/shared" + "hyapp-admin-server/internal/response" + + "github.com/gin-gonic/gin" +) + +const AppUserExportJobType = "app-user-export" + +type appUserExportJobStore interface { + CreateJob(job *model.AdminJob) error +} + +type appUserExportPayload struct { + AppCode string `json:"appCode"` + Query listQuery `json:"query"` +} + +type appUserExportJobResponse struct { + JobID uint `json:"jobId"` + Status string `json:"status"` + SnapshotAtMs int64 `json:"snapshotAtMs"` +} + +type appUserExportResult struct { + JobID uint `json:"jobId"` + Rows int64 `json:"rows"` + Artifact string `json:"artifact"` + DownloadPath string `json:"downloadPath"` + SnapshotAtMs int64 `json:"snapshotAtMs"` +} + +func (s *Service) CreateExportJob(ctx context.Context, actor shared.Actor, query listQuery) (*appUserExportJobResponse, error) { + if s.jobStore == nil || !s.jobsConfig.Enabled { + return nil, errors.New("admin jobs are not enabled") + } + query = normalizeListQuery(query) + query = stableAppUserExportQuery(query) + query.Page = 1 + query.PageSize = 100 + query.SnapshotAtMs = nowMillis() + payload := appUserExportPayload{ + AppCode: appctx.FromContext(ctx), + Query: query, + } + body, err := json.Marshal(payload) + if err != nil { + return nil, err + } + job := model.AdminJob{ + Type: AppUserExportJobType, + Status: model.JobStatusPending, + PayloadJSON: string(body), + MaxAttempts: s.jobsConfig.MaxAttempts, + CreatedBy: actor.UserID, + CreatedByName: strings.TrimSpace(actor.Username), + } + if job.MaxAttempts <= 0 { + job.MaxAttempts = 3 + } + if err := s.jobStore.CreateJob(&job); err != nil { + return nil, err + } + return &appUserExportJobResponse{JobID: job.ID, Status: job.Status, SnapshotAtMs: query.SnapshotAtMs}, nil +} + +// HandleExportJob 使用创建任务时固化的 App 与筛选条件逐页读取;任务执行期间不会重新解释前端 query。 +func (s *Service) HandleExportJob(ctx context.Context, job *model.AdminJob) (string, string, error) { + if job == nil || job.ID == 0 { + return "", "", errors.New("job is required") + } + var payload appUserExportPayload + if err := json.Unmarshal([]byte(job.PayloadJSON), &payload); err != nil { + return "", "", err + } + payload.AppCode = strings.TrimSpace(payload.AppCode) + payload.Query = normalizeListQuery(payload.Query) + payload.Query = stableAppUserExportQuery(payload.Query) + if payload.AppCode == "" || payload.Query.SnapshotAtMs <= 0 { + return "", "", ErrInvalidArgument + } + + artifactDir := strings.TrimSpace(s.jobsConfig.ArtifactDir) + if artifactDir == "" { + artifactDir = "storage/exports" + } + if err := os.MkdirAll(artifactDir, 0o750); err != nil { + return "", "", err + } + file, err := os.CreateTemp(artifactDir, fmt.Sprintf("app-user-export-%d-*.csv", job.ID)) + if err != nil { + return "", "", err + } + artifactName := filepath.Base(file.Name()) + succeeded := false + defer func() { + _ = file.Close() + if !succeeded { + _ = os.Remove(file.Name()) + } + }() + + // UTF-8 BOM 让运营直接使用 Excel 打开中文昵称、国家和身份时不出现乱码。 + if _, err := file.WriteString("\xEF\xBB\xBF"); err != nil { + return "", "", err + } + writer := csv.NewWriter(file) + if err := writer.Write(appUserExportHeaders()); err != nil { + return "", "", err + } + + exportCtx := appctx.WithContext(ctx, payload.AppCode) + query := payload.Query + query.Page = 1 + query.PageSize = 100 + var exported int64 + for { + items, _, err := s.ListUsers(exportCtx, query) + if err != nil { + return "", "", err + } + for i := range items { + if err := writer.Write(appUserExportRow(items[i])); err != nil { + return "", "", err + } + } + exported += int64(len(items)) + if len(items) < query.PageSize { + break + } + last := items[len(items)-1] + cursorUserID, err := strconv.ParseInt(last.UserID, 10, 64) + if err != nil || cursorUserID <= 0 || last.CreatedAtMs <= 0 { + return "", "", errors.New("app user export cursor is invalid") + } + // 使用 (created_at_ms,user_id) keyset 而不是 OFFSET;任务运行期间新增用户或已读用户变更筛选字段时, + // 后续批次仍不会位移、重复或跳过尚未读取的稳定主键区间。 + query.ExportCursorCreatedAtMs = last.CreatedAtMs + query.ExportCursorUserID = cursorUserID + } + writer.Flush() + if err := writer.Error(); err != nil { + return "", "", err + } + if err := file.Sync(); err != nil { + return "", "", err + } + if err := file.Close(); err != nil { + return "", "", err + } + succeeded = true + + result := appUserExportResult{ + JobID: job.ID, + Rows: exported, + Artifact: artifactName, + DownloadPath: fmt.Sprintf("/api/v1/jobs/%d/artifact", job.ID), + SnapshotAtMs: payload.Query.SnapshotAtMs, + } + resultJSON, err := json.Marshal(result) + if err != nil { + return "", "", err + } + return string(resultJSON), artifactName, nil +} + +func stableAppUserExportQuery(query listQuery) listQuery { + // 导出顺序不属于筛选语义,统一 created_at/user_id 倒序才能和固定 keyset 游标一致。 + // 这也绕开 coin 投影的全量内存排序,避免每一批重复读取形成 O(批次数×用户数)。 + query.SortBy = "created_at" + query.SortDirection = "desc" + return query +} + +func (h *Handler) CreateExport(c *gin.Context) { + job, err := h.service.CreateExportJob(c.Request.Context(), shared.ActorFromContext(c), parseListQuery(c)) + if err != nil { + response.ServerError(c, "创建 App 用户导出任务失败") + return + } + writeAppUserAuditLog(c, h.audit, "export-app-users", "app_user_exports", int64(job.JobID), "success", fmt.Sprintf("snapshot_at_ms=%d", job.SnapshotAtMs)) + response.OK(c, job) +} + +func (h *Handler) HandleExportJob(ctx context.Context, job *model.AdminJob) (string, string, error) { + return h.service.HandleExportJob(ctx, job) +} + +func appUserExportHeaders() []string { + return []string{ + "用户ID", "短ID", "靓号", "昵称", "头像", "性别", "生日", "年龄", "国家", "区域", + "VIP等级", "VIP到期时间(ms)", "富豪真实等级", "富豪展示等级", "富豪临时目标", "富豪到期时间(ms)", + "魅力真实等级", "魅力展示等级", "魅力临时目标", "魅力到期时间(ms)", "游戏等级", "身份", + "注册设备", "金币", "钻石", "累计充值(USD分)", "状态", "封禁类型", "封禁到期时间(ms)", "封禁原因", + "注册时间(ms)", "最近活跃时间(ms)", "最近成功登录时间(ms)", "最新操作人", "最新操作", "最新操作时间(ms)", + } +} + +func appUserExportRow(item AppUser) []string { + age := "" + if item.Age != nil { + age = strconv.FormatInt(int64(*item.Age), 10) + } + banType := "" + if item.Ban.Active { + if item.Ban.Permanent { + banType = "永久" + } else { + banType = "限时" + } + } + operatorName := "" + operatorAction := "" + if item.LastOperator != nil { + operatorName = item.LastOperator.Name + operatorAction = item.LastOperator.Action + } + row := []string{ + item.UserID, item.DefaultDisplayUserID, firstNonEmptyString(item.PrettyDisplayUserID, item.PrettyID), item.Username, item.Avatar, + item.Gender, item.Birth, age, firstNonEmptyString(item.CountryDisplayName, item.CountryName, item.Country), item.RegionName, + strconv.FormatInt(int64(item.VIP.Level), 10), strconv.FormatInt(item.VIP.ExpiresAtMs, 10), + strconv.FormatInt(int64(item.Levels.Wealth.RealLevel), 10), strconv.FormatInt(int64(item.Levels.Wealth.DisplayLevel), 10), strconv.FormatInt(int64(item.Levels.Wealth.TemporaryTargetLevel), 10), strconv.FormatInt(item.Levels.Wealth.ExpiresAtMs, 10), + strconv.FormatInt(int64(item.Levels.Charm.RealLevel), 10), strconv.FormatInt(int64(item.Levels.Charm.DisplayLevel), 10), strconv.FormatInt(int64(item.Levels.Charm.TemporaryTargetLevel), 10), strconv.FormatInt(item.Levels.Charm.ExpiresAtMs, 10), + strconv.FormatInt(int64(item.Levels.Game.DisplayLevel), 10), strings.Join(item.Roles, "|"), item.RegisterDevice, + strconv.FormatInt(item.Coin, 10), strconv.FormatInt(item.Diamond, 10), strconv.FormatInt(item.CumulativeRechargeUSDMinor, 10), + item.Status, banType, strconv.FormatInt(item.Ban.ExpiresAtMs, 10), item.Ban.Reason, + strconv.FormatInt(item.CreatedAtMs, 10), strconv.FormatInt(item.LastActiveAtMs, 10), strconv.FormatInt(item.LastLoginAtMs, 10), + operatorName, operatorAction, strconv.FormatInt(item.LastOperatedAtMs, 10), + } + for index := range row { + row[index] = spreadsheetSafeCSVCell(row[index]) + } + return row +} + +func spreadsheetSafeCSVCell(value string) string { + trimmed := strings.TrimLeft(value, " \t\r\n") + if trimmed == "" { + return value + } + switch trimmed[0] { + case '=', '+', '-', '@': + // Excel/Sheets 会把这些前缀解释为公式;前置单引号只影响表格解释,不丢失原始文本内容。 + return "'" + value + default: + return value + } +} + +func firstNonEmptyString(values ...string) string { + for _, value := range values { + if value = strings.TrimSpace(value); value != "" { + return value + } + } + return "" +} diff --git a/server/admin/internal/modules/appuser/export_test.go b/server/admin/internal/modules/appuser/export_test.go new file mode 100644 index 00000000..f2a2250e --- /dev/null +++ b/server/admin/internal/modules/appuser/export_test.go @@ -0,0 +1,106 @@ +package appuser + +import ( + "context" + "encoding/json" + "strings" + "testing" + + "hyapp-admin-server/internal/appctx" + "hyapp-admin-server/internal/config" + "hyapp-admin-server/internal/model" + "hyapp-admin-server/internal/modules/shared" +) + +type recordingAppUserExportStore struct { + job *model.AdminJob +} + +func (s *recordingAppUserExportStore) CreateJob(job *model.AdminJob) error { + copy := *job + copy.ID = 81 + job.ID = copy.ID + s.job = © + return nil +} + +func TestCreateExportJobPersistsFilterSnapshot(t *testing.T) { + store := &recordingAppUserExportStore{} + service := &Service{ + jobStore: store, + jobsConfig: config.JobsConfig{ + Enabled: true, + MaxAttempts: 5, + }, + } + ctx := appctx.WithContext(context.Background(), "lalu") + response, err := service.CreateExportJob(ctx, shared.Actor{UserID: 7, Username: "operator"}, listQuery{ + Country: " ph ", + Status: "active", + Keyword: "1299324", + SortBy: "coin", + SortDirection: "asc", + Page: 9, + PageSize: 10, + }) + if err != nil { + t.Fatalf("create export job: %v", err) + } + if response.JobID != 81 || response.SnapshotAtMs <= 0 || store.job == nil { + t.Fatalf("response/job mismatch: response=%+v job=%+v", response, store.job) + } + if store.job.Type != AppUserExportJobType || store.job.MaxAttempts != 5 || store.job.CreatedBy != 7 { + t.Fatalf("job metadata mismatch: %+v", store.job) + } + var payload appUserExportPayload + if err := json.Unmarshal([]byte(store.job.PayloadJSON), &payload); err != nil { + t.Fatalf("decode payload: %v", err) + } + if payload.AppCode != "lalu" || payload.Query.Country != "PH" || payload.Query.Status != "active" || payload.Query.Keyword != "1299324" { + t.Fatalf("filter snapshot mismatch: %+v", payload) + } + if payload.Query.Page != 1 || payload.Query.PageSize != 100 || payload.Query.SnapshotAtMs != response.SnapshotAtMs { + t.Fatalf("pagination snapshot mismatch: %+v", payload.Query) + } + if payload.Query.SortBy != "created_at" || payload.Query.SortDirection != "desc" { + t.Fatalf("export must use stable database pagination instead of coin projection sort: %+v", payload.Query) + } +} + +func TestStableAppUserExportQueryForcesDescendingKeysetOrder(t *testing.T) { + for _, query := range []listQuery{ + {SortBy: "created_at", SortDirection: "asc"}, + {SortBy: "coin", SortDirection: "asc"}, + } { + stable := stableAppUserExportQuery(normalizeListQuery(query)) + if stable.SortBy != "created_at" || stable.SortDirection != "desc" { + t.Fatalf("export order must match descending keyset: input=%+v stable=%+v", query, stable) + } + } +} + +func TestAppUserExportWhereUsesSnapshotAndKeysetCursor(t *testing.T) { + query := normalizeListQuery(listQuery{ + SnapshotAtMs: 1_000, + ExportCursorCreatedAtMs: 900, + ExportCursorUserID: 88, + }) + whereSQL, args := appUserListWhereSQLAt("lalu", query, query.SnapshotAtMs) + for _, fragment := range []string{ + "u.created_at_ms <= ?", + "u.created_at_ms < ? OR (u.created_at_ms = ? AND u.user_id < ?)", + } { + if !strings.Contains(whereSQL, fragment) { + t.Fatalf("export where SQL missing %q: %s", fragment, whereSQL) + } + } + wantTail := []any{int64(1_000), int64(900), int64(900), int64(88)} + if len(args) < len(wantTail) { + t.Fatalf("export args too short: %+v", args) + } + for index, want := range wantTail { + if got := args[len(args)-len(wantTail)+index]; got != want { + t.Fatalf("export cursor arg[%d]=%v want=%v all=%+v", index, got, want, args) + } + } +} diff --git a/server/admin/internal/modules/appuser/handler.go b/server/admin/internal/modules/appuser/handler.go index ec25a596..144216ad 100644 --- a/server/admin/internal/modules/appuser/handler.go +++ b/server/admin/internal/modules/appuser/handler.go @@ -4,6 +4,8 @@ import ( "database/sql" "errors" "fmt" + "io" + "log/slog" "strconv" "strings" @@ -15,6 +17,8 @@ import ( "hyapp-admin-server/internal/response" "github.com/gin-gonic/gin" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) type Handler struct { @@ -23,9 +27,9 @@ type Handler struct { audit shared.OperationLogger } -func New(userClient userclient.Client, activityClient activityclient.Client, adminDB *sql.DB, userDB *sql.DB, walletDB *sql.DB, cfg config.Config, audit shared.OperationLogger) *Handler { +func New(userClient userclient.Client, activityClient activityclient.Client, adminDB *sql.DB, userDB *sql.DB, walletDB *sql.DB, cfg config.Config, audit shared.OperationLogger, opts ...ServiceOption) *Handler { return &Handler{ - service: NewService(userClient, activityClient, adminDB, userDB, walletDB), + service: NewService(userClient, activityClient, adminDB, userDB, walletDB, opts...), cfg: cfg, audit: audit, } @@ -113,11 +117,64 @@ func (h *Handler) UpdateUser(c *gin.Context) { } func (h *Handler) BanUser(c *gin.Context) { - h.setUserStatus(c, "banned", "ban-app-user", "用户已封禁") + userID, ok := parseInt64ID(c, "id") + if !ok { + return + } + var req banUserRequest + if err := c.ShouldBindJSON(&req); err != nil && !errors.Is(err, io.EOF) { + response.BadRequest(c, "封禁参数不正确") + return + } + result, err := h.service.AdminBanUser(c.Request.Context(), userID, int64(middleware.CurrentUserID(c)), middleware.CurrentRequestID(c), req) + if err != nil { + writeMutationError(c, err, "封禁 App 用户失败") + return + } + detail := "permanent" + if req.ExpiresAtMs > 0 { + detail = fmt.Sprintf("expires_at_ms=%d", req.ExpiresAtMs) + } + writeAppUserAuditLog(c, h.audit, "ban-app-user", "app_users", userID, "success", detail) + response.OK(c, result) } func (h *Handler) UnbanUser(c *gin.Context) { - h.setUserStatus(c, "active", "unban-app-user", "用户已解封") + userID, ok := parseInt64ID(c, "id") + if !ok { + return + } + var req unbanUserRequest + if err := c.ShouldBindJSON(&req); err != nil && !errors.Is(err, io.EOF) { + response.BadRequest(c, "解封参数不正确") + return + } + result, err := h.service.AdminUnbanUser(c.Request.Context(), userID, int64(middleware.CurrentUserID(c)), middleware.CurrentRequestID(c), req) + if err != nil { + writeMutationError(c, err, "解封 App 用户失败") + return + } + writeAppUserAuditLog(c, h.audit, "unban-app-user", "app_users", userID, "success", strings.TrimSpace(req.Reason)) + response.OK(c, result) +} + +func (h *Handler) AdjustTemporaryLevels(c *gin.Context) { + userID, ok := parseInt64ID(c, "id") + if !ok { + return + } + var req adjustTemporaryLevelsRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "等级调整参数不正确") + return + } + user, err := h.service.AdjustTemporaryLevels(c.Request.Context(), userID, int64(middleware.CurrentUserID(c)), middleware.CurrentRequestID(c), req) + if err != nil { + writeMutationError(c, err, "调整用户等级失败") + return + } + writeAppUserAuditLog(c, h.audit, "adjust-app-user-level", "app_users", userID, "success", fmt.Sprintf("tracks=%d", len(req.Adjustments))) + response.OK(c, user) } func (h *Handler) SetPassword(c *gin.Context) { @@ -138,6 +195,30 @@ func (h *Handler) SetPassword(c *gin.Context) { response.OK(c, gin.H{"passwordSet": true}) } +func (h *Handler) IssueAccessToken(c *gin.Context) { + userID, ok := parseInt64ID(c, "id") + if !ok { + return + } + result, err := h.service.IssueAccessToken(c.Request.Context(), userID) + if err != nil { + if grpcStatus, ok := status.FromError(err); ok && grpcStatus.Code() == codes.NotFound { + // 无活跃会话是明确业务事实:token 是无状态 JWT,没有 active session 就没有可重签对象。 + response.NotFound(c, "该用户当前没有活跃会话") + return + } + writeMutationError(c, err, "获取用户 Token 失败") + return + } + // 重签出的 token 等价于账号接管能力,且 user-service 侧刻意不写 login_audit—— + // 后台操作日志是唯一审计痕迹,所以这里 fail-closed:审计落库失败就不返回 token。 + if err := shared.OperationLogWithResourceIDStrict(c, h.audit, "issue-app-user-token", "app_users", fmt.Sprintf("%d", userID), "success", fmt.Sprintf("session_id=%s", result.SessionID)); err != nil { + response.ServerError(c, "操作日志写入失败,未返回 Token") + return + } + response.OK(c, result) +} + func (h *Handler) setUserStatus(c *gin.Context, status string, action string, successMessage string) { userID, ok := parseInt64ID(c, "id") if !ok { @@ -257,6 +338,11 @@ func writeReadError(c *gin.Context, err error, fallback string) { response.NotFound(c, "App 用户不存在") return } + // 读路径对外只回兜底文案、不透出内部细节;原始错误必须落日志,否则详情页 500 无从排查。 + slog.ErrorContext(c.Request.Context(), "admin_app_user_read_failed", + slog.String("path", c.Request.URL.Path), + slog.String("error", err.Error()), + ) response.ServerError(c, fallback) } @@ -269,6 +355,22 @@ func writeMutationError(c *gin.Context, err error, fallback string) { response.BadRequest(c, err.Error()) return } + if grpcStatus, ok := status.FromError(err); ok { + message := strings.TrimSpace(grpcStatus.Message()) + if message == "" { + message = fallback + } + switch grpcStatus.Code() { + case codes.InvalidArgument, codes.AlreadyExists, codes.FailedPrecondition, codes.Aborted: + // 等级目标不高于真实等级、规则缺失或仍有其他封禁都属于可修正的业务拒绝,不能伪装成 500。 + response.BadRequest(c, message) + case codes.NotFound: + response.NotFound(c, message) + default: + response.ServerError(c, fallback) + } + return + } response.ServerError(c, fallback) } diff --git a/server/admin/internal/modules/appuser/handler_test.go b/server/admin/internal/modules/appuser/handler_test.go new file mode 100644 index 00000000..283ba996 --- /dev/null +++ b/server/admin/internal/modules/appuser/handler_test.go @@ -0,0 +1,34 @@ +package appuser + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func TestWriteMutationErrorMapsGRPCBusinessFailures(t *testing.T) { + gin.SetMode(gin.TestMode) + tests := []struct { + name string + err error + wantStatus int + }{ + {name: "invalid level", err: status.Error(codes.FailedPrecondition, "target must exceed real level"), wantStatus: http.StatusBadRequest}, + {name: "missing user", err: status.Error(codes.NotFound, "user not found"), wantStatus: http.StatusNotFound}, + {name: "owner unavailable", err: status.Error(codes.Unavailable, "activity unavailable"), wantStatus: http.StatusInternalServerError}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + recorder := httptest.NewRecorder() + context, _ := gin.CreateTestContext(recorder) + writeMutationError(context, test.err, "mutation failed") + if recorder.Code != test.wantStatus { + t.Fatalf("HTTP status=%d want=%d body=%s", recorder.Code, test.wantStatus, recorder.Body.String()) + } + }) + } +} diff --git a/server/admin/internal/modules/appuser/projection_test.go b/server/admin/internal/modules/appuser/projection_test.go new file mode 100644 index 00000000..6c8f1ee8 --- /dev/null +++ b/server/admin/internal/modules/appuser/projection_test.go @@ -0,0 +1,85 @@ +package appuser + +import ( + "context" + "testing" + + "hyapp-admin-server/internal/appctx" + activityv1 "hyapp.local/api/proto/activity/v1" + userv1 "hyapp.local/api/proto/user/v1" +) + +type fakeUserAdminProjectionClient struct { + profiles map[int64]*userv1.UserAdminProfile +} + +func (f *fakeUserAdminProjectionClient) BatchGetUserAdminProfiles(context.Context, *userv1.BatchGetUserAdminProfilesRequest) (*userv1.BatchGetUserAdminProfilesResponse, error) { + return &userv1.BatchGetUserAdminProfilesResponse{Profiles: f.profiles}, nil +} + +func (f *fakeUserAdminProjectionClient) AdminBanUser(context.Context, *userv1.AdminBanUserRequest) (*userv1.AdminBanUserResponse, error) { + return &userv1.AdminBanUserResponse{}, nil +} + +func (f *fakeUserAdminProjectionClient) AdminUnbanUser(context.Context, *userv1.AdminUnbanUserRequest) (*userv1.AdminUnbanUserResponse, error) { + return &userv1.AdminUnbanUserResponse{}, nil +} + +type fakeLevelAdminProjectionClient struct { + profiles []*activityv1.AdminUserLevelProfile +} + +func (f *fakeLevelAdminProjectionClient) BatchGetUserLevelAdminProfiles(context.Context, *activityv1.BatchGetUserLevelAdminProfilesRequest) (*activityv1.BatchGetUserLevelAdminProfilesResponse, error) { + return &activityv1.BatchGetUserLevelAdminProfilesResponse{Profiles: f.profiles}, nil +} + +func (f *fakeLevelAdminProjectionClient) AdjustTemporaryUserLevels(context.Context, *activityv1.AdjustTemporaryUserLevelsRequest) (*activityv1.AdjustTemporaryUserLevelsResponse, error) { + return &activityv1.AdjustTemporaryUserLevelsResponse{}, nil +} + +func TestAdminProjectionEnrichmentMapsOwnerFacts(t *testing.T) { + service := &Service{ + userAdminClient: &fakeUserAdminProjectionClient{profiles: map[int64]*userv1.UserAdminProfile{ + 10001: { + Birth: "2000-02-03", + RegisterDevice: "Pixel 9", + LastSuccessLoginAtMs: 7000, + Roles: []string{"host", "bd_leader", "manager"}, + Ban: &userv1.ActiveUserBanSummary{ + Active: true, Source: "admin", BanId: "ban-1", Permanent: false, ExpiresAtMs: 9000, Reason: "risk", + }, + }, + }}, + levelAdminClient: &fakeLevelAdminProjectionClient{profiles: []*activityv1.AdminUserLevelProfile{ + {UserId: 10001, Tracks: []*activityv1.AdminLevelTrackProfile{ + {Track: "wealth", RealLevel: 8, RealTotalValue: 800, DisplayLevel: 14, DisplayValue: 1400, TemporaryLevelId: "temp-1", TemporaryTargetLevel: 14, StartedAtMs: 1000, ExpiresAtMs: 9000}, + {Track: "charm", RealLevel: 9, RealTotalValue: 900, DisplayLevel: 9, DisplayValue: 900}, + {Track: "game", RealLevel: 6, RealTotalValue: 600, DisplayLevel: 6, DisplayValue: 600}, + }}, + }}, + } + items := []AppUser{{UserID: "10001"}} + ctx := appctx.WithContext(context.Background(), "lalu") + if err := service.fillUserAdminProfiles(ctx, items, []int64{10001}); err != nil { + t.Fatalf("fill user profiles: %v", err) + } + if err := service.fillLevelAdminProfiles(ctx, items, []int64{10001}); err != nil { + t.Fatalf("fill level profiles: %v", err) + } + item := items[0] + if item.Birth != "2000-02-03" || item.RegisterDevice != "Pixel 9" || item.LastLoginAtMs != 7000 { + t.Fatalf("user owner facts mismatch: %+v", item) + } + if len(item.Roles) != 3 || item.Roles[0] != "主播" || item.Roles[1] != "BD Leader" || item.Roles[2] != "经理" { + t.Fatalf("roles mismatch: %#v", item.Roles) + } + if !item.Ban.Active || item.Ban.ID != "ban-1" || item.Ban.ExpiresAtMs != 9000 { + t.Fatalf("ban mismatch: %+v", item.Ban) + } + if item.Levels.Wealth.RealLevel != 8 || item.Levels.Wealth.DisplayLevel != 14 || item.Levels.Wealth.TemporaryTargetLevel != 14 { + t.Fatalf("wealth overlay mismatch: %+v", item.Levels.Wealth) + } + if item.Levels.Game.DisplayLevel != 6 || item.Levels.Charm.DisplayLevel != 9 { + t.Fatalf("read-only levels mismatch: %+v", item.Levels) + } +} diff --git a/server/admin/internal/modules/appuser/request.go b/server/admin/internal/modules/appuser/request.go index be687d8d..c463d3e7 100644 --- a/server/admin/internal/modules/appuser/request.go +++ b/server/admin/internal/modules/appuser/request.go @@ -15,6 +15,11 @@ type listQuery struct { SortDirection string StartMs int64 EndMs int64 + // SnapshotAtMs 只由异步导出设置,使短号租约和筛选时间基线在所有分页中保持一致。 + SnapshotAtMs int64 + // ExportCursor* 仅在 job worker 内存中推进 keyset;不进入任务 payload,也不暴露为 HTTP 查询参数。 + ExportCursorCreatedAtMs int64 `json:"-"` + ExportCursorUserID int64 `json:"-"` } type loginLogQuery struct { @@ -58,3 +63,23 @@ type updateUserRequest struct { type setPasswordRequest struct { Password string `json:"password" binding:"required"` } + +type temporaryLevelAdjustmentRequest struct { + Track string `json:"track" binding:"required"` + Level int32 `json:"level" binding:"required"` + DurationDays int32 `json:"duration_days" binding:"required"` +} + +type adjustTemporaryLevelsRequest struct { + Adjustments []temporaryLevelAdjustmentRequest `json:"adjustments" binding:"required,min=1,max=2,dive"` + Reason string `json:"reason"` +} + +type banUserRequest struct { + ExpiresAtMs int64 `json:"expires_at_ms"` + Reason string `json:"reason"` +} + +type unbanUserRequest struct { + Reason string `json:"reason"` +} diff --git a/server/admin/internal/modules/appuser/routes.go b/server/admin/internal/modules/appuser/routes.go index da18e152..a5caf42b 100644 --- a/server/admin/internal/modules/appuser/routes.go +++ b/server/admin/internal/modules/appuser/routes.go @@ -12,12 +12,15 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { } protected.GET("/app/users", middleware.RequirePermission("app-user:view"), h.ListUsers) + protected.POST("/exports/app-users", middleware.RequirePermission("app-user:export"), h.CreateExport) protected.GET("/app/users/bans", middleware.RequirePermission("app-user:view"), h.ListBannedUsers) protected.GET("/app/users/login-logs", middleware.RequirePermission("app-user:view"), h.ListLoginLogs) protected.GET("/app/users/:id/login-logs", middleware.RequirePermission("app-user:view"), h.ListUserLoginLogs) protected.GET("/app/users/:id", middleware.RequirePermission("app-user:view"), h.GetUser) protected.PATCH("/app/users/:id", middleware.RequirePermission("app-user:update"), h.UpdateUser) + protected.PUT("/app/users/:id/levels", middleware.RequirePermission("app-user:level"), h.AdjustTemporaryLevels) protected.POST("/app/users/:id/ban", middleware.RequirePermission("app-user:status"), h.BanUser) protected.POST("/app/users/:id/unban", middleware.RequirePermission("app-user:status"), h.UnbanUser) protected.POST("/app/users/:id/password", middleware.RequirePermission("app-user:password"), h.SetPassword) + protected.POST("/app/users/:id/access-token", middleware.RequirePermission("app-user:token"), h.IssueAccessToken) } diff --git a/server/admin/internal/modules/appuser/service.go b/server/admin/internal/modules/appuser/service.go index ad82defb..25fb79f2 100644 --- a/server/admin/internal/modules/appuser/service.go +++ b/server/admin/internal/modules/appuser/service.go @@ -12,11 +12,15 @@ import ( "time" "hyapp-admin-server/internal/appctx" + "hyapp-admin-server/internal/config" "hyapp-admin-server/internal/integration/activityclient" "hyapp-admin-server/internal/integration/userclient" + "hyapp-admin-server/internal/integration/walletclient" "hyapp-admin-server/internal/modules/shared" "hyapp-admin-server/internal/security" activityv1 "hyapp.local/api/proto/activity/v1" + userv1 "hyapp.local/api/proto/user/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" ) var ( @@ -25,37 +29,141 @@ var ( ) type Service struct { - userClient userclient.Client - activityClient activityclient.Client - adminDB *sql.DB - userDB *sql.DB - walletDB *sql.DB + userClient userclient.Client + userAdminClient userAdminProjectionClient + // tokenIssuerClient 只在 gRPC client 具备后台重签 token 能力时非 nil。 + tokenIssuerClient accessTokenIssuerClient + activityClient activityclient.Client + levelAdminClient levelAdminProjectionClient + walletClient rechargeStatsClient + jobStore appUserExportJobStore + jobsConfig config.JobsConfig + adminDB *sql.DB + userDB *sql.DB + walletDB *sql.DB +} + +// rechargeStatsClient 只暴露用户后台需要的累计充值聚合;列表不能为展示字段回扫钱包流水。 +type rechargeStatsClient interface { + BatchGetUserRechargeStats(context.Context, *walletv1.BatchGetUserRechargeStatsRequest) (*walletv1.BatchGetUserRechargeStatsResponse, error) +} + +// userAdminProjectionClient 保证生日、成功登录和封禁语义继续由 user-service 统一解释。 +type userAdminProjectionClient interface { + BatchGetUserAdminProfiles(context.Context, *userv1.BatchGetUserAdminProfilesRequest) (*userv1.BatchGetUserAdminProfilesResponse, error) + AdminBanUser(context.Context, *userv1.AdminBanUserRequest) (*userv1.AdminBanUserResponse, error) + AdminUnbanUser(context.Context, *userv1.AdminUnbanUserRequest) (*userv1.AdminUnbanUserResponse, error) +} + +// levelAdminProjectionClient 只允许后台读 overlay 和一次原子调整,游戏等级没有写入口。 +type levelAdminProjectionClient interface { + BatchGetUserLevelAdminProfiles(context.Context, *activityv1.BatchGetUserLevelAdminProfilesRequest) (*activityv1.BatchGetUserLevelAdminProfilesResponse, error) + AdjustTemporaryUserLevels(context.Context, *activityv1.AdjustTemporaryUserLevelsRequest) (*activityv1.AdjustTemporaryUserLevelsResponse, error) +} + +type ServiceOption func(*Service) + +// WithWalletClient 通过 wallet-service 读取它拥有的充值聚合,保留 walletDB 仅用于现有余额与 VIP 展示。 +func WithWalletClient(client walletclient.Client) ServiceOption { + return func(service *Service) { + service.walletClient = client + } +} + +// WithExportJobs 把任务创建留在 admin 数据库,把分批导出交给统一 job runner 执行。 +func WithExportJobs(store appUserExportJobStore, cfg config.Config) ServiceOption { + return func(service *Service) { + service.jobStore = store + service.jobsConfig = cfg.Jobs + } } type AppUser struct { - Avatar string `json:"avatar"` - Balances []AppUserAssetBalance `json:"balances,omitempty"` - Coin int64 `json:"coin"` - Country string `json:"country"` - CountryDisplayName string `json:"countryDisplayName"` - CountryName string `json:"countryName"` - CreatedAtMs int64 `json:"createdAtMs"` - DefaultDisplayUserID string `json:"defaultDisplayUserId"` - Diamond int64 `json:"diamond"` - DisplayUserID string `json:"displayUserId"` - EquippedResources []AppUserResource `json:"equippedResources,omitempty"` - Gender string `json:"gender"` - LastActiveAtMs int64 `json:"lastActiveAtMs"` - PrettyDisplayUserID string `json:"prettyDisplayUserId"` - PrettyID string `json:"prettyId"` - RegionID int64 `json:"regionId"` - RegionName string `json:"regionName"` - Resources []AppUserResource `json:"resources,omitempty"` - Status string `json:"status"` - UpdatedAtMs int64 `json:"updatedAtMs"` - UserID string `json:"userId"` - Username string `json:"username"` - VIP AppUserVIP `json:"vip"` + Age *int32 `json:"age,omitempty"` + // AppVersion/BuildNumber/AppVersionAtMs 是最近一次真实登录(password/third_party/refresh, + // 不含 access_resign 心跳重签)携带的客户端版本快照,仅在 GetUser 填充,列表不回扫审计表。 + AppVersion string `json:"appVersion"` + AppVersionAtMs int64 `json:"appVersionAtMs"` + Avatar string `json:"avatar"` + Balances []AppUserAssetBalance `json:"balances,omitempty"` + Ban AppUserBan `json:"ban"` + Birth string `json:"birth"` + BuildNumber string `json:"buildNumber"` + Coin int64 `json:"coin"` + Country string `json:"country"` + CountryDisplayName string `json:"countryDisplayName"` + CountryName string `json:"countryName"` + CreatedAtMs int64 `json:"createdAtMs"` + CumulativeRechargeUSDMinor int64 `json:"cumulativeRechargeUsdMinor"` + DefaultDisplayUserID string `json:"defaultDisplayUserId"` + // Devices 是设备 tab 数据,来自 user_devices 认证成功聚合;仅在 GetUser 填充,按最近活跃倒序。 + Devices []AppUserDevice `json:"devices,omitempty"` + Diamond int64 `json:"diamond"` + DisplayUserID string `json:"displayUserId"` + EquippedResources []AppUserResource `json:"equippedResources,omitempty"` + Gender string `json:"gender"` + LastActiveAtMs int64 `json:"lastActiveAtMs"` + LastLoginAtMs int64 `json:"lastLoginAtMs"` + // LastLoginIp* 来自 login_audit 全部登录类型(含 access_resign 心跳重签)的最新成功行, + // 在线用户可精确到 25s 级新鲜度;仅在 GetUser 填充。 + LastLoginIP string `json:"lastLoginIp"` + LastLoginIPAtMs int64 `json:"lastLoginIpAtMs"` + LastLoginIPCountryCode string `json:"lastLoginIpCountryCode"` + LastOperatedAtMs int64 `json:"lastOperatedAtMs"` + LastOperator *AppUserOperator `json:"lastOperator,omitempty"` + Levels AppUserLevels `json:"levels"` + PrettyDisplayUserID string `json:"prettyDisplayUserId"` + PrettyID string `json:"prettyId"` + RegisterDevice string `json:"registerDevice"` + // RegisterDeviceID/RegisterOsVersion 是 users 表注册时一次性快照,配合设备 tab 的注册设备区块展示。 + RegisterDeviceID string `json:"registerDeviceId"` + RegisterOsVersion string `json:"registerOsVersion"` + RegionID int64 `json:"regionId"` + RegionName string `json:"regionName"` + Resources []AppUserResource `json:"resources,omitempty"` + Roles []string `json:"roles"` + Status string `json:"status"` + // ThirdPartyIdentities 暴露三方绑定 openid(provider_subject);一个用户可能绑定多个 provider。 + ThirdPartyIdentities []AppUserThirdPartyIdentity `json:"thirdPartyIdentities,omitempty"` + UpdatedAtMs int64 `json:"updatedAtMs"` + UserID string `json:"userId"` + Username string `json:"username"` + VIP AppUserVIP `json:"vip"` +} + +// AppUserLevels 固定三个成长轨道,前端无需按数组位置猜测富豪、游戏和魅力列。 +type AppUserLevels struct { + Wealth AppUserLevel `json:"wealth"` + Game AppUserLevel `json:"game"` + Charm AppUserLevel `json:"charm"` +} + +// AppUserLevel 同时暴露真实成长事实和当前临时展示事实,详情页可明确标出到期时间。 +type AppUserLevel struct { + Track string `json:"track"` + RealLevel int32 `json:"realLevel"` + RealTotalValue int64 `json:"realTotalValue"` + DisplayLevel int32 `json:"displayLevel"` + DisplayValue int64 `json:"displayValue"` + TemporaryLevelID string `json:"temporaryLevelId,omitempty"` + TemporaryTargetLevel int32 `json:"temporaryTargetLevel,omitempty"` + StartedAtMs int64 `json:"startedAtMs,omitempty"` + ExpiresAtMs int64 `json:"expiresAtMs,omitempty"` +} + +type AppUserBan struct { + ID string `json:"id,omitempty"` + Active bool `json:"active"` + Permanent bool `json:"permanent"` + ExpiresAtMs int64 `json:"expiresAtMs"` + Source string `json:"source,omitempty"` + Reason string `json:"reason,omitempty"` +} + +type AppUserOperator struct { + AdminID string `json:"adminId"` + Name string `json:"name"` + Action string `json:"action"` } type AppUserAssetBalance struct { @@ -110,8 +218,23 @@ type SetUserStatusResult struct { RoomEvictError string `json:"roomEvictError,omitempty"` } -func NewService(userClient userclient.Client, activityClient activityclient.Client, adminDB *sql.DB, userDB *sql.DB, walletDB *sql.DB) *Service { - return &Service{userClient: userClient, activityClient: activityClient, adminDB: adminDB, userDB: userDB, walletDB: walletDB} +func NewService(userClient userclient.Client, activityClient activityclient.Client, adminDB *sql.DB, userDB *sql.DB, walletDB *sql.DB, opts ...ServiceOption) *Service { + service := &Service{userClient: userClient, activityClient: activityClient, adminDB: adminDB, userDB: userDB, walletDB: walletDB} + if adminClient, ok := any(userClient).(userAdminProjectionClient); ok { + service.userAdminClient = adminClient + } + if tokenClient, ok := any(userClient).(accessTokenIssuerClient); ok { + service.tokenIssuerClient = tokenClient + } + if adminClient, ok := any(activityClient).(levelAdminProjectionClient); ok { + service.levelAdminClient = adminClient + } + for _, opt := range opts { + if opt != nil { + opt(service) + } + } + return service } func (s *Service) ListUsers(ctx context.Context, query listQuery) ([]AppUser, int64, error) { @@ -120,7 +243,10 @@ func (s *Service) ListUsers(ctx context.Context, query listQuery) ([]AppUser, in } query = normalizeListQuery(query) appCode := appctx.FromContext(ctx) - nowMs := nowMillis() + nowMs := query.SnapshotAtMs + if nowMs <= 0 { + nowMs = nowMillis() + } whereSQL, args := appUserListWhereSQLAt(appCode, query, nowMs) total, err := countRows(ctx, s.userDB, whereSQL, args...) @@ -192,6 +318,18 @@ func (s *Service) ListUsers(ctx context.Context, query listQuery) ([]AppUser, in if err := s.fillVIPLevels(ctx, items, userIDs); err != nil { return nil, 0, err } + if err := s.fillUserAdminProfiles(ctx, items, userIDs); err != nil { + return nil, 0, err + } + if err := s.fillLevelAdminProfiles(ctx, items, userIDs); err != nil { + return nil, 0, err + } + if err := s.fillRechargeStats(ctx, items, userIDs); err != nil { + return nil, 0, err + } + if err := s.fillLatestOperators(ctx, items, userIDs); err != nil { + return nil, 0, err + } return items, total, nil } @@ -256,6 +394,19 @@ func (s *Service) listUsersSortedByCoin(ctx context.Context, query listQuery, wh if err := s.fillVIPLevels(ctx, pagedItems, appUserNumericIDs(pagedItems)); err != nil { return nil, err } + pagedUserIDs := appUserNumericIDs(pagedItems) + if err := s.fillUserAdminProfiles(ctx, pagedItems, pagedUserIDs); err != nil { + return nil, err + } + if err := s.fillLevelAdminProfiles(ctx, pagedItems, pagedUserIDs); err != nil { + return nil, err + } + if err := s.fillRechargeStats(ctx, pagedItems, pagedUserIDs); err != nil { + return nil, err + } + if err := s.fillLatestOperators(ctx, pagedItems, pagedUserIDs); err != nil { + return nil, err + } return pagedItems, nil } @@ -343,7 +494,9 @@ func (s *Service) GetUser(ctx context.Context, userID int64) (AppUser, error) { GREATEST( COALESCE((SELECT MAX(s.updated_at_ms) FROM auth_sessions s WHERE s.app_code = u.app_code AND s.user_id = u.user_id), 0), COALESCE((SELECT MAX(l.created_at_ms) FROM login_audit l WHERE l.app_code = u.app_code AND l.user_id = u.user_id), 0) - ) + ), + COALESCE(u.register_device_id, ''), + COALESCE(u.os_version, '') `+appUserPrettySelectColumns()+` FROM users u WHERE u.app_code = ? AND u.user_id = ? @@ -363,6 +516,8 @@ func (s *Service) GetUser(ctx context.Context, userID int64) (AppUser, error) { &item.CreatedAtMs, &item.UpdatedAtMs, &item.LastActiveAtMs, + &item.RegisterDeviceID, + &item.RegisterOsVersion, &item.PrettyDisplayUserID, &item.PrettyID, ) @@ -380,12 +535,307 @@ func (s *Service) GetUser(ctx context.Context, userID int64) (AppUser, error) { if err := s.fillVIPLevels(ctx, items, []int64{userID}); err != nil { return AppUser{}, err } + if err := s.fillUserAdminProfiles(ctx, items, []int64{userID}); err != nil { + return AppUser{}, err + } + if err := s.fillLevelAdminProfiles(ctx, items, []int64{userID}); err != nil { + return AppUser{}, err + } + if err := s.fillRechargeStats(ctx, items, []int64{userID}); err != nil { + return AppUser{}, err + } + if err := s.fillLatestOperators(ctx, items, []int64{userID}); err != nil { + return AppUser{}, err + } if err := s.fillUserResources(ctx, items, userID); err != nil { return AppUser{}, err } + // 状态记录/设备 tab 的补充事实只在详情页读取,列表接口不承担这三组查询。 + if err := s.fillLoginSnapshots(ctx, &items[0], userID); err != nil { + return AppUser{}, err + } + if err := s.fillThirdPartyIdentities(ctx, &items[0], userID); err != nil { + return AppUser{}, err + } + if err := s.fillDevices(ctx, &items[0], userID); err != nil { + return AppUser{}, err + } return items[0], nil } +func (s *Service) fillUserAdminProfiles(ctx context.Context, items []AppUser, userIDs []int64) error { + initializeAdminProjectionDefaults(items) + if s.userAdminClient == nil || len(items) == 0 || len(userIDs) == 0 { + return nil + } + nowMs := nowMillis() + response, err := s.userAdminClient.BatchGetUserAdminProfiles(ctx, &userv1.BatchGetUserAdminProfilesRequest{ + Meta: &userv1.RequestMeta{ + RequestId: fmt.Sprintf("admin-app-user-profile-%d", nowMs), + Caller: "hyapp-admin-server", + SentAtMs: nowMs, + AppCode: appctx.FromContext(ctx), + }, + UserIds: userIDs, + }) + if err != nil { + return err + } + index := appUserIndex(items) + for userID, profile := range response.GetProfiles() { + if profile == nil { + continue + } + i, ok := index[userID] + if !ok { + continue + } + items[i].Birth = strings.TrimSpace(profile.GetBirth()) + items[i].Age = ageAtUTC(items[i].Birth, time.UnixMilli(nowMs).UTC()) + items[i].RegisterDevice = strings.TrimSpace(profile.GetRegisterDevice()) + items[i].LastLoginAtMs = profile.GetLastSuccessLoginAtMs() + items[i].Roles = displayUserRoles(profile.GetRoles()) + if ban := profile.GetBan(); ban != nil { + items[i].Ban = AppUserBan{ + ID: strings.TrimSpace(ban.GetBanId()), + Active: ban.GetActive(), + Permanent: ban.GetPermanent(), + ExpiresAtMs: ban.GetExpiresAtMs(), + Source: strings.TrimSpace(ban.GetSource()), + Reason: strings.TrimSpace(ban.GetReason()), + } + } + if profile.GetLastOperationAtMs() > items[i].LastOperatedAtMs { + items[i].LastOperatedAtMs = profile.GetLastOperationAtMs() + items[i].LastOperator = &AppUserOperator{ + AdminID: strconv.FormatInt(profile.GetLastOperatorUserId(), 10), + Name: displayOperatorType(profile.GetLastOperatorType()), + Action: strings.TrimSpace(profile.GetLastOperationReason()), + } + } + } + return nil +} + +func (s *Service) fillLevelAdminProfiles(ctx context.Context, items []AppUser, userIDs []int64) error { + initializeAdminProjectionDefaults(items) + if s.levelAdminClient == nil || len(items) == 0 || len(userIDs) == 0 { + return nil + } + nowMs := nowMillis() + response, err := s.levelAdminClient.BatchGetUserLevelAdminProfiles(ctx, &activityv1.BatchGetUserLevelAdminProfilesRequest{ + Meta: &activityv1.RequestMeta{ + RequestId: fmt.Sprintf("admin-app-user-level-%d", nowMs), + Caller: "hyapp-admin-server", + SentAtMs: nowMs, + AppCode: appctx.FromContext(ctx), + }, + UserIds: userIDs, + }) + if err != nil { + return err + } + index := appUserIndex(items) + for _, profile := range response.GetProfiles() { + if profile == nil { + continue + } + i, ok := index[profile.GetUserId()] + if !ok { + continue + } + for _, track := range profile.GetTracks() { + if track == nil { + continue + } + level := AppUserLevel{ + Track: strings.ToLower(strings.TrimSpace(track.GetTrack())), + RealLevel: track.GetRealLevel(), + RealTotalValue: track.GetRealTotalValue(), + DisplayLevel: track.GetDisplayLevel(), + DisplayValue: track.GetDisplayValue(), + TemporaryLevelID: strings.TrimSpace(track.GetTemporaryLevelId()), + TemporaryTargetLevel: track.GetTemporaryTargetLevel(), + StartedAtMs: track.GetStartedAtMs(), + ExpiresAtMs: track.GetExpiresAtMs(), + } + switch level.Track { + case "wealth": + items[i].Levels.Wealth = level + case "charm": + items[i].Levels.Charm = level + case "game": + items[i].Levels.Game = level + } + } + } + return nil +} + +func initializeAdminProjectionDefaults(items []AppUser) { + for i := range items { + if len(items[i].Roles) == 0 { + items[i].Roles = []string{"普通用户"} + } + if items[i].Levels.Wealth.Track == "" { + items[i].Levels.Wealth.Track = "wealth" + } + if items[i].Levels.Charm.Track == "" { + items[i].Levels.Charm.Track = "charm" + } + if items[i].Levels.Game.Track == "" { + items[i].Levels.Game.Track = "game" + } + } +} + +func displayUserRoles(roles []string) []string { + displayByRole := map[string]string{ + "host": "主播", + "agency": "公会长", + "bd": "BD", + "bd_leader": "BD Leader", + "coin_seller": "币商", + "manager": "经理", + } + seen := make(map[string]struct{}, len(roles)) + for _, role := range roles { + role = strings.ToLower(strings.TrimSpace(role)) + if _, ok := displayByRole[role]; ok { + seen[role] = struct{}{} + } + } + order := []string{"host", "agency", "bd", "bd_leader", "coin_seller", "manager"} + result := make([]string, 0, len(seen)) + for _, role := range order { + if _, ok := seen[role]; ok { + result = append(result, displayByRole[role]) + } + } + if len(result) == 0 { + return []string{"普通用户"} + } + return result +} + +func displayOperatorType(operatorType string) string { + switch strings.ToLower(strings.TrimSpace(operatorType)) { + case "admin": + return "管理员" + case "manager": + return "经理" + default: + return strings.TrimSpace(operatorType) + } +} + +func ageAtUTC(birth string, now time.Time) *int32 { + birthDate, err := time.Parse("2006-01-02", strings.TrimSpace(birth)) + if err != nil || birthDate.After(now) { + return nil + } + age := now.Year() - birthDate.Year() + if now.Month() < birthDate.Month() || (now.Month() == birthDate.Month() && now.Day() < birthDate.Day()) { + age-- + } + if age < 0 { + return nil + } + value := int32(age) + return &value +} + +func (s *Service) fillRechargeStats(ctx context.Context, items []AppUser, userIDs []int64) error { + if s.walletClient == nil || len(items) == 0 || len(userIDs) == 0 { + return nil + } + response, err := s.walletClient.BatchGetUserRechargeStats(ctx, &walletv1.BatchGetUserRechargeStatsRequest{ + RequestId: fmt.Sprintf("admin-app-user-recharge-%d", nowMillis()), + AppCode: appctx.FromContext(ctx), + UserIds: userIDs, + }) + if err != nil { + return err + } + index := appUserIndex(items) + for _, stat := range response.GetItems() { + if stat == nil { + continue + } + if i, ok := index[stat.GetUserId()]; ok { + items[i].CumulativeRechargeUSDMinor = stat.GetTotalUsdMinorAmount() + } + } + return nil +} + +func (s *Service) fillLatestOperators(ctx context.Context, items []AppUser, userIDs []int64) error { + if s.adminDB == nil || len(items) == 0 || len(userIDs) == 0 { + return nil + } + args := make([]any, 0, len(userIDs)+1) + args = append(args, "app_users") + for _, userID := range userIDs { + args = append(args, strconv.FormatInt(userID, 10)) + } + rows, err := s.adminDB.QueryContext(ctx, fmt.Sprintf(` + SELECT resource_id, user_id, username, action, created_at_ms + FROM admin_operation_logs + WHERE resource = ? AND resource_id IN (%s) AND status = 'success' + ORDER BY resource_id ASC, created_at_ms DESC, id DESC + `, placeholders(len(userIDs))), args...) + if err != nil { + return err + } + defer rows.Close() + + index := appUserIndex(items) + seen := make(map[int64]struct{}, len(userIDs)) + for rows.Next() { + var resourceID string + var adminID uint64 + var name string + var action string + var operatedAtMs int64 + if err := rows.Scan(&resourceID, &adminID, &name, &action, &operatedAtMs); err != nil { + return err + } + userID, err := strconv.ParseInt(strings.TrimSpace(resourceID), 10, 64) + if err != nil { + continue + } + if _, ok := seen[userID]; ok { + continue + } + i, ok := index[userID] + if !ok { + continue + } + seen[userID] = struct{}{} + if operatedAtMs <= items[i].LastOperatedAtMs { + continue + } + items[i].LastOperatedAtMs = operatedAtMs + items[i].LastOperator = &AppUserOperator{ + AdminID: strconv.FormatUint(adminID, 10), + Name: strings.TrimSpace(name), + Action: strings.TrimSpace(action), + } + } + return rows.Err() +} + +func appUserIndex(items []AppUser) map[int64]int { + index := make(map[int64]int, len(items)) + for i := range items { + userID, err := strconv.ParseInt(strings.TrimSpace(items[i].UserID), 10, 64) + if err == nil && userID > 0 { + index[userID] = i + } + } + return index +} + func (s *Service) UpdateUser(ctx context.Context, userID int64, adminUserID int64, requestID string, req updateUserRequest) (AppUser, error) { if s.userDB == nil { return AppUser{}, fmt.Errorf("user mysql is not configured") @@ -527,6 +977,152 @@ func (s *Service) SetUserStatus(ctx context.Context, userID int64, status string return output, nil } +func (s *Service) AdjustTemporaryLevels(ctx context.Context, userID int64, operatorAdminID int64, requestID string, req adjustTemporaryLevelsRequest) (AppUser, error) { + if s.levelAdminClient == nil { + return AppUser{}, fmt.Errorf("activity service level admin client is not configured") + } + if userID <= 0 || operatorAdminID <= 0 || len(req.Adjustments) == 0 || len(req.Adjustments) > 2 { + return AppUser{}, fmt.Errorf("%w: 等级调整参数不正确", ErrInvalidArgument) + } + seen := make(map[string]struct{}, len(req.Adjustments)) + adjustments := make([]*activityv1.TemporaryLevelAdjustment, 0, len(req.Adjustments)) + for _, adjustment := range req.Adjustments { + track := strings.ToLower(strings.TrimSpace(adjustment.Track)) + if track != "wealth" && track != "charm" { + return AppUser{}, fmt.Errorf("%w: 只允许调整富豪或魅力等级", ErrInvalidArgument) + } + if _, duplicated := seen[track]; duplicated { + return AppUser{}, fmt.Errorf("%w: 同一等级轨道不能重复提交", ErrInvalidArgument) + } + if adjustment.Level < 1 || adjustment.Level > 50 || adjustment.DurationDays <= 0 { + return AppUser{}, fmt.Errorf("%w: 等级必须为 1-50 且有效期必须大于 0 天", ErrInvalidArgument) + } + seen[track] = struct{}{} + adjustments = append(adjustments, &activityv1.TemporaryLevelAdjustment{ + Track: track, + Level: adjustment.Level, + DurationDays: adjustment.DurationDays, + }) + } + requestID = strings.TrimSpace(requestID) + if requestID == "" { + return AppUser{}, fmt.Errorf("%w: request_id 不能为空", ErrInvalidArgument) + } + nowMs := nowMillis() + reason := strings.TrimSpace(req.Reason) + if reason == "" { + reason = "admin_temporary_user_level" + } + _, err := s.levelAdminClient.AdjustTemporaryUserLevels(ctx, &activityv1.AdjustTemporaryUserLevelsRequest{ + Meta: &activityv1.RequestMeta{ + RequestId: requestID, + Caller: "hyapp-admin-server", + SentAtMs: nowMs, + AppCode: appctx.FromContext(ctx), + }, + CommandId: fmt.Sprintf("app-user-level:%s:%d", requestID, userID), + UserId: userID, + Adjustments: adjustments, + OperatorAdminId: operatorAdminID, + Reason: reason, + }) + if err != nil { + return AppUser{}, err + } + return s.GetUser(ctx, userID) +} + +func (s *Service) AdminBanUser(ctx context.Context, userID int64, operatorAdminID int64, requestID string, req banUserRequest) (SetUserStatusResult, error) { + if s.userAdminClient == nil { + return SetUserStatusResult{}, fmt.Errorf("user service admin ban client is not configured") + } + nowMs := nowMillis() + if userID <= 0 || operatorAdminID <= 0 || req.ExpiresAtMs < 0 || (req.ExpiresAtMs > 0 && req.ExpiresAtMs <= nowMs) { + return SetUserStatusResult{}, fmt.Errorf("%w: 封禁截止时间必须为 0 或晚于当前 UTC 时间", ErrInvalidArgument) + } + requestID = strings.TrimSpace(requestID) + if requestID == "" { + return SetUserStatusResult{}, fmt.Errorf("%w: request_id 不能为空", ErrInvalidArgument) + } + reason := strings.TrimSpace(req.Reason) + if reason == "" { + reason = "admin_app_user_ban" + } + response, err := s.userAdminClient.AdminBanUser(ctx, &userv1.AdminBanUserRequest{ + Meta: &userv1.RequestMeta{ + RequestId: requestID, + Caller: "hyapp-admin-server", + SentAtMs: nowMs, + AppCode: appctx.FromContext(ctx), + }, + CommandId: fmt.Sprintf("app-user-ban:%s:%d", requestID, userID), + TargetUserId: userID, + ExpiresAtMs: req.ExpiresAtMs, + OperatorAdminId: operatorAdminID, + Reason: reason, + }) + if err != nil { + return SetUserStatusResult{}, err + } + return s.userStatusResultFromAdminResponse(ctx, userID, response.GetStatus()) +} + +func (s *Service) AdminUnbanUser(ctx context.Context, userID int64, operatorAdminID int64, requestID string, req unbanUserRequest) (SetUserStatusResult, error) { + if s.userAdminClient == nil { + return SetUserStatusResult{}, fmt.Errorf("user service admin ban client is not configured") + } + if userID <= 0 || operatorAdminID <= 0 { + return SetUserStatusResult{}, fmt.Errorf("%w: 解封参数不正确", ErrInvalidArgument) + } + requestID = strings.TrimSpace(requestID) + if requestID == "" { + return SetUserStatusResult{}, fmt.Errorf("%w: request_id 不能为空", ErrInvalidArgument) + } + reason := strings.TrimSpace(req.Reason) + if reason == "" { + reason = "admin_app_user_unban" + } + nowMs := nowMillis() + response, err := s.userAdminClient.AdminUnbanUser(ctx, &userv1.AdminUnbanUserRequest{ + Meta: &userv1.RequestMeta{ + RequestId: requestID, + Caller: "hyapp-admin-server", + SentAtMs: nowMs, + AppCode: appctx.FromContext(ctx), + }, + // user-service 以 target_user_id 为边界原子释放全部 admin/manager 事实;不再由聚合摘要猜测具体来源。 + TargetUserId: userID, + OperatorAdminId: operatorAdminID, + Reason: reason, + }) + if err != nil { + return SetUserStatusResult{}, err + } + return s.userStatusResultFromAdminResponse(ctx, userID, response.GetStatus()) +} + +func (s *Service) userStatusResultFromAdminResponse(ctx context.Context, userID int64, status *userv1.SetUserStatusResponse) (SetUserStatusResult, error) { + user, err := s.GetUser(ctx, userID) + if err != nil { + return SetUserStatusResult{}, err + } + result := SetUserStatusResult{User: user} + if status == nil { + return result, nil + } + result.RevokedSessionCount = status.GetRevokedSessionCount() + result.AccessTokenRevoked = status.GetAccessTokenRevoked() + result.AccessTokenError = status.GetAccessTokenRevokeError() + result.IMKicked = status.GetImKicked() + result.IMKickError = status.GetImKickError() + result.RoomEvicted = status.GetRoomEvicted() + result.RoomID = status.GetRoomId() + result.RTCKicked = status.GetRtcKicked() + result.RTCKickError = status.GetRtcKickError() + result.RoomEvictError = status.GetRoomEvictError() + return result, nil +} + func (s *Service) SetPassword(ctx context.Context, userID int64, password string) error { if s.userDB == nil { return fmt.Errorf("user mysql is not configured") @@ -784,6 +1380,9 @@ func normalizeListQuery(query listQuery) listQuery { if query.EndMs < 0 { query.EndMs = 0 } + if query.SnapshotAtMs < 0 { + query.SnapshotAtMs = 0 + } query.Status = strings.ToLower(strings.TrimSpace(query.Status)) query.SortBy = normalizeAppUserSortBy(query.SortBy) query.SortDirection = normalizeSortDirection(query.SortDirection) @@ -833,6 +1432,16 @@ func appUserListWhereSQLAt(appCode string, query listQuery, nowMs int64) (string whereSQL += " AND u.created_at_ms < ?" args = append(args, query.EndMs) } + if query.SnapshotAtMs > 0 { + // 导出只读取任务创建时已经存在的用户,避免长任务执行期间的新注册插入到头部并改变分页集合。 + whereSQL += " AND u.created_at_ms <= ?" + args = append(args, query.SnapshotAtMs) + } + if query.ExportCursorCreatedAtMs > 0 && query.ExportCursorUserID > 0 { + // 导出固定 created_at DESC/user_id DESC,组合游标处理同毫秒注册且不依赖 OFFSET。 + whereSQL += " AND (u.created_at_ms < ? OR (u.created_at_ms = ? AND u.user_id < ?))" + args = append(args, query.ExportCursorCreatedAtMs, query.ExportCursorCreatedAtMs, query.ExportCursorUserID) + } return whereSQL, args } diff --git a/server/admin/internal/modules/appuser/service_test.go b/server/admin/internal/modules/appuser/service_test.go index d7ec538f..6882f7f7 100644 --- a/server/admin/internal/modules/appuser/service_test.go +++ b/server/admin/internal/modules/appuser/service_test.go @@ -3,6 +3,7 @@ package appuser import ( "strings" "testing" + "time" "hyapp-admin-server/internal/modules/shared" ) @@ -24,6 +25,68 @@ func TestNormalizeListQuerySort(t *testing.T) { } } +func TestAgeAtUTCBirthdayBoundary(t *testing.T) { + beforeBirthday := ageAtUTC("2000-07-11", time.Date(2026, time.July, 10, 23, 59, 59, 0, time.UTC)) + if beforeBirthday == nil || *beforeBirthday != 25 { + t.Fatalf("age before UTC birthday = %v, want 25", beforeBirthday) + } + onBirthday := ageAtUTC("2000-07-11", time.Date(2026, time.July, 11, 0, 0, 0, 0, time.UTC)) + if onBirthday == nil || *onBirthday != 26 { + t.Fatalf("age on UTC birthday = %v, want 26", onBirthday) + } + if got := ageAtUTC("", time.Now().UTC()); got != nil { + t.Fatalf("empty birth age = %v, want nil", got) + } +} + +func TestDisplayUserRolesSupportsMultipleIdentities(t *testing.T) { + roles := displayUserRoles([]string{"manager", "host", "bd_leader", "host", "unknown"}) + want := []string{"主播", "BD Leader", "经理"} + if strings.Join(roles, ",") != strings.Join(want, ",") { + t.Fatalf("roles = %#v, want %#v", roles, want) + } + ordinary := displayUserRoles(nil) + if len(ordinary) != 1 || ordinary[0] != "普通用户" { + t.Fatalf("ordinary roles = %#v", ordinary) + } +} + +func TestAppUserExportRowKeepsCompositeIdentityFieldsAndAddsColumns(t *testing.T) { + age := int32(26) + item := AppUser{ + UserID: "10001", + DefaultDisplayUserID: "1299324", + PrettyDisplayUserID: "11222", + Username: "sumon Ahammad", + Avatar: "https://example.test/avatar.png", + Age: &age, + Roles: []string{"主播", "经理"}, + Levels: AppUserLevels{ + Wealth: AppUserLevel{RealLevel: 8, DisplayLevel: 14, TemporaryTargetLevel: 14, ExpiresAtMs: 1234}, + Charm: AppUserLevel{RealLevel: 9, DisplayLevel: 15, TemporaryTargetLevel: 15, ExpiresAtMs: 5678}, + Game: AppUserLevel{DisplayLevel: 6}, + }, + } + row := appUserExportRow(item) + if len(row) != len(appUserExportHeaders()) { + t.Fatalf("export row columns = %d, headers = %d", len(row), len(appUserExportHeaders())) + } + if row[0] != "10001" || row[1] != "1299324" || row[2] != "11222" || row[3] != "sumon Ahammad" || row[4] != "https://example.test/avatar.png" { + t.Fatalf("identity columns changed: %#v", row[:5]) + } +} + +func TestSpreadsheetSafeCSVCellEscapesFormulaPrefixes(t *testing.T) { + for _, value := range []string{"=HYPERLINK(\"https://example.test\")", "+cmd", " -1+1", "@SUM(A1:A2)"} { + if got := spreadsheetSafeCSVCell(value); !strings.HasPrefix(got, "'") { + t.Fatalf("dangerous CSV cell %q was not escaped: %q", value, got) + } + } + if got := spreadsheetSafeCSVCell("normal user"); got != "normal user" { + t.Fatalf("normal CSV cell changed: %q", got) + } +} + func TestAppUserListWhereSQLFilters(t *testing.T) { whereSQL, args := appUserListWhereSQL("lalu", normalizeListQuery(listQuery{ Country: "ph", diff --git a/server/admin/internal/modules/dashboard/handler.go b/server/admin/internal/modules/dashboard/handler.go index 0147aebf..5264189f 100644 --- a/server/admin/internal/modules/dashboard/handler.go +++ b/server/admin/internal/modules/dashboard/handler.go @@ -4,6 +4,7 @@ import ( "strconv" "github.com/gin-gonic/gin" + "hyapp-admin-server/internal/appctx" "hyapp-admin-server/internal/config" "hyapp-admin-server/internal/integration/userclient" "hyapp-admin-server/internal/middleware" @@ -33,6 +34,25 @@ func (h *Handler) DashboardOverview(c *gin.Context) { response.OK(c, overview) } +func (h *Handler) UserProfileOverview(c *gin.Context) { + overview, err := h.service.UserProfileOverview(c.Request.Context(), UserProfileOverviewQuery{ + // app_code 显式 query 用于后台页面切换 App;未传时回退鉴权中间件写入的 X-App-Code, + // 避免页面只切 Header App 后仍静默读取 lalu。 + AppCode: c.DefaultQuery("app_code", appctx.FromContext(c.Request.Context())), + StatTZ: c.Query("stat_tz"), + StartMS: parseInt64(c.Query("start_ms")), + EndMS: parseInt64(c.Query("end_ms")), + }) + if err != nil { + // 对外继续返回稳定的通用文案,真实依赖/SQL 错误挂到 Gin request errors;access log 才能用 + // request_id 还原失败节点,同时避免把数据库结构或内部地址泄露给后台浏览器。 + _ = c.Error(err) + response.ServerError(c, "获取用户画像总览失败") + return + } + response.OK(c, overview) +} + func (h *Handler) StatisticsOverview(c *gin.Context) { overview, err := h.service.StatisticsOverview(c.Request.Context(), StatisticsQuery{ AppCode: c.DefaultQuery("app_code", "lalu"), diff --git a/server/admin/internal/modules/dashboard/routes.go b/server/admin/internal/modules/dashboard/routes.go index 1e5aa78c..8fa7b583 100644 --- a/server/admin/internal/modules/dashboard/routes.go +++ b/server/admin/internal/modules/dashboard/routes.go @@ -8,6 +8,7 @@ import ( func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { protected.GET("/dashboard/overview", middleware.RequirePermission("overview:view"), h.DashboardOverview) + protected.GET("/dashboard/user-profile-overview", middleware.RequirePermission("overview:view"), h.UserProfileOverview) protected.GET("/statistics/overview", middleware.RequirePermission("overview:view"), h.StatisticsOverview) protected.GET("/statistics/platform-grants/users", middleware.RequirePermission("overview:view"), h.PlatformGrantUsers) protected.GET("/statistics/platform-grants/records", middleware.RequirePermission("overview:view"), h.PlatformGrantRecords) diff --git a/server/admin/internal/modules/dashboard/service.go b/server/admin/internal/modules/dashboard/service.go index 1274a45a..2f9d8bd6 100644 --- a/server/admin/internal/modules/dashboard/service.go +++ b/server/admin/internal/modules/dashboard/service.go @@ -3,6 +3,7 @@ package dashboard import ( "bytes" "context" + "database/sql" "encoding/json" "fmt" "net/http" @@ -18,6 +19,7 @@ import ( type DashboardService struct { store *repository.Store + userDB *sql.DB cfg config.Config httpClient *http.Client user userclient.Client @@ -57,16 +59,17 @@ type AppTrackingFunnelQuery struct { } type SocialRequirementsQuery struct { - AppCode string - StatTZ string - StartMS int64 - EndMS int64 - RegionID int64 - RegionIDs []int64 - CountryID int64 - Section string - UserRole string - PayerType string + AppCode string + StatTZ string + StartMS int64 + EndMS int64 + RegionID int64 + RegionIDs []int64 + CountryID int64 + Section string + UserRole string + PayerType string + NewUserType string } type PlatformGrantStatisticsQuery struct { @@ -95,6 +98,14 @@ type ExternalDashboardRegionResolver interface { type Option func(*DashboardService) +// WithUserDB 注入用户主数据只读连接;Dashboard 用它计算已完成真实用户的人群快照, +// 并从 login_audit 读取最近一次成功登录版本。当天新增与活跃仍沿用 statistics-service 的事实投影。 +func WithUserDB(userDB *sql.DB) Option { + return func(s *DashboardService) { + s.userDB = userDB + } +} + func WithExternalDashboardSources(sources ...ExternalDashboardSource) Option { return func(s *DashboardService) { for _, source := range sources { @@ -260,6 +271,9 @@ func (s *DashboardService) SocialRequirements(ctx context.Context, query SocialR if payerType := strings.TrimSpace(query.PayerType); payerType != "" { values.Set("payer_type", payerType) } + if newUserType := strings.TrimSpace(query.NewUserType); newUserType != "" { + values.Set("new_user_type", newUserType) + } // Social BI 数据需求只读 statistics-service 聚合投影;admin 不直接拼 owner service 明细或跨库查询。 return s.statisticsGET(ctx, "/internal/v1/statistics/social/requirements", values) } @@ -279,7 +293,8 @@ func (s *DashboardService) RecordFinanceCoinSellerRechargeOrder(ctx context.Cont "target_region_id": order.TargetRegionID, "usd_minor": order.USDMinorAmount, "coin_amount": order.CoinAmount, - "created_at_ms": order.CreatedAtMS, + // 统计切日必须使用运营选择的订单业务时间;历史数据通过模型方法回退到录入时间。 + "created_at_ms": order.BusinessTimeMS(), } return s.statisticsPOST(ctx, "/internal/v1/statistics/social/coin-seller-recharge-orders", payload) } diff --git a/server/admin/internal/modules/dashboard/user_profile.go b/server/admin/internal/modules/dashboard/user_profile.go new file mode 100644 index 00000000..bbfcd0d8 --- /dev/null +++ b/server/admin/internal/modules/dashboard/user_profile.go @@ -0,0 +1,215 @@ +package dashboard + +import ( + "context" + "fmt" + "strings" + "time" + + "hyapp-admin-server/internal/appctx" +) + +type UserProfileOverviewQuery struct { + AppCode string + StatTZ string + StartMS int64 + EndMS int64 +} + +type UserProfileDistributionItem struct { + Key string `json:"key"` + Label string `json:"label"` + Count int64 `json:"count"` +} + +type UserProfileOverview struct { + AppCode string `json:"appCode"` + TotalUsers int64 `json:"totalUsers"` + NewUsers int64 `json:"newUsers"` + ActiveUsers int64 `json:"activeUsers"` + GenderDistribution []UserProfileDistributionItem `json:"genderDistribution"` + AppVersionDistribution []UserProfileDistributionItem `json:"appVersionDistribution"` + UpdatedAtMS int64 `json:"updatedAtMs"` +} + +func (s *DashboardService) UserProfileOverview(ctx context.Context, query UserProfileOverviewQuery) (UserProfileOverview, error) { + if s == nil || s.userDB == nil { + return UserProfileOverview{}, fmt.Errorf("user mysql is not configured") + } + query.AppCode = appctx.Normalize(query.AppCode) + totalUsers, genderDistribution, err := s.queryNaturalUserGenderDistribution(ctx, query.AppCode) + if err != nil { + return UserProfileOverview{}, fmt.Errorf("query dashboard user profile: %w", err) + } + + // 新增和 DAU 必须沿用数据大屏的事件聚合口径;直接按 users.created_at_ms 或登录表重算会与 + // StatisticsOverview 的 UserRegistered/活跃事实去重规则产生双重口径。 + statisticsOverview, err := s.StatisticsOverview(ctx, StatisticsQuery{ + AppCode: query.AppCode, + StatTZ: query.StatTZ, + StartMS: query.StartMS, + EndMS: query.EndMS, + }) + if err != nil { + return UserProfileOverview{}, fmt.Errorf("query dashboard daily statistics: %w", err) + } + newUsers := anyInt64(statisticsOverview["new_users"]) + activeUsers := anyInt64(statisticsOverview["active_users"]) + + versionDistribution, err := s.queryNaturalUserAppVersionDistribution(ctx, query.AppCode) + if err != nil { + return UserProfileOverview{}, fmt.Errorf("query dashboard app version distribution: %w", err) + } + + return UserProfileOverview{ + AppCode: query.AppCode, + TotalUsers: totalUsers, + NewUsers: newUsers, + ActiveUsers: activeUsers, + GenderDistribution: genderDistribution, + AppVersionDistribution: versionDistribution, + UpdatedAtMS: time.Now().UTC().UnixMilli(), + }, nil +} + +func (s *DashboardService) queryNaturalUserGenderDistribution(ctx context.Context, appCode string) (int64, []UserProfileDistributionItem, error) { + rows, err := s.userDB.QueryContext(ctx, ` + SELECT CASE + WHEN TRIM(COALESCE(gender, '')) = '' THEN 'unknown' + ELSE LOWER(TRIM(gender)) + END AS gender_key, + COUNT(*) AS user_count + FROM users + WHERE app_code = ? + AND profile_completed = 1 + AND LOWER(TRIM(COALESCE(source, ''))) NOT IN ('game_robot', 'quick_account') + GROUP BY gender_key + ORDER BY user_count DESC, gender_key ASC`, appCode) + if err != nil { + return 0, nil, err + } + defer rows.Close() + + // 总人数直接求和同一条性别分组结果,让 totalUsers 与饼图永远共享 profile_completed/source 条件; + // 分开执行 COUNT 和 GROUP BY 容易在以后新增“真实用户”排除条件时只改到其中一处。 + items := make([]UserProfileDistributionItem, 0) + var total int64 + for rows.Next() { + var key string + var count int64 + if err := rows.Scan(&key, &count); err != nil { + return 0, nil, err + } + key = normalizeDistributionKey(key) + items = append(items, UserProfileDistributionItem{Key: key, Label: genderDistributionLabel(key), Count: count}) + total += count + } + if err := rows.Err(); err != nil { + return 0, nil, err + } + return total, items, nil +} + +func (s *DashboardService) queryNaturalUserAppVersionDistribution(ctx context.Context, appCode string) ([]UserProfileDistributionItem, error) { + // login_audit 的复合索引把 login_type 放在 created_at_ms 之前;若把三种登录类型写成 IN, + // MySQL 必须为每个用户扫描并排序全部候选,线上百万级审计表会超时。这里按类型拆成三个等值索引 + // 范围,每个范围只取一条,再对最多三条候选按 (created_at_ms, id) 选最新记录,既保持原口径, + // 也避免窗口函数物化整张审计表。natural 是 MySQL 保留字,用户别名必须使用 natural_user。 + rows, err := s.userDB.QueryContext(ctx, ` + SELECT CASE + WHEN latest.app_version IS NULL OR TRIM(latest.app_version) = '' THEN 'unknown' + ELSE TRIM(latest.app_version) + END AS version_key, + COUNT(*) AS user_count + FROM users AS natural_user + LEFT JOIN LATERAL ( + SELECT candidate.app_version + FROM ( + (SELECT audit.app_version, audit.created_at_ms, audit.id + FROM login_audit AS audit + WHERE audit.app_code = natural_user.app_code + AND audit.user_id = natural_user.user_id + AND audit.result = 'success' + AND audit.blocked = 0 + AND audit.login_type = 'password' + ORDER BY audit.created_at_ms DESC, audit.id DESC + LIMIT 1) + UNION ALL + (SELECT audit.app_version, audit.created_at_ms, audit.id + FROM login_audit AS audit + WHERE audit.app_code = natural_user.app_code + AND audit.user_id = natural_user.user_id + AND audit.result = 'success' + AND audit.blocked = 0 + AND audit.login_type = 'third_party' + ORDER BY audit.created_at_ms DESC, audit.id DESC + LIMIT 1) + UNION ALL + (SELECT audit.app_version, audit.created_at_ms, audit.id + FROM login_audit AS audit + WHERE audit.app_code = natural_user.app_code + AND audit.user_id = natural_user.user_id + AND audit.result = 'success' + AND audit.blocked = 0 + AND audit.login_type = 'refresh' + ORDER BY audit.created_at_ms DESC, audit.id DESC + LIMIT 1) + ) AS candidate + ORDER BY candidate.created_at_ms DESC, candidate.id DESC + LIMIT 1 + ) AS latest ON TRUE + WHERE natural_user.app_code = ? + AND natural_user.profile_completed = 1 + AND LOWER(TRIM(COALESCE(natural_user.source, ''))) NOT IN ('game_robot', 'quick_account') + GROUP BY version_key + ORDER BY user_count DESC, version_key ASC`, appCode) + if err != nil { + return nil, err + } + defer rows.Close() + + items := make([]UserProfileDistributionItem, 0) + for rows.Next() { + var key string + var count int64 + if err := rows.Scan(&key, &count); err != nil { + return nil, err + } + key = normalizeDistributionKey(key) + items = append(items, UserProfileDistributionItem{Key: key, Label: appVersionDistributionLabel(key), Count: count}) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +func normalizeDistributionKey(value string) string { + value = strings.TrimSpace(value) + if value == "" || strings.EqualFold(value, "unknown") || value == "" { + return "unknown" + } + return value +} + +func genderDistributionLabel(key string) string { + switch strings.ToLower(key) { + case "male": + return "男" + case "female": + return "女" + case "non_binary", "nonbinary": + return "非二元" + case "unknown": + return "未知" + default: + return key + } +} + +func appVersionDistributionLabel(key string) string { + if key == "unknown" { + return "未知版本" + } + return key +} diff --git a/server/admin/internal/modules/dashboard/user_profile_mysql_test.go b/server/admin/internal/modules/dashboard/user_profile_mysql_test.go new file mode 100644 index 00000000..39766dab --- /dev/null +++ b/server/admin/internal/modules/dashboard/user_profile_mysql_test.go @@ -0,0 +1,126 @@ +package dashboard + +import ( + "context" + "database/sql" + "fmt" + "os" + "strings" + "testing" + "time" + + mysqlDriver "github.com/go-sql-driver/mysql" +) + +func TestUserProfileVersionDistributionRealMySQL(t *testing.T) { + baseDSN := strings.TrimSpace(os.Getenv("DASHBOARD_USER_PROFILE_MYSQL_TEST_DSN")) + if baseDSN == "" { + t.Skip("set DASHBOARD_USER_PROFILE_MYSQL_TEST_DSN to run the real MySQL user-profile query") + } + + // sqlmock only matches text and previously let a reserved-word alias reach production. This focused + // integration test asks a real MySQL parser/optimizer to execute the LATERAL query and its tie-break rule. + db := newUserProfileMySQLTestDB(t, baseDSN) + execUserProfileSQL(t, db, + `CREATE TABLE users ( + app_code VARCHAR(32) NOT NULL, + user_id BIGINT NOT NULL, + gender VARCHAR(32) NULL, + profile_completed TINYINT(1) NOT NULL DEFAULT 0, + source VARCHAR(64) NULL, + PRIMARY KEY (app_code, user_id) + ) ENGINE=InnoDB`, + `CREATE TABLE login_audit ( + app_code VARCHAR(32) NOT NULL, + id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT NULL, + login_type VARCHAR(32) NOT NULL, + app_version VARCHAR(64) NOT NULL DEFAULT '', + result VARCHAR(32) NOT NULL, + blocked TINYINT(1) NOT NULL DEFAULT 0, + created_at_ms BIGINT NOT NULL, + KEY idx_login_audit_latest_success (app_code, user_id, result, blocked, login_type, created_at_ms, id) + ) ENGINE=InnoDB`, + `INSERT INTO users (app_code, user_id, gender, profile_completed, source) VALUES + ('lalu', 1, 'male', 1, 'third_party'), + ('lalu', 2, 'female', 1, 'password'), + ('lalu', 3, 'male', 1, 'quick_account'), + ('lalu', 4, 'female', 1, 'third_party'), + ('lalu', 5, 'male', 1, 'password'), + ('lalu', 6, 'male', 1, 'game_robot'), + ('lalu', 7, 'female', 0, 'third_party'), + ('lalu', 9, 'female', 1, 'third_party'), + ('huwaa', 1, 'female', 1, 'third_party')`, + `INSERT INTO login_audit (app_code, user_id, login_type, app_version, result, blocked, created_at_ms) VALUES + ('lalu', 1, 'password', '1.0.0', 'success', 0, 100), + ('lalu', 1, 'third_party', '2.0.0', 'success', 0, 200), + ('lalu', 1, 'refresh', '3.0.0', 'success', 0, 200), + ('lalu', 3, 'refresh', 'excluded', 'success', 0, 500), + ('lalu', 4, 'password', '', 'success', 0, 100), + ('lalu', 4, 'third_party', 'blocked', 'success', 1, 300), + ('lalu', 4, 'refresh', 'failed', 'failed', 0, 400), + ('lalu', 4, 'logout', 'unsupported', 'success', 0, 500), + ('lalu', 5, 'password', 'time-wins', 'success', 0, 500), + ('lalu', 5, 'password', 'larger-id-loses', 'success', 0, 100), + ('lalu', 6, 'password', 'excluded', 'success', 0, 500), + ('lalu', 7, 'password', 'excluded', 'success', 0, 500), + ('lalu', 9, 'password', ' ', 'success', 0, 500), + ('huwaa', 1, 'password', 'other-app', 'success', 0, 500)`, + ) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + items, err := (&DashboardService{userDB: db}).queryNaturalUserAppVersionDistribution(ctx, "lalu") + if err != nil { + t.Fatalf("query real MySQL version distribution: %v", err) + } + if len(items) != 3 || + items[0] != (UserProfileDistributionItem{Key: "unknown", Label: "未知版本", Count: 3}) || + items[1] != (UserProfileDistributionItem{Key: "3.0.0", Label: "3.0.0", Count: 1}) || + items[2] != (UserProfileDistributionItem{Key: "time-wins", Label: "time-wins", Count: 1}) { + t.Fatalf("version distribution mismatch: %+v", items) + } +} + +func newUserProfileMySQLTestDB(t *testing.T, baseDSN string) *sql.DB { + t.Helper() + baseConfig, err := mysqlDriver.ParseDSN(baseDSN) + if err != nil { + t.Fatalf("parse MySQL test DSN: %v", err) + } + databaseName := fmt.Sprintf("hy_admin_user_profile_%d", time.Now().UnixNano()) + adminConfig := baseConfig.Clone() + adminConfig.DBName = "" + adminDB, err := sql.Open("mysql", adminConfig.FormatDSN()) + if err != nil { + t.Fatalf("open MySQL admin connection: %v", err) + } + if _, err := adminDB.Exec("CREATE DATABASE `" + databaseName + "` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); err != nil { + _ = adminDB.Close() + t.Fatalf("create MySQL test database: %v", err) + } + + testConfig := baseConfig.Clone() + testConfig.DBName = databaseName + db, err := sql.Open("mysql", testConfig.FormatDSN()) + if err != nil { + _, _ = adminDB.Exec("DROP DATABASE IF EXISTS `" + databaseName + "`") + _ = adminDB.Close() + t.Fatalf("open MySQL test database: %v", err) + } + t.Cleanup(func() { + _ = db.Close() + _, _ = adminDB.Exec("DROP DATABASE IF EXISTS `" + databaseName + "`") + _ = adminDB.Close() + }) + return db +} + +func execUserProfileSQL(t *testing.T, db *sql.DB, statements ...string) { + t.Helper() + for _, statement := range statements { + if _, err := db.Exec(statement); err != nil { + t.Fatalf("execute user-profile fixture SQL: %v", err) + } + } +} diff --git a/server/admin/internal/modules/dashboard/user_profile_test.go b/server/admin/internal/modules/dashboard/user_profile_test.go new file mode 100644 index 00000000..3393d4fb --- /dev/null +++ b/server/admin/internal/modules/dashboard/user_profile_test.go @@ -0,0 +1,178 @@ +package dashboard + +import ( + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/gin-gonic/gin" + "hyapp-admin-server/internal/appctx" + "hyapp-admin-server/internal/config" + "hyapp-admin-server/internal/middleware" +) + +func TestUserProfileOverviewAggregatesNaturalUsersAndDailyStatistics(t *testing.T) { + userDB, sqlMock, err := sqlmock.New() + if err != nil { + t.Fatalf("create user sqlmock: %v", err) + } + defer userDB.Close() + + sqlMock.ExpectQuery(`(?s)SELECT CASE.*COALESCE\(gender, ''\).*FROM users.*WHERE app_code = \?.*profile_completed = 1.*game_robot.*quick_account.*GROUP BY gender_key.*ORDER BY user_count DESC`). + WithArgs("huwaa"). + WillReturnRows(sqlmock.NewRows([]string{"gender_key", "user_count"}). + AddRow("male", int64(4)). + AddRow("female", int64(2)). + AddRow("unknown", int64(1))) + sqlMock.ExpectQuery(`(?s)FROM users AS natural_user.*LEFT JOIN LATERAL.*login_type = 'password'.*ORDER BY audit.created_at_ms DESC, audit.id DESC.*UNION ALL.*login_type = 'third_party'.*UNION ALL.*login_type = 'refresh'.*ORDER BY candidate.created_at_ms DESC, candidate.id DESC.*WHERE natural_user.app_code = \?.*profile_completed = 1.*game_robot.*quick_account.*GROUP BY version_key`). + WithArgs("huwaa"). + WillReturnRows(sqlmock.NewRows([]string{"version_key", "user_count"}). + AddRow("2.4.0", int64(4)). + AddRow("unknown", int64(3))) + + requested := map[string]bool{} + statisticsServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requested[r.URL.Path] = true + query := r.URL.Query() + if query.Get("app_code") != "huwaa" || query.Get("stat_tz") != "Asia/Shanghai" || + query.Get("start_ms") != "1783699200000" || query.Get("end_ms") != "1783785600000" { + http.Error(w, "unexpected statistics query: "+r.URL.RawQuery, http.StatusBadRequest) + return + } + w.Header().Set("Content-Type", "application/json") + switch r.URL.Path { + case "/internal/v1/statistics/overview": + _ = json.NewEncoder(w).Encode(map[string]any{"active_users": 5, "new_users": 2}) + default: + http.NotFound(w, r) + } + })) + defer statisticsServer.Close() + + service := NewService(nil, config.Config{StatisticsService: config.StatisticsServiceConfig{ + BaseURL: statisticsServer.URL, + RequestTimeout: time.Second, + }}, nil, WithUserDB(userDB)) + router := newDashboardUserProfileTestRouter(NewWithService(service), []string{"overview:view"}) + request := httptest.NewRequest(http.MethodGet, + "/api/v1/dashboard/user-profile-overview?app_code=Huwaa&stat_tz=Asia%2FShanghai&start_ms=1783699200000&end_ms=1783785600000", nil) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: got=%d body=%s", recorder.Code, recorder.Body.String()) + } + var envelope struct { + Code int `json:"code"` + Data UserProfileOverview `json:"data"` + } + if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil { + t.Fatalf("decode response: %v", err) + } + if envelope.Code != 0 || envelope.Data.AppCode != "huwaa" || envelope.Data.TotalUsers != 7 || + envelope.Data.NewUsers != 2 || envelope.Data.ActiveUsers != 5 || envelope.Data.UpdatedAtMS <= 0 { + t.Fatalf("overview mismatch: %+v", envelope.Data) + } + if len(envelope.Data.GenderDistribution) != 3 || + envelope.Data.GenderDistribution[0] != (UserProfileDistributionItem{Key: "male", Label: "男", Count: 4}) || + envelope.Data.GenderDistribution[2] != (UserProfileDistributionItem{Key: "unknown", Label: "未知", Count: 1}) { + t.Fatalf("gender distribution mismatch: %+v", envelope.Data.GenderDistribution) + } + if len(envelope.Data.AppVersionDistribution) != 2 || + envelope.Data.AppVersionDistribution[0] != (UserProfileDistributionItem{Key: "2.4.0", Label: "2.4.0", Count: 4}) || + envelope.Data.AppVersionDistribution[1] != (UserProfileDistributionItem{Key: "unknown", Label: "未知版本", Count: 3}) { + t.Fatalf("version distribution mismatch: %+v", envelope.Data.AppVersionDistribution) + } + // 版本分布以全部自然用户为分母并直接查询 login_audit;statistics-service 只保留新增/DAU 口径。 + if len(requested) != 1 || !requested["/internal/v1/statistics/overview"] { + t.Fatalf("statistics calls mismatch: %+v", requested) + } + if err := sqlMock.ExpectationsWereMet(); err != nil { + t.Fatalf("sql expectations mismatch: %v", err) + } +} + +func TestGenderDistributionLabelMapsFlutterValues(t *testing.T) { + tests := map[string]string{ + "male": "男", + "female": "女", + "non_binary": "非二元", + "unknown": "未知", + } + for input, expected := range tests { + if actual := genderDistributionLabel(input); actual != expected { + t.Fatalf("gender label mismatch: input=%q got=%q want=%q", input, actual, expected) + } + } +} + +func TestUserProfileOverviewRouteRequiresOverviewPermission(t *testing.T) { + router := newDashboardUserProfileTestRouter(NewWithService(&DashboardService{}), nil) + request := httptest.NewRequest(http.MethodGet, "/api/v1/dashboard/user-profile-overview", nil) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusForbidden { + t.Fatalf("status mismatch: got=%d body=%s", recorder.Code, recorder.Body.String()) + } +} + +func TestUserProfileOverviewRecordsInternalErrorAgainstRequestID(t *testing.T) { + userDB, sqlMock, err := sqlmock.New() + if err != nil { + t.Fatalf("create user sqlmock: %v", err) + } + defer userDB.Close() + + sqlMock.ExpectQuery(`(?s)SELECT CASE.*FROM users.*profile_completed = 1`). + WithArgs("lalu"). + WillReturnError(errors.New("synthetic mysql failure")) + + service := NewService(nil, config.Config{}, nil, WithUserDB(userDB)) + gin.SetMode(gin.TestMode) + router := gin.New() + var recordedErrors string + router.Use(func(c *gin.Context) { + c.Request = c.Request.WithContext(appctx.WithContext(c.Request.Context(), "lalu")) + c.Set(middleware.ContextPermissions, []string{"overview:view"}) + c.Next() + recordedErrors = c.Errors.String() + }) + protected := router.Group("/api/v1") + RegisterRoutes(protected, NewWithService(service)) + request := httptest.NewRequest(http.MethodGet, "/api/v1/dashboard/user-profile-overview", nil) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusInternalServerError { + t.Fatalf("status mismatch: got=%d body=%s", recorder.Code, recorder.Body.String()) + } + if !strings.Contains(recordedErrors, "query dashboard user profile: synthetic mysql failure") { + t.Fatalf("request error was not recorded: %q", recordedErrors) + } + if err := sqlMock.ExpectationsWereMet(); err != nil { + t.Fatalf("sql expectations mismatch: %v", err) + } +} + +func newDashboardUserProfileTestRouter(handler *Handler, permissions []string) *gin.Engine { + gin.SetMode(gin.TestMode) + router := gin.New() + router.Use(func(c *gin.Context) { + // 复现 AuthRequired/AppCode 已完成的上下文写入,让路由测试同时覆盖 overview:view 中间件和 query 优先级。 + c.Request = c.Request.WithContext(appctx.WithContext(c.Request.Context(), "lalu")) + c.Set(middleware.ContextPermissions, permissions) + c.Next() + }) + protected := router.Group("/api/v1") + RegisterRoutes(protected, handler) + return router +} diff --git a/server/admin/internal/modules/databi/finance_recharge.go b/server/admin/internal/modules/databi/finance_recharge.go new file mode 100644 index 00000000..7eecb90d --- /dev/null +++ b/server/admin/internal/modules/databi/finance_recharge.go @@ -0,0 +1,556 @@ +package databi + +import ( + "context" + "sort" + "strconv" + "strings" + + "hyapp-admin-server/internal/modules/payment" +) + +const financeRechargeTZOffsetMinutes int32 = 480 + +type financeRechargeValues struct { + GoogleUSDMinor int64 + ThirdPartyUSDMinor int64 + CoinSellerUSDMinor int64 +} + +func (v financeRechargeValues) userUSDMinor() int64 { + return v.GoogleUSDMinor + v.ThirdPartyUSDMinor +} + +func (v financeRechargeValues) totalUSDMinor() int64 { + return v.userUSDMinor() + v.CoinSellerUSDMinor +} + +func (s *Service) applyFinanceRechargeOverview( + ctx context.Context, + out *AppOverview, + query OverviewQuery, + regions []RegionInfo, + restricted bool, + allowedRegionIDs []int64, +) error { + if s == nil || s.financeRecharge == nil || out == nil { + return nil + } + scopeRegionIDs := []int64(nil) + if query.RegionID > 0 { + scopeRegionIDs = []int64{normalizeRegionID(regions, query.RegionID)} + } else if len(query.RegionIDs) > 0 { + scopeRegionIDs = normalizeRegionIDs(regions, query.RegionIDs) + } else if restricted { + scopeRegionIDs = normalizeRegionIDs(regions, allowedRegionIDs) + } + + current, err := s.financeRecharge.FinanceRechargeOverview(ctx, payment.FinanceRechargeOverviewQuery{ + AppCode: out.AppCode, + StartAtMS: query.StartMS, + EndAtMS: query.EndMS, + ScopeRegionIDs: scopeRegionIDs, + IncludeRegions: true, + TZOffsetMinutes: financeRechargeTZOffsetMinutes, + }) + if err != nil { + return err + } + + var previous payment.FinanceRechargeOverview + if query.StartMS > 0 && query.EndMS > query.StartMS { + periodLength := query.EndMS - query.StartMS + previous, err = s.financeRecharge.FinanceRechargeOverview(ctx, payment.FinanceRechargeOverviewQuery{ + AppCode: out.AppCode, + StartAtMS: query.StartMS - periodLength, + EndAtMS: query.StartMS, + ScopeRegionIDs: scopeRegionIDs, + IncludeRegions: false, + TZOffsetMinutes: financeRechargeTZOffsetMinutes, + }) + if err != nil { + return err + } + } + + currentTotal := financeRechargeOverviewTotal(current) + previousTotal := financeRechargeOverviewTotal(previous) + applyFinanceRechargeValues(out.Total, currentTotal) + if query.StartMS > 0 && query.EndMS > query.StartMS { + out.Total["recharge_delta_rate"] = financeRechargeDelta(currentTotal.totalUSDMinor(), previousTotal.totalUSDMinor()) + out.Total["user_recharge_delta_rate"] = financeRechargeDelta(currentTotal.userUSDMinor(), previousTotal.userUSDMinor()) + } + // Finance 没有付费用户人数口径;金额切源后旧 ARPU/ARPPU 环比不可继续展示,否则分子来自旧 Dashboard。 + out.Total["arpu_delta_rate"] = nil + out.Total["arppu_delta_rate"] = nil + + out.DailySeries = overlayFinanceRechargeDaily(out.DailySeries, current.Daily) + out.RegionBreakdown, out.DailyRegionBreakdown = overlayFinanceRechargeRegions( + out.RegionBreakdown, + out.DailyRegionBreakdown, + current.Regions, + regions, + ) + // Finance 页面没有国家维度,国家充值必须整体标为不可用;保留任何旧 Dashboard 金额都会导致 + // App/区域合计与国家合计无法对账,并与下发的 Finance 指标来源说明矛盾。 + markFinanceRechargeUnavailable(out.CountryBreakdown) + markFinanceRechargeUnavailable(out.DailyCountryBreakdown) + out.ReportMetricSources = appendFinanceRechargeMetricSources(out.ReportMetricSources) + return nil +} + +func (s *Service) applyFinanceRechargeKpiWindow( + ctx context.Context, + appCode string, + startMS int64, + endMS int64, + total map[string]any, + regionMetrics map[int64]map[string]any, +) error { + financeOverview, err := s.financeRecharge.FinanceRechargeOverview(ctx, payment.FinanceRechargeOverviewQuery{ + AppCode: appCode, + StartAtMS: startMS, + EndAtMS: endMS, + IncludeRegions: true, + TZOffsetMinutes: financeRechargeTZOffsetMinutes, + }) + if err != nil { + return err + } + applyFinanceRechargeValues(total, financeRechargeOverviewTotal(financeOverview)) + for regionID, row := range regionMetrics { + zeroFinanceRecharge([]map[string]any{row}) + regionMetrics[regionID] = row + } + for _, financeRegion := range financeOverview.Regions { + row := regionMetrics[financeRegion.RegionID] + if row == nil { + row = map[string]any{"region_id": financeRegion.RegionID} + regionMetrics[financeRegion.RegionID] = row + } + // KPI 只消费区域总充值;Finance 区域分布没有渠道拆分,不能用 0 伪造各渠道。 + applyFinanceRechargeTotalOnly(row, financeRegion.TotalUSDMinor) + } + return nil +} + +func financeRechargeOverviewTotal(overview payment.FinanceRechargeOverview) financeRechargeValues { + var total financeRechargeValues + for _, row := range overview.Daily { + total.GoogleUSDMinor += row.GoogleUSDMinor + total.ThirdPartyUSDMinor += row.ThirdPartyUSDMinor + total.CoinSellerUSDMinor += row.CoinSellerUSDMinor + } + return total +} + +func financeRechargeValuesFromDaily(row payment.FinanceRechargeDailyBucket) financeRechargeValues { + return financeRechargeValues{ + GoogleUSDMinor: row.GoogleUSDMinor, + ThirdPartyUSDMinor: row.ThirdPartyUSDMinor, + CoinSellerUSDMinor: row.CoinSellerUSDMinor, + } +} + +func applyFinanceRechargeValues(row map[string]any, values financeRechargeValues) { + if row == nil { + return + } + row["google_recharge_usd_minor"] = values.GoogleUSDMinor + // Databi 历史字段名是 mifapay,但 Finance 的 thirdParty 包含全部非 Google 三方渠道。 + row["mifapay_recharge_usd_minor"] = values.ThirdPartyUSDMinor + row["third_party_recharge_usd_minor"] = values.ThirdPartyUSDMinor + row["coin_seller_recharge_usd_minor"] = values.CoinSellerUSDMinor + row["user_recharge_usd_minor"] = values.userUSDMinor() + row["recharge_usd_minor"] = values.totalUSDMinor() + row["total_recharge_usd_minor"] = values.totalUSDMinor() + row["new_user_recharge_usd_minor"] = nil + markFinanceRechargeDerivedUnavailable(row) +} + +func applyFinanceRechargeTotalOnly(row map[string]any, totalUSDMinor int64) { + if row == nil { + return + } + markFinanceRechargeUnavailable([]map[string]any{row}) + row["recharge_usd_minor"] = totalUSDMinor + row["total_recharge_usd_minor"] = totalUSDMinor +} + +func markFinanceRechargeDerivedUnavailable(row map[string]any) { + if row == nil { + return + } + // Finance 的币商/H5 金额没有与旧 Dashboard paid_users 一致的去重人数, + // 因此 ARPU/ARPPU 不做跨来源除法;前端通过该标记在跨 App 聚合时继续保持缺失。 + row["finance_recharge_aligned"] = true + row["arpu_usd_minor"] = nil + row["arppu_usd_minor"] = nil +} + +func overlayFinanceRechargeDaily(existing []map[string]any, finance []payment.FinanceRechargeDailyBucket) []map[string]any { + byDay := make(map[string]payment.FinanceRechargeDailyBucket, len(finance)) + for _, row := range finance { + if row.Date != "" { + byDay[row.Date] = row + } + } + seen := map[string]struct{}{} + for _, row := range existing { + day := rowString(row, "stat_day", "label") + bucket := byDay[day] + applyFinanceRechargeValues(row, financeRechargeValuesFromDaily(bucket)) + if day != "" { + seen[day] = struct{}{} + } + } + for day, bucket := range byDay { + if _, ok := seen[day]; ok { + continue + } + row := map[string]any{"label": day, "stat_day": day} + applyFinanceRechargeValues(row, financeRechargeValuesFromDaily(bucket)) + existing = append(existing, row) + } + sort.Slice(existing, func(i, j int) bool { + return rowString(existing[i], "stat_day", "label") < rowString(existing[j], "stat_day", "label") + }) + return existing +} + +func overlayFinanceRechargeRegions( + regionRows []map[string]any, + dailyRegionRows []map[string]any, + financeRegions []payment.FinanceRechargeRegionOverview, + regions []RegionInfo, +) ([]map[string]any, []map[string]any) { + zeroFinanceRecharge(regionRows) + dailyAvailable := false + for _, financeRegion := range financeRegions { + if financeRegion.DailyAvailable { + dailyAvailable = true + break + } + } + if dailyAvailable { + zeroFinanceRecharge(dailyRegionRows) + } else { + // 默认全量查询只复用 Finance 已有的区域总额;逐日渠道仅在用户显式选择区域时查询。 + // 这里用 null 清掉旧 Dashboard 值,避免“未查询”被展示成 0 或旧口径。 + markFinanceRechargeUnavailable(dailyRegionRows) + } + regionIndex := map[int64]map[string]any{} + for _, row := range regionRows { + if regionID, ok := rowInt64(row, "region_id", "regionId"); ok { + regionIndex[normalizeRegionID(regions, regionID)] = row + } + } + dailyIndex := map[string]map[string]any{} + for _, row := range dailyRegionRows { + regionID, ok := rowInt64(row, "region_id", "regionId") + if !ok { + continue + } + day := rowString(row, "stat_day", "label") + dailyIndex[financeRegionDayKey(normalizeRegionID(regions, regionID), day)] = row + } + + for _, financeRegion := range financeRegions { + regionID := normalizeRegionID(regions, financeRegion.RegionID) + row := regionIndex[regionID] + if row == nil { + code, name := regionDisplay(regions, regionID) + row = map[string]any{"region_id": regionID, "region_code": code, "region_name": name} + regionRows = append(regionRows, row) + regionIndex[regionID] = row + } + var total financeRechargeValues + for _, dayBucket := range financeRegion.Daily { + values := financeRechargeValuesFromDaily(dayBucket) + total.GoogleUSDMinor += values.GoogleUSDMinor + total.ThirdPartyUSDMinor += values.ThirdPartyUSDMinor + total.CoinSellerUSDMinor += values.CoinSellerUSDMinor + key := financeRegionDayKey(regionID, dayBucket.Date) + dailyRow := dailyIndex[key] + if dailyRow == nil { + code, name := regionDisplay(regions, regionID) + dailyRow = map[string]any{ + "label": dayBucket.Date, + "stat_day": dayBucket.Date, + "region_id": regionID, + "region_code": code, + "region_name": name, + } + dailyRegionRows = append(dailyRegionRows, dailyRow) + dailyIndex[key] = dailyRow + } + applyFinanceRechargeValues(dailyRow, values) + } + if financeRegion.DailyAvailable { + applyFinanceRechargeValues(row, total) + } else { + applyFinanceRechargeTotalOnly(row, financeRegion.TotalUSDMinor) + } + } + sort.Slice(regionRows, func(i, j int) bool { + left, _ := rowInt64(regionRows[i], "region_id", "regionId") + right, _ := rowInt64(regionRows[j], "region_id", "regionId") + return left < right + }) + sort.Slice(dailyRegionRows, func(i, j int) bool { + leftDay := rowString(dailyRegionRows[i], "stat_day", "label") + rightDay := rowString(dailyRegionRows[j], "stat_day", "label") + if leftDay != rightDay { + return leftDay < rightDay + } + left, _ := rowInt64(dailyRegionRows[i], "region_id", "regionId") + right, _ := rowInt64(dailyRegionRows[j], "region_id", "regionId") + return left < right + }) + return regionRows, dailyRegionRows +} + +func zeroFinanceRecharge(rows []map[string]any) { + for _, row := range rows { + applyFinanceRechargeValues(row, financeRechargeValues{}) + } +} + +func markFinanceRechargeUnavailable(rows []map[string]any) { + for _, row := range rows { + row["google_recharge_usd_minor"] = nil + row["mifapay_recharge_usd_minor"] = nil + row["third_party_recharge_usd_minor"] = nil + row["coin_seller_recharge_usd_minor"] = nil + row["user_recharge_usd_minor"] = nil + row["recharge_usd_minor"] = nil + row["total_recharge_usd_minor"] = nil + row["new_user_recharge_usd_minor"] = nil + markFinanceRechargeDerivedUnavailable(row) + } +} + +func requirementsIncludesRevenue(section string) bool { + switch strings.ToLower(strings.TrimSpace(section)) { + case "", "all", "revenue": + return true + default: + return false + } +} + +func requirementsHasCohortFilter(query RequirementsQuery) bool { + for _, value := range []string{query.UserRole, query.PayerType, query.NewUserType} { + normalized := strings.ToLower(strings.TrimSpace(value)) + if normalized != "" && normalized != "all" { + return true + } + } + return false +} + +func overlayFinanceRechargeRequirements(sections []map[string]any, overview payment.FinanceRechargeOverview) { + byDay := make(map[string]financeRechargeValues, len(overview.Daily)) + for _, bucket := range overview.Daily { + if bucket.Date != "" { + byDay[bucket.Date] = financeRechargeValuesFromDaily(bucket) + } + } + total := financeRechargeOverviewTotal(overview) + for _, section := range sections { + if rowString(section, "key") != "revenue" { + continue + } + section["recharge_source"] = "finance" + section["finance_recharge_available"] = true + updateFinanceRequirementMetadata(section, true) + if row, ok := section["total"].(map[string]any); ok { + applyFinanceRequirementAmounts(row, total, 1) + } + + daily := anyRowSlice(section["daily_series"]) + seen := map[string]struct{}{} + for _, row := range daily { + day := rowString(row, "stat_day", "label") + applyFinanceRequirementAmounts(row, byDay[day], 1) + if day != "" { + seen[day] = struct{}{} + } + } + for day, values := range byDay { + if _, ok := seen[day]; ok { + continue + } + row := map[string]any{"label": day, "stat_day": day} + applyFinanceRequirementAmounts(row, values, 1) + daily = append(daily, row) + } + sort.Slice(daily, func(i, j int) bool { + return rowString(daily[i], "stat_day", "label") < rowString(daily[j], "stat_day", "label") + }) + section["daily_series"] = daily + if row, ok := section["average"].(map[string]any); ok { + applyFinanceRequirementAmounts(row, total, len(daily)) + } + } +} + +func markFinanceRechargeRequirementsUnavailable(sections []map[string]any) { + for _, section := range sections { + if rowString(section, "key") != "revenue" { + continue + } + section["recharge_source"] = "unavailable_for_cohort" + section["finance_recharge_available"] = false + updateFinanceRequirementMetadata(section, false) + if row, ok := section["total"].(map[string]any); ok { + markFinanceRequirementAmountsUnavailable(row) + } + if row, ok := section["average"].(map[string]any); ok { + markFinanceRequirementAmountsUnavailable(row) + } + for _, row := range anyRowSlice(section["daily_series"]) { + markFinanceRequirementAmountsUnavailable(row) + } + } +} + +func applyFinanceRequirementAmounts(row map[string]any, values financeRechargeValues, dayCount int) { + if row == nil { + return + } + assign := func(key string, value int64) { + if dayCount > 1 { + row[key] = float64(value) / float64(dayCount) + return + } + row[key] = value + } + assign("recharge_usd_minor", values.totalUSDMinor()) + assign("google_recharge_usd_minor", values.GoogleUSDMinor) + assign("third_party_recharge_usd_minor", values.ThirdPartyUSDMinor) + assign("coin_seller_recharge_usd_minor", values.CoinSellerUSDMinor) + // Finance 无首充/复购和去重付费人数维度,这些金额均值不能继续沿用宽表分子。 + row["first_recharge_usd_minor"] = nil + row["repeat_recharge_usd_minor"] = nil + row["arpu_usd_minor"] = nil + row["arppu_usd_minor"] = nil + row["finance_recharge_aligned"] = true +} + +func markFinanceRequirementAmountsUnavailable(row map[string]any) { + if row == nil { + return + } + for _, key := range []string{ + "recharge_usd_minor", + "google_recharge_usd_minor", + "third_party_recharge_usd_minor", + "coin_seller_recharge_usd_minor", + "first_recharge_usd_minor", + "repeat_recharge_usd_minor", + "arpu_usd_minor", + "arppu_usd_minor", + } { + row[key] = nil + } + row["finance_recharge_aligned"] = true +} + +func updateFinanceRequirementMetadata(section map[string]any, available bool) { + definitions := map[string]string{ + "recharge_usd_minor": "Finance 成功充值总额 = Google + 非 Google 三方 + 币商充值。", + "google_recharge_usd_minor": "Finance Google 成功充值。", + "third_party_recharge_usd_minor": "Finance 非 Google 三方成功充值。", + "coin_seller_recharge_usd_minor": "Finance 已核验币商订单 + H5 币商身份成功充值。", + "first_recharge_usd_minor": "Finance 暂无首充金额拆分,本字段不可用。", + "repeat_recharge_usd_minor": "Finance 暂无复购金额拆分,本字段不可用。", + "arpu_usd_minor": "Finance 金额没有同口径活跃人数,本字段不可用。", + "arppu_usd_minor": "Finance 币商/H5 金额没有同口径付费人数,本字段不可用。", + } + if !available { + for _, key := range []string{"recharge_usd_minor", "google_recharge_usd_minor", "third_party_recharge_usd_minor", "coin_seller_recharge_usd_minor"} { + definitions[key] = "Finance 不支持当前角色/付费身份/新老用户筛选,本字段不可用。" + } + } + + metricDefinitions := anyRowSlice(section["metric_definitions"]) + definitionIndex := map[string]map[string]any{} + for _, row := range metricDefinitions { + definitionIndex[rowString(row, "metric", "field")] = row + } + for metric, definition := range definitions { + row := definitionIndex[metric] + if row == nil { + row = map[string]any{"metric": metric} + metricDefinitions = append(metricDefinitions, row) + } + row["definition"] = definition + } + section["metric_definitions"] = metricDefinitions + + columns := anyRowSlice(section["columns"]) + for _, column := range columns { + key := rowString(column, "key") + if definition, ok := definitions[key]; ok { + column["tooltip"] = definition + } + } + section["columns"] = columns +} + +func financeRegionDayKey(regionID int64, day string) string { + return strconv.FormatInt(regionID, 10) + "|" + day +} + +func financeRechargeDelta(current int64, previous int64) float64 { + if previous == 0 { + if current > 0 { + return 1 + } + return 0 + } + denominator := previous + if denominator < 0 { + denominator = -denominator + } + return float64(current-previous) / float64(denominator) +} + +func appendFinanceRechargeMetricSources(value any) []map[string]any { + out := anyRowSlice(value) + fields := []struct { + field string + definition string + }{ + {"recharge_usd_minor", "Finance 总充值 = Google + 三方 + 币商充值。"}, + {"google_recharge_usd_minor", "Finance 谷歌成功充值账单。"}, + {"mifapay_recharge_usd_minor", "Finance 非 Google 三方成功充值账单。"}, + {"coin_seller_recharge_usd_minor", "Finance 已核验币商订单 + H5 币商身份成功充值。"}, + } + for _, item := range fields { + replaced := false + for index := range out { + if rowString(out[index], "field") != item.field { + continue + } + out[index] = map[string]any{ + "available": true, + "definition": item.definition, + "field": item.field, + "source": "Finance recharge-bills overview", + } + replaced = true + break + } + if !replaced { + out = append(out, map[string]any{ + "available": true, + "definition": item.definition, + "field": item.field, + "source": "Finance recharge-bills overview", + }) + } + } + return out +} diff --git a/server/admin/internal/modules/databi/finance_recharge_test.go b/server/admin/internal/modules/databi/finance_recharge_test.go new file mode 100644 index 00000000..7ee6b205 --- /dev/null +++ b/server/admin/internal/modules/databi/finance_recharge_test.go @@ -0,0 +1,217 @@ +package databi + +import ( + "context" + "testing" + + "hyapp-admin-server/internal/modules/payment" +) + +type fakeFinanceRechargeOverviewSource struct { + calls []payment.FinanceRechargeOverviewQuery +} + +func (f *fakeFinanceRechargeOverviewSource) FinanceRechargeOverview(_ context.Context, query payment.FinanceRechargeOverviewQuery) (payment.FinanceRechargeOverview, error) { + f.calls = append(f.calls, query) + if query.EndAtMS == 200 { + return payment.FinanceRechargeOverview{Daily: []payment.FinanceRechargeDailyBucket{{ + Date: "1970-01-01", GoogleUSDMinor: 50, ThirdPartyUSDMinor: 100, CoinSellerUSDMinor: 50, + }}}, nil + } + region := payment.FinanceRechargeRegionOverview{RegionID: 7, TotalUSDMinor: 150} + if len(query.ScopeRegionIDs) > 0 { + region.DailyAvailable = true + region.Daily = []payment.FinanceRechargeDailyBucket{{ + Date: "2026-07-10", GoogleUSDMinor: 40, ThirdPartyUSDMinor: 50, CoinSellerUSDMinor: 60, + }} + } + return payment.FinanceRechargeOverview{ + Daily: []payment.FinanceRechargeDailyBucket{ + {Date: "2026-07-10", GoogleUSDMinor: 100, ThirdPartyUSDMinor: 200, CoinSellerUSDMinor: 300}, + {Date: "2026-07-11", GoogleUSDMinor: 10, ThirdPartyUSDMinor: 20, CoinSellerUSDMinor: 30}, + }, + Regions: []payment.FinanceRechargeRegionOverview{region}, + }, nil +} + +func TestApplyFinanceRechargeOverviewReplacesDashboardRechargeMetrics(t *testing.T) { + finance := &fakeFinanceRechargeOverviewSource{} + service := &Service{financeRecharge: finance} + out := AppOverview{ + AppCode: "aslan", + Total: map[string]any{ + "active_users": int64(10), "paid_users": int64(2), "recharge_usd_minor": int64(9999), + "coin_seller_recharge_usd_minor": int64(8888), "arpu_delta_rate": 0.9, "arppu_delta_rate": 0.8, + }, + DailySeries: []map[string]any{{ + "stat_day": "2026-07-10", "active_users": int64(5), "paid_users": int64(1), + "recharge_usd_minor": int64(9999), "coin_seller_recharge_usd_minor": int64(8888), + }}, + RegionBreakdown: []map[string]any{{ + "region_id": int64(7), "active_users": int64(5), "paid_users": int64(1), + "recharge_usd_minor": int64(9999), "coin_seller_recharge_usd_minor": int64(8888), + }, { + "region_id": int64(8), "active_users": int64(3), "paid_users": int64(1), + "recharge_usd_minor": int64(7777), "coin_seller_recharge_usd_minor": int64(6666), + }}, + DailyRegionBreakdown: []map[string]any{{ + "region_id": int64(7), "stat_day": "2026-07-10", "active_users": int64(5), "paid_users": int64(1), + "recharge_usd_minor": int64(9999), "coin_seller_recharge_usd_minor": int64(8888), + }, { + "region_id": int64(8), "stat_day": "2026-07-10", "active_users": int64(3), "paid_users": int64(1), + "recharge_usd_minor": int64(7777), "coin_seller_recharge_usd_minor": int64(6666), + }}, + CountryBreakdown: []map[string]any{{ + "country_code": "SA", "recharge_usd_minor": int64(9999), "coin_seller_recharge_usd_minor": int64(8888), + }}, + DailyCountryBreakdown: []map[string]any{{ + "country_code": "SA", "stat_day": "2026-07-10", "recharge_usd_minor": int64(9999), "coin_seller_recharge_usd_minor": int64(8888), + }}, + } + query := OverviewQuery{StartMS: 200, EndMS: 300} + regions := []RegionInfo{ + {AppCode: "aslan", RegionID: 7, RegionCode: "SA", RegionName: "沙特"}, + {AppCode: "aslan", RegionID: 8, RegionCode: "AE", RegionName: "阿联酋"}, + } + + if err := service.applyFinanceRechargeOverview(context.Background(), &out, query, regions, false, nil); err != nil { + t.Fatalf("apply Finance recharge overview failed: %v", err) + } + + if got := mustMetric(t, out.Total, "recharge_usd_minor"); got != 660 { + t.Fatalf("total recharge should use Finance channels: got %d", got) + } + if got := mustMetric(t, out.Total, "coin_seller_recharge_usd_minor"); got != 330 { + t.Fatalf("coin seller recharge should use Finance verified/H5 amount: got %d", got) + } + if got := mustMetric(t, out.Total, "mifapay_recharge_usd_minor"); got != 220 { + t.Fatalf("third-party recharge should use Finance amount: got %d", got) + } + if delta, ok := out.Total["recharge_delta_rate"].(float64); !ok || delta != 2.3 { + t.Fatalf("recharge delta should compare the previous Finance range: %#v", out.Total["recharge_delta_rate"]) + } + if out.Total["arpu_delta_rate"] != nil || out.Total["arppu_delta_rate"] != nil { + t.Fatalf("amount-derived deltas must not retain the old Dashboard numerator: %#v", out.Total) + } + if out.Total["arpu_usd_minor"] != nil || out.Total["arppu_usd_minor"] != nil || out.Total["finance_recharge_aligned"] != true { + t.Fatalf("Finance recharge must not be divided by old Dashboard users: %#v", out.Total) + } + if len(out.DailySeries) != 2 || mustMetric(t, out.DailySeries[0], "recharge_usd_minor") != 600 || mustMetric(t, out.DailySeries[1], "recharge_usd_minor") != 60 { + t.Fatalf("daily series should be replaced and missing Finance days appended: %#v", out.DailySeries) + } + if mustMetric(t, out.RegionBreakdown[0], "recharge_usd_minor") != 150 || out.RegionBreakdown[0]["coin_seller_recharge_usd_minor"] != nil { + t.Fatalf("global region rows should use Finance totals without inventing channel splits: %#v", out.RegionBreakdown) + } + if mustMetric(t, out.RegionBreakdown[1], "recharge_usd_minor") != 0 || out.DailyRegionBreakdown[0]["recharge_usd_minor"] != nil || out.DailyRegionBreakdown[1]["recharge_usd_minor"] != nil { + t.Fatalf("regions/days missing from Finance must not retain Dashboard recharge: region=%#v daily=%#v", out.RegionBreakdown, out.DailyRegionBreakdown) + } + if out.CountryBreakdown[0]["recharge_usd_minor"] != nil || out.CountryBreakdown[0]["coin_seller_recharge_usd_minor"] != nil || out.DailyCountryBreakdown[0]["recharge_usd_minor"] != nil { + t.Fatalf("country recharge must be unavailable because Finance has no country dimension: %#v %#v", out.CountryBreakdown, out.DailyCountryBreakdown) + } + if len(finance.calls) != 2 || !finance.calls[0].IncludeRegions || finance.calls[1].IncludeRegions || finance.calls[0].TZOffsetMinutes != 480 { + t.Fatalf("current/previous Finance queries mismatch: %#v", finance.calls) + } +} + +func TestApplyFinanceRechargeOverviewLoadsDailyChannelsOnlyForExplicitRegions(t *testing.T) { + finance := &fakeFinanceRechargeOverviewSource{} + service := &Service{financeRecharge: finance} + out := AppOverview{ + AppCode: "aslan", + Total: map[string]any{}, + RegionBreakdown: []map[string]any{{"region_id": int64(7)}}, + DailyRegionBreakdown: []map[string]any{{"region_id": int64(7), "stat_day": "2026-07-10"}}, + } + query := OverviewQuery{StartMS: 200, EndMS: 300, RegionIDs: []int64{7}} + regions := []RegionInfo{{AppCode: "aslan", RegionID: 7, RegionCode: "SA", RegionName: "沙特"}} + + if err := service.applyFinanceRechargeOverview(context.Background(), &out, query, regions, false, nil); err != nil { + t.Fatalf("apply scoped Finance recharge overview failed: %v", err) + } + if mustMetric(t, out.RegionBreakdown[0], "recharge_usd_minor") != 150 || mustMetric(t, out.DailyRegionBreakdown[0], "coin_seller_recharge_usd_minor") != 60 { + t.Fatalf("explicit regions should receive Finance daily channels: region=%#v daily=%#v", out.RegionBreakdown, out.DailyRegionBreakdown) + } + if len(finance.calls) != 2 || len(finance.calls[0].ScopeRegionIDs) != 1 || finance.calls[0].ScopeRegionIDs[0] != 7 || finance.calls[1].ScopeRegionIDs[0] != 7 { + t.Fatalf("current and previous windows must keep the explicit region scope: %#v", finance.calls) + } +} + +func TestApplyFinanceRechargeKpiWindowUsesFinanceTotalsAndRegions(t *testing.T) { + finance := &fakeFinanceRechargeOverviewSource{} + service := &Service{financeRecharge: finance} + total := map[string]any{"recharge_usd_minor": int64(9999), "coin_seller_recharge_usd_minor": int64(8888)} + regions := map[int64]map[string]any{7: { + "region_id": int64(7), "recharge_usd_minor": int64(9999), "coin_seller_recharge_usd_minor": int64(8888), + }} + + if err := service.applyFinanceRechargeKpiWindow(context.Background(), "aslan", 200, 300, total, regions); err != nil { + t.Fatalf("apply Finance KPI window failed: %v", err) + } + if mustMetric(t, total, "recharge_usd_minor") != 660 || mustMetric(t, regions[7], "recharge_usd_minor") != 150 { + t.Fatalf("KPI window should use Finance total/region values: total=%#v regions=%#v", total, regions) + } + if len(finance.calls) != 1 || !finance.calls[0].IncludeRegions { + t.Fatalf("KPI window should issue one Finance query with regions: %#v", finance.calls) + } +} + +func TestOverlayFinanceRechargeRequirementsUsesFinanceAmounts(t *testing.T) { + sections := []map[string]any{{ + "key": "revenue", + "total": map[string]any{"recharge_usd_minor": int64(9999), "recharge_users": int64(4), "arppu_usd_minor": int64(123)}, + "average": map[string]any{"recharge_usd_minor": float64(9999)}, + "daily_series": []any{ + map[string]any{"stat_day": "2026-07-10", "recharge_usd_minor": int64(9999), "recharge_users": int64(3)}, + map[string]any{"stat_day": "2026-07-11", "recharge_usd_minor": int64(9999), "recharge_users": int64(1)}, + }, + "columns": []any{map[string]any{"key": "coin_seller_recharge_usd_minor"}}, + "metric_definitions": []any{map[string]any{"metric": "coin_seller_recharge_usd_minor", "definition": "old"}}, + }} + overlayFinanceRechargeRequirements(sections, payment.FinanceRechargeOverview{Daily: []payment.FinanceRechargeDailyBucket{ + {Date: "2026-07-10", GoogleUSDMinor: 100, ThirdPartyUSDMinor: 200, CoinSellerUSDMinor: 300}, + {Date: "2026-07-11", GoogleUSDMinor: 10, ThirdPartyUSDMinor: 20, CoinSellerUSDMinor: 30}, + }}) + + total := sections[0]["total"].(map[string]any) + daily := anyRowSlice(sections[0]["daily_series"]) + average := sections[0]["average"].(map[string]any) + if mustMetric(t, total, "recharge_usd_minor") != 660 || mustMetric(t, total, "coin_seller_recharge_usd_minor") != 330 { + t.Fatalf("requirements total should use Finance channels: %#v", total) + } + if mustMetric(t, daily[0], "recharge_usd_minor") != 600 || mustMetric(t, daily[1], "recharge_usd_minor") != 60 { + t.Fatalf("requirements daily should use Finance channels: %#v", daily) + } + if average["recharge_usd_minor"] != float64(330) || total["recharge_users"] != int64(4) || total["arppu_usd_minor"] != nil { + t.Fatalf("requirements average/non-amount fields mismatch: total=%#v average=%#v", total, average) + } + if sections[0]["recharge_source"] != "finance" || sections[0]["finance_recharge_available"] != true { + t.Fatalf("requirements must expose Finance source metadata: %#v", sections[0]) + } +} + +func TestFinanceRechargeRequirementsCohortAmountsAreUnavailable(t *testing.T) { + query := RequirementsQuery{UserRole: "host", PayerType: "all", NewUserType: "all"} + if !requirementsHasCohortFilter(query) || !requirementsIncludesRevenue("revenue") { + t.Fatalf("cohort/revenue detection mismatch") + } + sections := []map[string]any{{ + "key": "revenue", + "total": map[string]any{"recharge_usd_minor": int64(9999), "recharge_users": int64(2)}, + "average": map[string]any{"recharge_usd_minor": float64(9999)}, + "daily_series": []any{map[string]any{"stat_day": "2026-07-10", "recharge_usd_minor": int64(9999)}}, + }} + markFinanceRechargeRequirementsUnavailable(sections) + total := sections[0]["total"].(map[string]any) + if total["recharge_usd_minor"] != nil || total["recharge_users"] != int64(2) || sections[0]["finance_recharge_available"] != false { + t.Fatalf("cohort should retain counts but hide non-Finance amounts: %#v", sections[0]) + } +} + +func mustMetric(t *testing.T, row map[string]any, key string) int64 { + t.Helper() + value, ok := metricInt64(row[key]) + if !ok { + t.Fatalf("metric %s missing from %#v", key, row) + } + return value +} diff --git a/server/admin/internal/modules/databi/handler.go b/server/admin/internal/modules/databi/handler.go index ed517d0d..9ae127d2 100644 --- a/server/admin/internal/modules/databi/handler.go +++ b/server/admin/internal/modules/databi/handler.go @@ -40,11 +40,12 @@ func (h *Handler) Overview(c *gin.Context) { return } result, err := h.service.Overview(c.Request.Context(), access, OverviewQuery{ - StatTZ: c.Query("stat_tz"), - StartMS: queryInt64(c, "start_ms"), - EndMS: queryInt64(c, "end_ms"), - AppCodes: splitCSV(c.Query("app_codes")), - RegionID: queryInt64(c, "region_id"), + StatTZ: c.Query("stat_tz"), + StartMS: queryInt64(c, "start_ms"), + EndMS: queryInt64(c, "end_ms"), + AppCodes: splitCSV(c.Query("app_codes")), + RegionID: queryInt64(c, "region_id"), + RegionIDs: queryInt64CSV(c.Query("region_ids")), }) if err != nil { response.ServerError(c, "获取 BI 统计数据失败") @@ -78,15 +79,16 @@ func (h *Handler) Requirements(c *gin.Context) { return } result, err := h.service.Requirements(c.Request.Context(), access, RequirementsQuery{ - StatTZ: c.Query("stat_tz"), - StartMS: queryInt64(c, "start_ms"), - EndMS: queryInt64(c, "end_ms"), - AppCodes: splitCSV(c.Query("app_codes")), - RegionID: queryInt64(c, "region_id"), - RegionIDs: queryInt64CSV(c.Query("region_ids")), - Section: c.Query("section"), - UserRole: c.Query("user_role"), - PayerType: c.Query("payer_type"), + StatTZ: c.Query("stat_tz"), + StartMS: queryInt64(c, "start_ms"), + EndMS: queryInt64(c, "end_ms"), + AppCodes: splitCSV(c.Query("app_codes")), + RegionID: queryInt64(c, "region_id"), + RegionIDs: queryInt64CSV(c.Query("region_ids")), + Section: c.Query("section"), + UserRole: c.Query("user_role"), + PayerType: c.Query("payer_type"), + NewUserType: c.Query("new_user_type"), }) if err != nil { response.ServerError(c, "获取数据需求指标失败") diff --git a/server/admin/internal/modules/databi/service.go b/server/admin/internal/modules/databi/service.go index d8d310e7..8de8b868 100644 --- a/server/admin/internal/modules/databi/service.go +++ b/server/admin/internal/modules/databi/service.go @@ -49,6 +49,7 @@ type Service struct { dashboards *dashboard.DashboardService apps *appregistry.Service user userclient.Client + financeRecharge payment.FinanceRechargeOverviewSource legacyApps []LegacyAppDescriptor legacyRegionCatalogs []LegacyRegionCatalogSource @@ -115,6 +116,14 @@ func WithLegacyApps(apps ...LegacyAppDescriptor) ServiceOption { } } +// WithFinanceRechargeOverviewSource 强制 Social BI 的充值渠道复用 Finance 口径; +// 不在 Databi 内复制订单、H5 或 legacy 账单 SQL,避免两个页面再次漂移。 +func WithFinanceRechargeOverviewSource(source payment.FinanceRechargeOverviewSource) ServiceOption { + return func(s *Service) { + s.financeRecharge = source + } +} + func WithLegacyRegionCatalogs(catalogs ...LegacyRegionCatalogSource) ServiceOption { return func(s *Service) { for _, catalog := range catalogs { @@ -242,11 +251,12 @@ func (s *Service) listOperators(actorUserID uint, viewAll bool) ([]OperatorInfo, } type OverviewQuery struct { - StatTZ string - StartMS int64 - EndMS int64 - AppCodes []string - RegionID int64 + StatTZ string + StartMS int64 + EndMS int64 + AppCodes []string + RegionID int64 + RegionIDs []int64 } type AppOverview struct { @@ -298,15 +308,16 @@ type FunnelResult struct { } type RequirementsQuery struct { - StatTZ string - StartMS int64 - EndMS int64 - AppCodes []string - RegionID int64 - RegionIDs []int64 - Section string - UserRole string - PayerType string + StatTZ string + StartMS int64 + EndMS int64 + AppCodes []string + RegionID int64 + RegionIDs []int64 + Section string + UserRole string + PayerType string + NewUserType string } type AppRequirements struct { @@ -466,21 +477,47 @@ func (s *Service) appRequirements(ctx context.Context, app AppInfo, regions []Re regionIDs = requestRegionIDs } requirements, err := s.dashboards.SocialRequirements(ctx, dashboard.SocialRequirementsQuery{ - AppCode: app.AppCode, - StatTZ: query.StatTZ, - StartMS: query.StartMS, - EndMS: query.EndMS, - RegionID: query.RegionID, - RegionIDs: regionIDs, - Section: query.Section, - UserRole: query.UserRole, - PayerType: query.PayerType, + AppCode: app.AppCode, + StatTZ: query.StatTZ, + StartMS: query.StartMS, + EndMS: query.EndMS, + RegionID: query.RegionID, + RegionIDs: regionIDs, + Section: query.Section, + UserRole: query.UserRole, + PayerType: query.PayerType, + NewUserType: query.NewUserType, }) if err != nil { out.Error = fmt.Sprintf("获取数据需求指标失败: %v", err) return out } out.Sections = anyRowSlice(requirements["sections"]) + if s.financeRecharge != nil && requirementsIncludesRevenue(query.Section) { + if requirementsHasCohortFilter(query) { + // Finance 不具备角色/付费身份/新老用户维度;筛选后必须把金额标为不可用, + // 不能把全量 Finance 金额灌进 cohort,也不能静默展示旧宽表金额冒充 Finance。 + markFinanceRechargeRequirementsUnavailable(out.Sections) + } else { + financeRegionIDs := regionIDs + if query.RegionID > 0 { + financeRegionIDs = []int64{normalizeRegionID(regions, query.RegionID)} + } + financeOverview, financeErr := s.financeRecharge.FinanceRechargeOverview(ctx, payment.FinanceRechargeOverviewQuery{ + AppCode: app.AppCode, + StartAtMS: query.StartMS, + EndAtMS: query.EndMS, + ScopeRegionIDs: financeRegionIDs, + IncludeRegions: false, + TZOffsetMinutes: financeRechargeTZOffsetMinutes, + }) + if financeErr != nil { + out.Error = fmt.Sprintf("获取 Finance 充值统计失败: %v", financeErr) + return out + } + overlayFinanceRechargeRequirements(out.Sections, financeOverview) + } + } out.UpdatedAtMS = time.Now().UnixMilli() return out } @@ -587,6 +624,17 @@ func (s *Service) appOverview(ctx context.Context, app AppInfo, regions []Region out.Error = "该 App 未配置此区域" return out } + requestRegionIDs := normalizeRegionIDs(regions, query.RegionIDs) + if len(query.RegionIDs) > 0 && len(requestRegionIDs) == 0 { + out.Error = "该 App 未配置所选区域" + return out + } + for _, regionID := range requestRegionIDs { + if !access.Allows(app.AppCode, regionID) { + out.Error = "没有该区域的数据权限" + return out + } + } overview, err := s.statisticsOverview(ctx, dashboard.StatisticsQuery{ AppCode: app.AppCode, @@ -606,13 +654,19 @@ func (s *Service) appOverview(ctx context.Context, app AppInfo, regions []Region dailyCountryRows := anyRowSlice(overview["daily_country_breakdown"]) resolve := regionResolver(regions) - restricted := !allowAll && query.RegionID == 0 + restricted := !allowAll && query.RegionID == 0 && len(requestRegionIDs) == 0 + filterRegionIDs := requestRegionIDs if restricted { - allowedSet := int64Set(allowedIDs) - countryRows = filterRowsByRegion(countryRows, resolve, allowedSet) - dailyCountryRows = filterRowsByRegion(dailyCountryRows, resolve, allowedSet) + filterRegionIDs = allowedIDs out.Restricted = true out.AllowedRegionIDs = allowedIDs + } + if query.RegionID == 0 && len(filterRegionIDs) > 0 { + // statistics-service 目前只有单区域参数;多区域在已返回的国家投影上裁剪并重算非充值指标, + // 充值随后由 Finance 按同一组区域重新覆盖,避免顶栏多选退回旧统计口径。 + allowedSet := int64Set(filterRegionIDs) + countryRows = filterRowsByRegion(countryRows, resolve, allowedSet) + dailyCountryRows = filterRowsByRegion(dailyCountryRows, resolve, allowedSet) totalAcc := newMetricAccumulator() for _, row := range countryRows { @@ -631,6 +685,13 @@ func (s *Service) appOverview(ctx context.Context, app AppInfo, regions []Region out.DailyCountryBreakdown = dailyCountryRows out.ReportMetricSources = overview["report_metric_sources"] out.UpdatedAtMS = overview["updated_at_ms"] + if s.financeRecharge != nil { + // Social BI 的充值卡片、趋势和区域下钻必须与 Finance 同源; + // Finance 查询失败时整 App 显式报错,不能静默回退到旧后台库存流水。 + if err := s.applyFinanceRechargeOverview(ctx, &out, query, regions, restricted, allowedIDs); err != nil { + out.Error = fmt.Sprintf("获取 Finance 充值统计失败: %v", err) + } + } return out } @@ -1033,7 +1094,15 @@ func (s *Service) kpiWindow(ctx context.Context, app AppInfo, resolve func(map[s regionMetrics[regionID] = row } } - return topLevelMetrics(overview), regionMetrics, nil + total := topLevelMetrics(overview) + if s.financeRecharge != nil { + // 运营中心的人×App×区域充值 KPI 与经营概览共用 Finance 口径; + // 否则同一路径切换菜单会出现两套充值金额。 + if err := s.applyFinanceRechargeKpiWindow(ctx, app.AppCode, startMS, endMS, total, regionMetrics); err != nil { + return nil, nil, err + } + } + return total, regionMetrics, nil } func scopeMetrics(total map[string]any, regions map[int64]map[string]any, regionID int64) map[string]any { diff --git a/server/admin/internal/modules/financeorder/dto.go b/server/admin/internal/modules/financeorder/dto.go index f05d4a8d..53016374 100644 --- a/server/admin/internal/modules/financeorder/dto.go +++ b/server/admin/internal/modules/financeorder/dto.go @@ -6,7 +6,7 @@ type createCoinSellerRechargeOrderRequest struct { AppCode string `json:"appCode"` TargetUserID string `json:"targetUserId"` USDAmount float64 `json:"usdAmount"` - CoinAmount int64 `json:"coinAmount"` + IsMakeup bool `json:"isMakeup"` ProviderCode string `json:"providerCode"` Chain string `json:"chain"` ExternalOrderNo string `json:"externalOrderNo"` @@ -19,6 +19,12 @@ type createCoinSellerRechargeOrderRequest struct { Remark string `json:"remark"` } +type coinSellerRechargeQuoteRequest struct { + AppCode string `json:"appCode"` + TargetUserID string `json:"targetUserId"` + USDAmount float64 `json:"usdAmount"` +} + type verifyCoinSellerRechargeReceiptRequest struct { AppCode string `json:"appCode"` USDAmount float64 `json:"usdAmount"` diff --git a/server/admin/internal/modules/financeorder/exchange_rate.go b/server/admin/internal/modules/financeorder/exchange_rate.go new file mode 100644 index 00000000..1436486a --- /dev/null +++ b/server/admin/internal/modules/financeorder/exchange_rate.go @@ -0,0 +1,305 @@ +package financeorder + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math" + "sort" + "strconv" + "strings" + + "hyapp-admin-server/internal/appctx" + "hyapp-admin-server/internal/model" + "hyapp-admin-server/internal/modules/shared" + + "gorm.io/gorm" +) + +const ( + coinSellerRechargeExchangeRateGroup = "finance-coin-seller-recharge" + coinSellerRechargeExchangeRateKey = "exchange-rate-policy" + maxExchangeRateTiers = 100 + maxExchangeRateWhitelistEntries = 1000 +) + +type coinSellerRechargeExchangeRateTierRequest struct { + MinUSDAmount float64 `json:"minUsdAmount"` + MaxUSDAmount float64 `json:"maxUsdAmount"` + CoinsPerUSD int64 `json:"coinsPerUsd"` +} + +type coinSellerRechargeExchangeRateWhitelistRequest struct { + UserID string `json:"userId"` + CoinsPerUSD int64 `json:"coinsPerUsd"` +} + +type coinSellerRechargeExchangeRatePolicyRequest struct { + Tiers []coinSellerRechargeExchangeRateTierRequest `json:"tiers"` + Whitelist []coinSellerRechargeExchangeRateWhitelistRequest `json:"whitelist"` +} + +type coinSellerRechargeExchangeRateTierDTO struct { + MinUSDAmount string `json:"minUsdAmount"` + MaxUSDAmount string `json:"maxUsdAmount"` + CoinsPerUSD int64 `json:"coinsPerUsd"` +} + +type coinSellerRechargeExchangeRateWhitelistDTO struct { + UserID string `json:"userId"` + CoinsPerUSD int64 `json:"coinsPerUsd"` +} + +type coinSellerRechargeExchangeRatePolicyDTO struct { + AppCode string `json:"appCode"` + Tiers []coinSellerRechargeExchangeRateTierDTO `json:"tiers"` + Whitelist []coinSellerRechargeExchangeRateWhitelistDTO `json:"whitelist"` + UpdatedAtMS int64 `json:"updatedAtMs"` +} + +type coinSellerRechargeQuoteDTO struct { + AppCode string `json:"appCode"` + TargetUserID string `json:"targetUserId"` + TargetDisplayUserID string `json:"targetDisplayUserId"` + USDAmount string `json:"usdAmount"` + CoinAmount int64 `json:"coinAmount"` + CoinsPerUSD int64 `json:"coinsPerUsd"` + RateSource string `json:"rateSource"` + MatchedUserID string `json:"matchedUserId,omitempty"` + MinUSDAmount string `json:"minUsdAmount,omitempty"` + MaxUSDAmount string `json:"maxUsdAmount,omitempty"` +} + +type coinSellerRechargeExchangeRatePolicy struct { + Tiers []coinSellerRechargeExchangeRateTier `json:"tiers"` + Whitelist []coinSellerRechargeExchangeRateWhitelist `json:"whitelist"` +} + +type coinSellerRechargeExchangeRateTier struct { + MinUSDMinor int64 `json:"minUsdMinor"` + MaxUSDMinor int64 `json:"maxUsdMinor"` + CoinsPerUSD int64 `json:"coinsPerUsd"` +} + +type coinSellerRechargeExchangeRateWhitelist struct { + UserID string `json:"userId"` + CoinsPerUSD int64 `json:"coinsPerUsd"` +} + +func (s *Service) QuoteOrder(ctx context.Context, actor shared.Actor, req coinSellerRechargeQuoteRequest, requestID string) (*coinSellerRechargeQuoteDTO, error) { + if actor.UserID == 0 || !hasPermission(actor.Permissions, permissionCoinSellerRechargeCreate) { + return nil, errCoinSellerRechargeForbidden + } + appCode := appctx.Normalize(req.AppCode) + targetUserID := strings.TrimSpace(req.TargetUserID) + usdAmount, usdMinorAmount, err := normalizeUSD(req.USDAmount) + if err != nil || appCode == "" || targetUserID == "" { + return nil, errInvalidCoinSellerRechargeOrderInput + } + // 报价先解析真实币商,保证白名单同时支持运营输入的展示 ID 和账务内部 user_id,且不会给非币商生成可提交结果。 + target, err := s.resolveCoinSellerTarget(ctx, requestID, appCode, targetUserID) + if err != nil { + return nil, err + } + quote, err := s.quoteResolvedTarget(appCode, targetUserID, target, usdMinorAmount) + if err != nil { + return nil, err + } + quote.USDAmount = usdAmount + return "e, nil +} + +func (s *Service) createOrderCoinAmount(input normalizedCreateOrderInput, target coinSellerTarget) (int64, error) { + if input.IsMakeup { + // 补单仍绑定并校验真实充值凭证、记录 USD 充值统计,但明确不读取汇率、不产生金币发放;GrantOrder 会走既有 0 金币完成分支。 + return 0, nil + } + quote, err := s.quoteResolvedTarget(input.AppCode, input.TargetUserID, target, input.USDMinorAmount) + if err != nil { + return 0, err + } + return quote.CoinAmount, nil +} + +func (s *Service) GetExchangeRatePolicy(appCode string) (*coinSellerRechargeExchangeRatePolicyDTO, error) { + appCode = appctx.Normalize(appCode) + if appCode == "" { + return nil, errInvalidCoinSellerRechargeOrderInput + } + policy, updatedAtMS, err := s.loadExchangeRatePolicy(appCode) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return exchangeRatePolicyDTO(appCode, coinSellerRechargeExchangeRatePolicy{}, 0), nil + } + return nil, err + } + return exchangeRatePolicyDTO(appCode, policy, updatedAtMS), nil +} + +func (s *Service) ReplaceExchangeRatePolicy(actor shared.Actor, appCode string, req coinSellerRechargeExchangeRatePolicyRequest) (*coinSellerRechargeExchangeRatePolicyDTO, error) { + if actor.UserID == 0 || !hasPermission(actor.Permissions, permissionCoinSellerExchangeRate) { + return nil, errCoinSellerRechargeForbidden + } + appCode = appctx.Normalize(appCode) + policy, err := normalizeExchangeRatePolicyRequest(req) + if err != nil || appCode == "" { + if err != nil { + return nil, err + } + return nil, errInvalidCoinSellerRechargeOrderInput + } + value, err := json.Marshal(policy) + if err != nil { + return nil, err + } + // 配置写入 app-scoped 行,禁止一个 APP 的汇率覆盖其他 APP;订单创建会重新读取此行,不信任前端缓存的报价。 + if err := s.store.UpsertScopedAppConfigs(appCode, []model.AppConfig{{ + Group: coinSellerRechargeExchangeRateGroup, + Key: coinSellerRechargeExchangeRateKey, + Value: string(value), + Description: "币商充值 USD 金币兑换区间与用户白名单", + }}); err != nil { + return nil, err + } + return s.GetExchangeRatePolicy(appCode) +} + +func (s *Service) quoteResolvedTarget(appCode string, rawTarget string, target coinSellerTarget, usdMinorAmount int64) (coinSellerRechargeQuoteDTO, error) { + policy, _, err := s.loadExchangeRatePolicy(appCode) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return coinSellerRechargeQuoteDTO{}, errors.New("当前 APP 尚未配置币商充值金币汇率") + } + return coinSellerRechargeQuoteDTO{}, err + } + quote, err := calculateCoinSellerRechargeQuote(policy, rawTarget, target, usdMinorAmount) + if err != nil { + return coinSellerRechargeQuoteDTO{}, err + } + quote.AppCode = appCode + quote.TargetUserID = formatInt64(target.UserID) + quote.TargetDisplayUserID = target.DisplayUserID + return quote, nil +} + +func (s *Service) loadExchangeRatePolicy(appCode string) (coinSellerRechargeExchangeRatePolicy, int64, error) { + if s == nil || s.store == nil { + return coinSellerRechargeExchangeRatePolicy{}, 0, errors.New("admin store is not configured") + } + item, err := s.store.GetScopedAppConfig(appctx.Normalize(appCode), coinSellerRechargeExchangeRateGroup, coinSellerRechargeExchangeRateKey) + if err != nil { + return coinSellerRechargeExchangeRatePolicy{}, 0, err + } + var policy coinSellerRechargeExchangeRatePolicy + if err := json.Unmarshal([]byte(item.Value), &policy); err != nil { + return coinSellerRechargeExchangeRatePolicy{}, 0, errors.New("币商充值汇率配置损坏") + } + return policy, item.UpdatedAtMS, nil +} + +func normalizeExchangeRatePolicyRequest(req coinSellerRechargeExchangeRatePolicyRequest) (coinSellerRechargeExchangeRatePolicy, error) { + if len(req.Tiers) == 0 { + return coinSellerRechargeExchangeRatePolicy{}, errors.New("至少配置一个 USD 金额区间") + } + if len(req.Tiers) > maxExchangeRateTiers || len(req.Whitelist) > maxExchangeRateWhitelistEntries { + return coinSellerRechargeExchangeRatePolicy{}, errors.New("币商充值汇率配置条目过多") + } + policy := coinSellerRechargeExchangeRatePolicy{ + Tiers: make([]coinSellerRechargeExchangeRateTier, 0, len(req.Tiers)), + Whitelist: make([]coinSellerRechargeExchangeRateWhitelist, 0, len(req.Whitelist)), + } + for _, item := range req.Tiers { + _, minMinor, minErr := normalizeUSD(item.MinUSDAmount) + _, maxMinor, maxErr := normalizeUSD(item.MaxUSDAmount) + if minErr != nil || maxErr != nil || minMinor <= 0 || maxMinor < minMinor || item.CoinsPerUSD <= 0 { + return coinSellerRechargeExchangeRatePolicy{}, errors.New("USD 金额区间或金币汇率不正确") + } + policy.Tiers = append(policy.Tiers, coinSellerRechargeExchangeRateTier{MinUSDMinor: minMinor, MaxUSDMinor: maxMinor, CoinsPerUSD: item.CoinsPerUSD}) + } + sort.Slice(policy.Tiers, func(i, j int) bool { return policy.Tiers[i].MinUSDMinor < policy.Tiers[j].MinUSDMinor }) + for index := 1; index < len(policy.Tiers); index++ { + if policy.Tiers[index].MinUSDMinor <= policy.Tiers[index-1].MaxUSDMinor { + return coinSellerRechargeExchangeRatePolicy{}, errors.New("USD 金额区间不能重叠") + } + } + seen := make(map[string]struct{}, len(req.Whitelist)) + for _, item := range req.Whitelist { + userID := strings.TrimSpace(item.UserID) + if userID == "" || item.CoinsPerUSD <= 0 { + return coinSellerRechargeExchangeRatePolicy{}, errors.New("白名单用户 ID 或金币汇率不正确") + } + if _, exists := seen[userID]; exists { + return coinSellerRechargeExchangeRatePolicy{}, fmt.Errorf("白名单用户 ID 重复: %s", userID) + } + seen[userID] = struct{}{} + policy.Whitelist = append(policy.Whitelist, coinSellerRechargeExchangeRateWhitelist{UserID: userID, CoinsPerUSD: item.CoinsPerUSD}) + } + return policy, nil +} + +func calculateCoinSellerRechargeQuote(policy coinSellerRechargeExchangeRatePolicy, rawTarget string, target coinSellerTarget, usdMinorAmount int64) (coinSellerRechargeQuoteDTO, error) { + if usdMinorAmount <= 0 { + return coinSellerRechargeQuoteDTO{}, errors.New("充值美金数量不正确") + } + targetIDs := map[string]struct{}{ + strings.TrimSpace(rawTarget): {}, + strings.TrimSpace(target.DisplayUserID): {}, + formatInt64(target.UserID): {}, + } + for _, item := range policy.Whitelist { + if _, ok := targetIDs[item.UserID]; ok { + return quoteFromRate(usdMinorAmount, item.CoinsPerUSD, "whitelist", item.UserID, 0, 0) + } + } + for _, item := range policy.Tiers { + if usdMinorAmount >= item.MinUSDMinor && usdMinorAmount <= item.MaxUSDMinor { + return quoteFromRate(usdMinorAmount, item.CoinsPerUSD, "tier", "", item.MinUSDMinor, item.MaxUSDMinor) + } + } + return coinSellerRechargeQuoteDTO{}, errors.New("USD 金额未命中当前 APP 的金币兑换区间") +} + +func quoteFromRate(usdMinorAmount int64, coinsPerUSD int64, source string, matchedUserID string, minMinor int64, maxMinor int64) (coinSellerRechargeQuoteDTO, error) { + // USD 最多两位小数;金币必须为整数,因此按最接近整数四舍五入,创建订单时会用同一公式重新计算。 + if coinsPerUSD <= 0 || usdMinorAmount > (math.MaxInt64-50)/coinsPerUSD { + return coinSellerRechargeQuoteDTO{}, errors.New("计算后的充值金币数量过大") + } + coinAmount := (usdMinorAmount*coinsPerUSD + 50) / 100 + return coinSellerRechargeQuoteDTO{ + USDAmount: formatUSDMinorAmount(usdMinorAmount), + CoinAmount: coinAmount, + CoinsPerUSD: coinsPerUSD, + RateSource: source, + MatchedUserID: matchedUserID, + MinUSDAmount: optionalUSDMinorAmount(minMinor), + MaxUSDAmount: optionalUSDMinorAmount(maxMinor), + }, nil +} + +func exchangeRatePolicyDTO(appCode string, policy coinSellerRechargeExchangeRatePolicy, updatedAtMS int64) *coinSellerRechargeExchangeRatePolicyDTO { + dto := &coinSellerRechargeExchangeRatePolicyDTO{ + AppCode: appCode, + Tiers: make([]coinSellerRechargeExchangeRateTierDTO, 0, len(policy.Tiers)), + Whitelist: make([]coinSellerRechargeExchangeRateWhitelistDTO, 0, len(policy.Whitelist)), + UpdatedAtMS: updatedAtMS, + } + for _, item := range policy.Tiers { + dto.Tiers = append(dto.Tiers, coinSellerRechargeExchangeRateTierDTO{MinUSDAmount: formatUSDMinorAmount(item.MinUSDMinor), MaxUSDAmount: formatUSDMinorAmount(item.MaxUSDMinor), CoinsPerUSD: item.CoinsPerUSD}) + } + for _, item := range policy.Whitelist { + dto.Whitelist = append(dto.Whitelist, coinSellerRechargeExchangeRateWhitelistDTO{UserID: item.UserID, CoinsPerUSD: item.CoinsPerUSD}) + } + return dto +} + +func formatUSDMinorAmount(value int64) string { + return strconv.FormatInt(value/100, 10) + "." + fmt.Sprintf("%02d", value%100) +} + +func optionalUSDMinorAmount(value int64) string { + if value <= 0 { + return "" + } + return formatUSDMinorAmount(value) +} diff --git a/server/admin/internal/modules/financeorder/handler.go b/server/admin/internal/modules/financeorder/handler.go index 1de492fe..7ce85aae 100644 --- a/server/admin/internal/modules/financeorder/handler.go +++ b/server/admin/internal/modules/financeorder/handler.go @@ -78,6 +78,44 @@ func (h *Handler) CreateCoinSellerRechargeOrder(c *gin.Context) { response.Created(c, item) } +func (h *Handler) QuoteCoinSellerRechargeOrder(c *gin.Context) { + var req coinSellerRechargeQuoteRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "币商充值报价参数不正确") + return + } + item, err := h.service.QuoteOrder(c.Request.Context(), shared.ActorFromContext(c), req, middleware.CurrentRequestID(c)) + if err != nil { + writeServiceError(c, err, "生成币商充值报价失败") + return + } + response.OK(c, item) +} + +func (h *Handler) GetCoinSellerRechargeExchangeRate(c *gin.Context) { + item, err := h.service.GetExchangeRatePolicy(c.Param("app_code")) + if err != nil { + writeServiceError(c, err, "获取币商充值汇率配置失败") + return + } + response.OK(c, item) +} + +func (h *Handler) ReplaceCoinSellerRechargeExchangeRate(c *gin.Context) { + var req coinSellerRechargeExchangeRatePolicyRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "币商充值汇率配置参数不正确") + return + } + item, err := h.service.ReplaceExchangeRatePolicy(shared.ActorFromContext(c), c.Param("app_code"), req) + if err != nil { + writeServiceError(c, err, "保存币商充值汇率配置失败") + return + } + shared.OperationLogWithResourceID(c, h.audit, "replace-coin-seller-recharge-exchange-rate", "admin_app_configs", item.AppCode, "success", "tiers="+strconv.Itoa(len(item.Tiers))+",whitelist="+strconv.Itoa(len(item.Whitelist))) + response.OK(c, item) +} + func (h *Handler) VerifyCoinSellerRechargeReceipt(c *gin.Context) { var req verifyCoinSellerRechargeReceiptRequest if err := c.ShouldBindJSON(&req); err != nil { diff --git a/server/admin/internal/modules/financeorder/routes.go b/server/admin/internal/modules/financeorder/routes.go index e55f7bd6..36e0d825 100644 --- a/server/admin/internal/modules/financeorder/routes.go +++ b/server/admin/internal/modules/financeorder/routes.go @@ -12,8 +12,11 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { } protected.GET("/admin/finance/orders/coin-seller-recharges", middleware.RequirePermission(permissionCoinSellerRechargeView), h.ListCoinSellerRechargeOrders) protected.GET("/admin/finance/orders/coin-seller-recharges/:order_id", middleware.RequirePermission(permissionCoinSellerRechargeView), h.GetCoinSellerRechargeOrder) + protected.POST("/admin/finance/orders/coin-seller-recharges/quotes", middleware.RequirePermission(permissionCoinSellerRechargeCreate), h.QuoteCoinSellerRechargeOrder) protected.POST("/admin/finance/orders/coin-seller-recharges/receipt-verifications", middleware.RequirePermission(permissionCoinSellerRechargeVerify), h.VerifyCoinSellerRechargeReceipt) protected.POST("/admin/finance/orders/coin-seller-recharges", middleware.RequirePermission(permissionCoinSellerRechargeCreate), h.CreateCoinSellerRechargeOrder) protected.POST("/admin/finance/orders/coin-seller-recharges/:order_id/verify", middleware.RequirePermission(permissionCoinSellerRechargeVerify), h.VerifyCoinSellerRechargeOrder) protected.POST("/admin/finance/orders/coin-seller-recharges/:order_id/grant", middleware.RequirePermission(permissionCoinSellerRechargeGrant), h.GrantCoinSellerRechargeOrder) + protected.GET("/admin/finance/coin-seller-recharge-exchange-rates/:app_code", middleware.RequirePermission(permissionCoinSellerExchangeRate), h.GetCoinSellerRechargeExchangeRate) + protected.PUT("/admin/finance/coin-seller-recharge-exchange-rates/:app_code", middleware.RequirePermission(permissionCoinSellerExchangeRate), h.ReplaceCoinSellerRechargeExchangeRate) } diff --git a/server/admin/internal/modules/financeorder/service.go b/server/admin/internal/modules/financeorder/service.go index cfc165ab..0619f026 100644 --- a/server/admin/internal/modules/financeorder/service.go +++ b/server/admin/internal/modules/financeorder/service.go @@ -29,6 +29,7 @@ const ( permissionCoinSellerRechargeCreate = "finance-order:coin-seller-recharge:create" permissionCoinSellerRechargeVerify = "finance-order:coin-seller-recharge:verify" permissionCoinSellerRechargeGrant = "finance-order:coin-seller-recharge:grant" + permissionCoinSellerExchangeRate = "coin-seller:exchange-rate" coinSellerRechargeAssetType = "COIN_SELLER_COIN" coinSellerRechargeStockType = "usdt_purchase" @@ -106,6 +107,21 @@ func (s *Service) CreateOrder(ctx context.Context, actor shared.Actor, req creat if err != nil { return nil, err } + nowMS := s.now().UnixMilli() + input.OrderDateMS, err = normalizeCreateOrderBusinessTime(input.OrderDateMS, nowMS) + if err != nil { + return nil, err + } + // 金币数只由当前 APP 汇率配置生成;即使调用方绕过前端直接提交 coinAmount,也不会进入请求结构或账务结果。 + target, err := s.resolveCoinSellerTarget(ctx, requestID, input.AppCode, input.TargetUserID) + if err != nil { + return nil, err + } + coinAmount, err := s.createOrderCoinAmount(input, target) + if err != nil { + return nil, err + } + input.CoinAmount = coinAmount // 前端“校验通过”只能改善操作体验,不能作为资金事实;创建时必须复用同一套服务端校验,避免篡改请求直接落库。 receipt, err := s.verifyReceiptInput(ctx, input.receiptVerificationInput(), strings.TrimSpace(requestID)) if err != nil { @@ -117,12 +133,6 @@ func (s *Service) CreateOrder(ctx context.Context, actor shared.Actor, req creat if err := verifyReceiptMatchesInput(input, receipt); err != nil { return nil, err } - // 钱包入账对象必须在订单占用前确认身份;Lalu 查 user-service,Aslan/Yumi 走 legacy writer,避免跨账套写错目标。 - target, err := s.resolveCoinSellerTarget(ctx, requestID, input.AppCode, input.TargetUserID) - if err != nil { - return nil, err - } - nowMS := s.now().UnixMilli() verifiedAtMS := receipt.VerifiedAtMS if verifiedAtMS <= 0 { verifiedAtMS = nowMS @@ -347,6 +357,7 @@ type normalizedCreateOrderInput struct { USDAmount string USDMinorAmount int64 CoinAmount int64 + IsMakeup bool ProviderCode string Chain string ExternalOrderNo string @@ -398,7 +409,7 @@ func normalizeCreateOrderRequest(req createCoinSellerRechargeOrderRequest) (norm return normalizedCreateOrderInput{}, err } externalOrderNo := strings.TrimSpace(req.ExternalOrderNo) - if appCode == "" || targetUserID == "" || externalOrderNo == "" || req.CoinAmount < 0 { + if appCode == "" || targetUserID == "" || externalOrderNo == "" { return normalizedCreateOrderInput{}, errInvalidCoinSellerRechargeOrderInput } if len(externalOrderNo) > 128 { @@ -437,7 +448,8 @@ func normalizeCreateOrderRequest(req createCoinSellerRechargeOrderRequest) (norm TargetUserID: targetUserID, USDAmount: usdAmount, USDMinorAmount: usdMinorAmount, - CoinAmount: req.CoinAmount, + CoinAmount: 0, + IsMakeup: req.IsMakeup, ProviderCode: providerCode, Chain: chain, ExternalOrderNo: externalOrderNo, @@ -927,6 +939,19 @@ func normalizeOrderDateMS(orderDate string, orderDateMS int64) (int64, error) { return 0, errors.New("订单日期不正确") } +func normalizeCreateOrderBusinessTime(orderDateMS int64, nowMS int64) (int64, error) { + if nowMS <= 0 { + return 0, errors.New("当前时间不正确") + } + if orderDateMS <= 0 { + return nowMS, nil + } + if orderDateMS > nowMS { + return 0, errors.New("订单时间不能晚于当前时间") + } + return orderDateMS, nil +} + func requiresLegacyWriter(appCode string) bool { switch appctx.Normalize(appCode) { case "aslan", "yumi": diff --git a/server/admin/internal/modules/financeorder/service_test.go b/server/admin/internal/modules/financeorder/service_test.go index 230703d3..f30fe9d1 100644 --- a/server/admin/internal/modules/financeorder/service_test.go +++ b/server/admin/internal/modules/financeorder/service_test.go @@ -15,7 +15,6 @@ func TestNormalizeCreateOrderRequiresExplicitUSDTChain(t *testing.T) { AppCode: "lalu", TargetUserID: "10001", USDAmount: 12.34, - CoinAmount: 1200, ProviderCode: "usdt", ExternalOrderNo: "0xabc", }) @@ -27,7 +26,6 @@ func TestNormalizeCreateOrderRequiresExplicitUSDTChain(t *testing.T) { AppCode: "lalu", TargetUserID: "10001", USDAmount: 12.34, - CoinAmount: 1200, ProviderCode: "usdt", Chain: "bsc", ExternalOrderNo: "0xabc", @@ -46,7 +44,6 @@ func TestNormalizeCreateOrderRequiresProviderCurrencyAmount(t *testing.T) { AppCode: "lalu", TargetUserID: "10001", USDAmount: 12.34, - CoinAmount: 1200, ProviderCode: providerCode, ExternalOrderNo: providerCode + "-order", }) @@ -58,7 +55,6 @@ func TestNormalizeCreateOrderRequiresProviderCurrencyAmount(t *testing.T) { AppCode: "lalu", TargetUserID: "10001", USDAmount: 12.34, - CoinAmount: 1200, ProviderCode: providerCode, ExternalOrderNo: providerCode + "-order", ProviderCurrencyCode: " php ", @@ -106,12 +102,24 @@ func TestNormalizeReceiptVerificationCarriesProviderCurrencyAmount(t *testing.T) } } -func TestNormalizeCreateOrderAllowsZeroCoinAmount(t *testing.T) { +func TestNormalizeCreateOrderBusinessTimeDefaultsToNowAndRejectsFuture(t *testing.T) { + const nowMS = int64(1_783_900_000_000) + if got, err := normalizeCreateOrderBusinessTime(0, nowMS); err != nil || got != nowMS { + t.Fatalf("missing order time should default to now: got=%d err=%v", got, err) + } + if got, err := normalizeCreateOrderBusinessTime(nowMS-86_400_000, nowMS); err != nil || got != nowMS-86_400_000 { + t.Fatalf("past business time should be preserved: got=%d err=%v", got, err) + } + if _, err := normalizeCreateOrderBusinessTime(nowMS+1, nowMS); err == nil || !strings.Contains(err.Error(), "不能晚于") { + t.Fatalf("future business time should be rejected, got %v", err) + } +} + +func TestNormalizeCreateOrderLeavesCoinAmountForServerQuote(t *testing.T) { input, err := normalizeCreateOrderRequest(createCoinSellerRechargeOrderRequest{ AppCode: "lalu", TargetUserID: "10001", USDAmount: 12.34, - CoinAmount: 0, ProviderCode: "mifapay", ExternalOrderNo: "MIFA-001", ProviderCurrencyCode: "PHP", @@ -119,10 +127,89 @@ func TestNormalizeCreateOrderAllowsZeroCoinAmount(t *testing.T) { Remark: " 补单,不发金币 ", }) if err != nil { - t.Fatalf("zero coin makeup order should be accepted: %v", err) + t.Fatalf("create order input should be accepted before server quote: %v", err) } if input.CoinAmount != 0 || input.USDMinorAmount != 1234 || input.Remark != "补单,不发金币" { - t.Fatalf("normalized zero coin order mismatch: %+v", input) + t.Fatalf("normalized order should not contain a client-controlled coin amount: %+v", input) + } +} + +func TestNormalizeCreateOrderCarriesExplicitMakeupState(t *testing.T) { + input, err := normalizeCreateOrderRequest(createCoinSellerRechargeOrderRequest{ + AppCode: "lalu", + TargetUserID: "10001", + USDAmount: 12.34, + IsMakeup: true, + ProviderCode: "mifapay", + ExternalOrderNo: "MIFA-MAKEUP-001", + ProviderCurrencyCode: "PHP", + ProviderAmountMinor: 1234, + }) + if err != nil { + t.Fatalf("normalize makeup order failed: %v", err) + } + if !input.IsMakeup || input.CoinAmount != 0 { + t.Fatalf("makeup state must force the later server branch to keep zero coins: %+v", input) + } +} + +func TestCreateOrderMakeupBypassesExchangeRateAndForcesZeroCoins(t *testing.T) { + service := NewService(nil, nil, nil) + coinAmount, err := service.createOrderCoinAmount(normalizedCreateOrderInput{ + AppCode: "lalu", + TargetUserID: "123456", + USDMinorAmount: 15000, + IsMakeup: true, + }, coinSellerTarget{UserID: 9988, DisplayUserID: "123456"}) + if err != nil || coinAmount != 0 { + t.Fatalf("makeup must not require an exchange-rate config and must force zero coins: amount=%d err=%v", coinAmount, err) + } +} + +func TestCoinSellerRechargeQuotePrefersWhitelistOverAmountTier(t *testing.T) { + policy := coinSellerRechargeExchangeRatePolicy{ + Tiers: []coinSellerRechargeExchangeRateTier{ + {MinUSDMinor: 10000, MaxUSDMinor: 20000, CoinsPerUSD: 80000}, + {MinUSDMinor: 20100, MaxUSDMinor: 30000, CoinsPerUSD: 90000}, + }, + Whitelist: []coinSellerRechargeExchangeRateWhitelist{{UserID: "123456", CoinsPerUSD: 100000}}, + } + + quote, err := calculateCoinSellerRechargeQuote(policy, "123456", coinSellerTarget{UserID: 9988, DisplayUserID: "123456"}, 15000) + if err != nil { + t.Fatalf("calculate whitelist quote failed: %v", err) + } + if quote.RateSource != "whitelist" || quote.CoinsPerUSD != 100000 || quote.CoinAmount != 15000000 || quote.MatchedUserID != "123456" { + t.Fatalf("whitelist quote mismatch: %+v", quote) + } + + tierQuote, err := calculateCoinSellerRechargeQuote(policy, "654321", coinSellerTarget{UserID: 7766, DisplayUserID: "654321"}, 25000) + if err != nil { + t.Fatalf("calculate tier quote failed: %v", err) + } + if tierQuote.RateSource != "tier" || tierQuote.CoinsPerUSD != 90000 || tierQuote.CoinAmount != 22500000 || tierQuote.MinUSDAmount != "201.00" || tierQuote.MaxUSDAmount != "300.00" { + t.Fatalf("tier quote mismatch: %+v", tierQuote) + } +} + +func TestNormalizeCoinSellerRechargeExchangeRateRejectsOverlapsAndDuplicateWhitelist(t *testing.T) { + _, err := normalizeExchangeRatePolicyRequest(coinSellerRechargeExchangeRatePolicyRequest{Tiers: []coinSellerRechargeExchangeRateTierRequest{ + {MinUSDAmount: 100, MaxUSDAmount: 200, CoinsPerUSD: 80000}, + {MinUSDAmount: 200, MaxUSDAmount: 300, CoinsPerUSD: 90000}, + }}) + if err == nil || !strings.Contains(err.Error(), "不能重叠") { + t.Fatalf("expected overlap validation error, got %v", err) + } + + _, err = normalizeExchangeRatePolicyRequest(coinSellerRechargeExchangeRatePolicyRequest{ + Tiers: []coinSellerRechargeExchangeRateTierRequest{{MinUSDAmount: 1, MaxUSDAmount: 10, CoinsPerUSD: 80000}}, + Whitelist: []coinSellerRechargeExchangeRateWhitelistRequest{ + {UserID: "123456", CoinsPerUSD: 100000}, + {UserID: "123456", CoinsPerUSD: 110000}, + }, + }) + if err == nil || !strings.Contains(err.Error(), "重复") { + t.Fatalf("expected duplicate whitelist validation error, got %v", err) } } diff --git a/server/admin/internal/modules/financewithdrawal/response.go b/server/admin/internal/modules/financewithdrawal/response.go index c03999bf..cae15c08 100644 --- a/server/admin/internal/modules/financewithdrawal/response.go +++ b/server/admin/internal/modules/financewithdrawal/response.go @@ -15,6 +15,7 @@ type withdrawalApplicationDTO struct { PointNetAmount int64 `json:"pointNetAmount,omitempty"` PointsPerUSD int64 `json:"pointsPerUsd,omitempty"` PointFeeBPS int32 `json:"pointFeeBps,omitempty"` + PointPolicyInstance string `json:"pointPolicyInstanceCode,omitempty"` WithdrawMethod string `json:"withdrawMethod"` WithdrawAddress string `json:"withdrawAddress"` FreezeTransactionID string `json:"freezeTransactionId"` @@ -52,17 +53,35 @@ func withdrawalApplicationDTOFromModel(item model.UserWithdrawalApplication) wit UpdatedAtMS: item.UpdatedAtMS, } if isPointWithdrawalAssetType(item.SalaryAssetType) { - grossPoints := item.WithdrawAmountMinor - feePoints := grossPoints * int64(pointWithdrawalDefaultFeeBPS) / 10000 - dto.PointGrossAmount = grossPoints + feePoints, netPoints, pointsPerUSD, feeBPS := pointWithdrawalSnapshot(item) + dto.PointGrossAmount = item.WithdrawAmountMinor dto.PointFeeAmount = feePoints - dto.PointNetAmount = grossPoints - feePoints - dto.PointsPerUSD = pointWithdrawalDefaultPointsPerUSD - dto.PointFeeBPS = pointWithdrawalDefaultFeeBPS + dto.PointNetAmount = netPoints + dto.PointsPerUSD = pointsPerUSD + dto.PointFeeBPS = feeBPS + dto.PointPolicyInstance = item.PointPolicyInstance } return dto } +func pointWithdrawalSnapshot(item model.UserWithdrawalApplication) (feePoints int64, netPoints int64, pointsPerUSD int64, feeBPS int32) { + pointsPerUSD = item.PointsPerUSD + if pointsPerUSD <= 0 { + pointsPerUSD = pointWithdrawalDefaultPointsPerUSD + } + feeBPS = item.PointFeeBPS + if feeBPS < 0 || feeBPS > 10000 { + feeBPS = pointWithdrawalDefaultFeeBPS + } + feePoints, netPoints = item.PointFeeAmount, item.PointNetAmount + if feePoints < 0 || netPoints < 0 || feePoints+netPoints != item.WithdrawAmountMinor { + // 090 之前的存量 POINT 申请没有快照;只对这些不一致旧行按历史 5% 默认回填。 + feePoints = item.WithdrawAmountMinor * int64(feeBPS) / 10000 + netPoints = item.WithdrawAmountMinor - feePoints + } + return +} + func withdrawalApplicationDTOsFromModel(items []model.UserWithdrawalApplication) []withdrawalApplicationDTO { out := make([]withdrawalApplicationDTO, 0, len(items)) for _, item := range items { diff --git a/server/admin/internal/modules/financewithdrawal/service.go b/server/admin/internal/modules/financewithdrawal/service.go index 3cf2c10d..b41ebf42 100644 --- a/server/admin/internal/modules/financewithdrawal/service.go +++ b/server/admin/internal/modules/financewithdrawal/service.go @@ -92,7 +92,7 @@ func (s *Service) auditApplication(ctx context.Context, actor shared.Actor, id u } commandID := withdrawalAuditCommandID(id, decision) amountMinor := item.WithdrawAmountMinor - transactionID, err := s.applyWalletDecision(ctx, decision, commandID, appCode, userID, item.SalaryAssetType, amountMinor, actor, strings.TrimSpace(remark), id) + transactionID, err := s.applyWalletDecision(ctx, decision, commandID, appCode, userID, item.SalaryAssetType, amountMinor, item.PointFeeAmount, item.PointNetAmount, item.PointsPerUSD, item.PointFeeBPS, actor, strings.TrimSpace(remark), id) if err != nil { return nil, err } @@ -130,7 +130,7 @@ func (s *Service) auditApplication(ctx context.Context, actor shared.Actor, id u return &dto, nil } -func (s *Service) applyWalletDecision(ctx context.Context, decision string, commandID string, appCode string, userID int64, assetType string, amountMinor int64, actor shared.Actor, remark string, applicationID uint) (string, error) { +func (s *Service) applyWalletDecision(ctx context.Context, decision string, commandID string, appCode string, userID int64, assetType string, amountMinor int64, storedFeePoints int64, storedNetPoints int64, storedPointsPerUSD int64, storedFeeBPS int32, actor shared.Actor, remark string, applicationID uint) (string, error) { reason := strings.TrimSpace(remark) if reason == "" { reason = "salary withdrawal " + decision @@ -141,8 +141,10 @@ func (s *Service) applyWalletDecision(ctx context.Context, decision string, comm if !ok { return "", errors.New("point withdrawal wallet executor is not configured") } - feePoints := amountMinor * int64(pointWithdrawalDefaultFeeBPS) / 10000 - netPoints := amountMinor - feePoints + feePoints, netPoints, pointsPerUSD, feeBPS := pointWithdrawalSnapshot(model.UserWithdrawalApplication{ + WithdrawAmountMinor: amountMinor, PointFeeAmount: storedFeePoints, PointNetAmount: storedNetPoints, + PointsPerUSD: storedPointsPerUSD, PointFeeBPS: storedFeeBPS, + }) switch decision { case model.WithdrawalApplicationStatusApproved: resp, err := pointWallet.SettlePointWithdrawal(ctx, &walletv1.SettlePointWithdrawalRequest{ @@ -152,8 +154,8 @@ func (s *Service) applyWalletDecision(ctx context.Context, decision string, comm GrossPointAmount: amountMinor, FeePointAmount: feePoints, NetPointAmount: netPoints, - PointsPerUsd: pointWithdrawalDefaultPointsPerUSD, - FeeBps: pointWithdrawalDefaultFeeBPS, + PointsPerUsd: pointsPerUSD, + FeeBps: feeBPS, OperatorUserId: int64(actor.UserID), Reason: reason, AppCode: appCode, @@ -171,8 +173,8 @@ func (s *Service) applyWalletDecision(ctx context.Context, decision string, comm GrossPointAmount: amountMinor, FeePointAmount: feePoints, NetPointAmount: netPoints, - PointsPerUsd: pointWithdrawalDefaultPointsPerUSD, - FeeBps: pointWithdrawalDefaultFeeBPS, + PointsPerUsd: pointsPerUSD, + FeeBps: feeBPS, OperatorUserId: int64(actor.UserID), Reason: reason, AppCode: appCode, diff --git a/server/admin/internal/modules/financewithdrawal/service_test.go b/server/admin/internal/modules/financewithdrawal/service_test.go index af644bb8..30c95036 100644 --- a/server/admin/internal/modules/financewithdrawal/service_test.go +++ b/server/admin/internal/modules/financewithdrawal/service_test.go @@ -14,7 +14,7 @@ func TestApplyWalletDecisionRoutesPointApprovalToPointSettlement(t *testing.T) { wallet := &fakeAuditWalletClient{} svc := &Service{wallet: wallet} - transactionID, err := svc.applyWalletDecision(context.Background(), model.WithdrawalApplicationStatusApproved, "audit:77:approved", "huwaa", 42001, "POINT", 1_000_000, shared.Actor{UserID: 90001}, "paid", 77) + transactionID, err := svc.applyWalletDecision(context.Background(), model.WithdrawalApplicationStatusApproved, "audit:77:approved", "fami", 42001, "POINT", 1_000_000, 25_000, 975_000, 200_000, 250, shared.Actor{UserID: 90001}, "paid", 77) if err != nil { t.Fatalf("apply point approval failed: %v", err) } @@ -23,12 +23,14 @@ func TestApplyWalletDecisionRoutesPointApprovalToPointSettlement(t *testing.T) { } if wallet.settlePoint == nil || wallet.settlePoint.GetCommandId() != "audit:77:approved" || - wallet.settlePoint.GetAppCode() != "huwaa" || + wallet.settlePoint.GetAppCode() != "fami" || wallet.settlePoint.GetUserId() != 42001 || wallet.settlePoint.GetAssetType() != "POINT" || wallet.settlePoint.GetGrossPointAmount() != 1_000_000 || - wallet.settlePoint.GetFeePointAmount() != 50_000 || - wallet.settlePoint.GetNetPointAmount() != 950_000 || + wallet.settlePoint.GetFeePointAmount() != 25_000 || + wallet.settlePoint.GetNetPointAmount() != 975_000 || + wallet.settlePoint.GetPointsPerUsd() != 200_000 || + wallet.settlePoint.GetFeeBps() != 250 || wallet.settlePoint.GetOperatorUserId() != 90001 || wallet.settlePoint.GetWithdrawalApplicationId() != "77" { t.Fatalf("SettlePointWithdrawal payload mismatch: %+v", wallet.settlePoint) @@ -42,7 +44,7 @@ func TestApplyWalletDecisionRoutesPointRejectionToPointRelease(t *testing.T) { wallet := &fakeAuditWalletClient{} svc := &Service{wallet: wallet} - transactionID, err := svc.applyWalletDecision(context.Background(), model.WithdrawalApplicationStatusRejected, "audit:78:rejected", "huwaa", 42002, "COIN_SELLER_POINT", 2_000_000, shared.Actor{UserID: 90002}, "bad address", 78) + transactionID, err := svc.applyWalletDecision(context.Background(), model.WithdrawalApplicationStatusRejected, "audit:78:rejected", "huwaa", 42002, "COIN_SELLER_POINT", 2_000_000, 100_000, 1_900_000, 100_000, 500, shared.Actor{UserID: 90002}, "bad address", 78) if err != nil { t.Fatalf("apply point rejection failed: %v", err) } @@ -71,7 +73,7 @@ func TestApplyWalletDecisionKeepsLegacySalaryAssetsOnSalaryRPC(t *testing.T) { wallet := &fakeAuditWalletClient{} svc := &Service{wallet: wallet} - transactionID, err := svc.applyWalletDecision(context.Background(), model.WithdrawalApplicationStatusApproved, "audit:79:approved", "lalu", 42003, "HOST_SALARY_USD", 12_345, shared.Actor{UserID: 90003}, "", 79) + transactionID, err := svc.applyWalletDecision(context.Background(), model.WithdrawalApplicationStatusApproved, "audit:79:approved", "lalu", 42003, "HOST_SALARY_USD", 12_345, 0, 0, 0, 0, shared.Actor{UserID: 90003}, "", 79) if err != nil { t.Fatalf("apply salary approval failed: %v", err) } diff --git a/server/admin/internal/modules/hostorg/handler.go b/server/admin/internal/modules/hostorg/handler.go index ee118eed..aa5182ae 100644 --- a/server/admin/internal/modules/hostorg/handler.go +++ b/server/admin/internal/modules/hostorg/handler.go @@ -68,12 +68,15 @@ func (h *Handler) ListManagers(c *gin.Context) { if !ok { return } - items, total, err := h.service.ListManagers(c.Request.Context(), query) + items, total, policy, err := h.service.ListManagers(c.Request.Context(), query) if err != nil { response.ServerError(c, "获取经理列表失败") return } - response.OK(c, response.Page{Items: items, Page: query.Page, PageSize: query.PageSize, Total: total}) + response.OK(c, ManagerPageResponse{ + Items: items, Page: query.Page, PageSize: query.PageSize, Total: total, + ManagerOperationScopePolicy: policy, + }) } func (h *Handler) CreateManager(c *gin.Context) { diff --git a/server/admin/internal/modules/hostorg/reader.go b/server/admin/internal/modules/hostorg/reader.go index f31f55c7..a11e288b 100644 --- a/server/admin/internal/modules/hostorg/reader.go +++ b/server/admin/internal/modules/hostorg/reader.go @@ -68,28 +68,47 @@ type UserIdentity struct { } type ManagerListItem struct { - UserID int64 `json:"userId,string"` - DisplayUserID string `json:"displayUserId"` - Username string `json:"username"` - Avatar string `json:"avatar"` - RegionID int64 `json:"regionId"` - RegionName string `json:"regionName"` - Status string `json:"status"` - Contact string `json:"contact"` - CanGrantAvatarFrame bool `json:"canGrantAvatarFrame"` - CanGrantVehicle bool `json:"canGrantVehicle"` - CanGrantBadge bool `json:"canGrantBadge"` - CanGrantVIP bool `json:"canGrantVip"` - CanUpdateUserLevel bool `json:"canUpdateUserLevel"` - CanAddBDLeader bool `json:"canAddBdLeader"` - CanAddAdmin bool `json:"canAddAdmin"` - CanAddSuperadmin bool `json:"canAddSuperadmin"` - CanBlockUser bool `json:"canBlockUser"` - CanTransferCountry bool `json:"canTransferUserCountry"` - BDLeaderCount int64 `json:"bdLeaderCount"` - LastInvitedAtMs int64 `json:"lastInvitedAtMs"` - CreatedAtMs int64 `json:"createdAtMs"` - UpdatedAtMs int64 `json:"updatedAtMs"` + UserID int64 `json:"userId,string"` + DisplayUserID string `json:"displayUserId"` + Username string `json:"username"` + Avatar string `json:"avatar"` + RegionID int64 `json:"regionId"` + RegionName string `json:"regionName"` + Status string `json:"status"` + Contact string `json:"contact"` + CanGrantAvatarFrame bool `json:"canGrantAvatarFrame"` + CanGrantVehicle bool `json:"canGrantVehicle"` + CanGrantBadge bool `json:"canGrantBadge"` + CanGrantVIP bool `json:"canGrantVip"` + CanUpdateUserLevel bool `json:"canUpdateUserLevel"` + CanAddBDLeader bool `json:"canAddBdLeader"` + CanManageCrossRegion bool `json:"canManageCrossRegion"` + LegacyBDLeaderInRegion bool `json:"canAddBdLeaderInRegion"` + ManagerOperationBaseScope string `json:"managerOperationBaseScope"` + ManagerOperationEffectiveScope string `json:"managerOperationEffectiveScope"` + ManagerCrossRegionConfigurable bool `json:"managerCrossRegionConfigurable"` + CanAddAdmin bool `json:"canAddAdmin"` + CanAddSuperadmin bool `json:"canAddSuperadmin"` + CanBlockUser bool `json:"canBlockUser"` + CanTransferCountry bool `json:"canTransferUserCountry"` + BDLeaderCount int64 `json:"bdLeaderCount"` + LastInvitedAtMs int64 `json:"lastInvitedAtMs"` + CreatedAtMs int64 `json:"createdAtMs"` + UpdatedAtMs int64 `json:"updatedAtMs"` +} + +type ManagerRoleScopePolicy struct { + BaseScope string `json:"baseScope"` + ExpandedScope string `json:"expandedScope"` + RegionExpansionConfigurable bool `json:"regionExpansionConfigurable"` +} + +type ManagerPageResponse struct { + Items []*ManagerListItem `json:"items"` + Page int `json:"page"` + PageSize int `json:"pageSize"` + Total int64 `json:"total"` + ManagerOperationScopePolicy ManagerRoleScopePolicy `json:"managerOperationScopePolicy"` } type CoinSellerSalaryRateTier struct { @@ -645,9 +664,9 @@ func (r *Reader) CreateManagerProfile(ctx context.Context, userID int64, actorID if _, err := r.db.ExecContext(ctx, ` INSERT INTO manager_profiles ( app_code, user_id, status, contact_info, can_grant_avatar_frame, can_grant_vehicle, can_grant_badge, - can_grant_vip, can_update_user_level, can_add_bd_leader, can_add_admin, can_add_superadmin, can_block_user, can_transfer_user_country, + can_grant_vip, can_update_user_level, can_add_bd_leader, can_manage_cross_region, can_add_admin, can_add_superadmin, can_block_user, can_transfer_user_country, created_by_admin_id, created_at_ms, updated_at_ms - ) VALUES (?, ?, 'active', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ) VALUES (?, ?, 'active', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE status = 'active', contact_info = VALUES(contact_info), @@ -657,13 +676,14 @@ func (r *Reader) CreateManagerProfile(ctx context.Context, userID int64, actorID can_grant_vip = VALUES(can_grant_vip), can_update_user_level = VALUES(can_update_user_level), can_add_bd_leader = VALUES(can_add_bd_leader), + can_manage_cross_region = VALUES(can_manage_cross_region), can_add_admin = VALUES(can_add_admin), can_add_superadmin = VALUES(can_add_superadmin), can_block_user = VALUES(can_block_user), can_transfer_user_country = VALUES(can_transfer_user_country), updated_at_ms = VALUES(updated_at_ms) `, appCode, userID, contact, permissions.CanGrantAvatarFrame, permissions.CanGrantVehicle, - permissions.CanGrantBadge, permissions.CanGrantVIP, permissions.CanUpdateUserLevel, permissions.CanAddBDLeader, permissions.CanAddAdmin, + permissions.CanGrantBadge, permissions.CanGrantVIP, permissions.CanUpdateUserLevel, permissions.CanAddBDLeader, permissions.CanManageCrossRegion, permissions.CanAddAdmin, permissions.CanAddSuperadmin, permissions.CanBlockUser, permissions.CanTransferCountry, actorID, nowMs, nowMs); err != nil { return nil, err } @@ -771,14 +791,14 @@ func (r *Reader) ListManagers(ctx context.Context, query listQuery) ([]*ManagerL COALESCE(manager.avatar, ''), `+userCountryRegionIDSQL("manager_region")+`, `+userCountryRegionNameSQL("manager_region")+`, mp.status, COALESCE(mp.contact_info, ''), mp.can_grant_avatar_frame, mp.can_grant_vehicle, mp.can_grant_badge, mp.can_grant_vip, mp.can_update_user_level, - mp.can_add_bd_leader, mp.can_add_admin, mp.can_add_superadmin, mp.can_block_user, mp.can_transfer_user_country, + mp.can_add_bd_leader, mp.can_manage_cross_region, mp.can_add_admin, mp.can_add_superadmin, mp.can_block_user, mp.can_transfer_user_country, COUNT(DISTINCT bl.user_id), COALESCE(MAX(bl.created_at_ms), 0), mp.created_at_ms, mp.updated_at_ms %s GROUP BY mp.user_id, manager.current_display_user_id, manager.username, manager.avatar, manager_region.region_id, manager_region.name, mp.status, mp.contact_info, mp.can_grant_avatar_frame, mp.can_grant_vehicle, mp.can_grant_badge, mp.can_grant_vip, mp.can_update_user_level, - mp.can_add_bd_leader, mp.can_add_admin, mp.can_add_superadmin, mp.can_block_user, mp.can_transfer_user_country, + mp.can_add_bd_leader, mp.can_manage_cross_region, mp.can_add_admin, mp.can_add_superadmin, mp.can_block_user, mp.can_transfer_user_country, mp.created_at_ms, mp.updated_at_ms ORDER BY mp.updated_at_ms DESC, mp.user_id DESC LIMIT ? OFFSET ? @@ -806,6 +826,7 @@ func (r *Reader) ListManagers(ctx context.Context, query listQuery) ([]*ManagerL &item.CanGrantVIP, &item.CanUpdateUserLevel, &item.CanAddBDLeader, + &item.CanManageCrossRegion, &item.CanAddAdmin, &item.CanAddSuperadmin, &item.CanBlockUser, diff --git a/server/admin/internal/modules/hostorg/reader_test.go b/server/admin/internal/modules/hostorg/reader_test.go index 4d767c49..447dbb67 100644 --- a/server/admin/internal/modules/hostorg/reader_test.go +++ b/server/admin/internal/modules/hostorg/reader_test.go @@ -29,7 +29,7 @@ func TestUpdateManagerProfilePersistsStatus(t *testing.T) { mock.ExpectQuery(`(?s)SELECT mp\.user_id, COALESCE\(manager\.current_display_user_id, ''\).*FROM manager_profiles mp`). WillReturnRows(managerListRows(). AddRow(int64(1003), "1003", "manager", "avatar.png", int64(7), "西亚区", "disabled", "+63", - true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, false, true, true, true, true, int64(0), int64(0), int64(1710000000000), int64(1710000001000))) item, err := reader.UpdateManagerProfile(ctx, 1003, 7, updateManagerRequest{Status: &status}) @@ -75,6 +75,7 @@ func managerListRows() *sqlmock.Rows { "can_grant_vip", "can_update_user_level", "can_add_bd_leader", + "can_manage_cross_region", "can_add_admin", "can_add_superadmin", "can_block_user", diff --git a/server/admin/internal/modules/hostorg/request.go b/server/admin/internal/modules/hostorg/request.go index b6718970..4eecd414 100644 --- a/server/admin/internal/modules/hostorg/request.go +++ b/server/admin/internal/modules/hostorg/request.go @@ -69,33 +69,37 @@ type createBDLeaderRequest struct { } type createManagerRequest struct { - Contact string `json:"contact"` - TargetUserID flexibleInt64 `json:"targetUserId" binding:"required"` - CanGrantAvatarFrame *bool `json:"canGrantAvatarFrame"` - CanGrantVehicle *bool `json:"canGrantVehicle"` - CanGrantBadge *bool `json:"canGrantBadge"` - CanGrantVIP *bool `json:"canGrantVip"` - CanUpdateUserLevel *bool `json:"canUpdateUserLevel"` - CanAddBDLeader *bool `json:"canAddBdLeader"` - CanAddAdmin *bool `json:"canAddAdmin"` - CanAddSuperadmin *bool `json:"canAddSuperadmin"` - CanBlockUser *bool `json:"canBlockUser"` - CanTransferCountry *bool `json:"canTransferUserCountry"` + Contact string `json:"contact"` + TargetUserID flexibleInt64 `json:"targetUserId" binding:"required"` + CanGrantAvatarFrame *bool `json:"canGrantAvatarFrame"` + CanGrantVehicle *bool `json:"canGrantVehicle"` + CanGrantBadge *bool `json:"canGrantBadge"` + CanGrantVIP *bool `json:"canGrantVip"` + CanUpdateUserLevel *bool `json:"canUpdateUserLevel"` + CanAddBDLeader *bool `json:"canAddBdLeader"` + CanManageCrossRegion *bool `json:"canManageCrossRegion"` + LegacyBDLeaderInRegion *bool `json:"canAddBdLeaderInRegion"` + CanAddAdmin *bool `json:"canAddAdmin"` + CanAddSuperadmin *bool `json:"canAddSuperadmin"` + CanBlockUser *bool `json:"canBlockUser"` + CanTransferCountry *bool `json:"canTransferUserCountry"` } type updateManagerRequest struct { - Contact *string `json:"contact"` - Status *string `json:"status"` - CanGrantAvatarFrame *bool `json:"canGrantAvatarFrame"` - CanGrantVehicle *bool `json:"canGrantVehicle"` - CanGrantBadge *bool `json:"canGrantBadge"` - CanGrantVIP *bool `json:"canGrantVip"` - CanUpdateUserLevel *bool `json:"canUpdateUserLevel"` - CanAddBDLeader *bool `json:"canAddBdLeader"` - CanAddAdmin *bool `json:"canAddAdmin"` - CanAddSuperadmin *bool `json:"canAddSuperadmin"` - CanBlockUser *bool `json:"canBlockUser"` - CanTransferCountry *bool `json:"canTransferUserCountry"` + Contact *string `json:"contact"` + Status *string `json:"status"` + CanGrantAvatarFrame *bool `json:"canGrantAvatarFrame"` + CanGrantVehicle *bool `json:"canGrantVehicle"` + CanGrantBadge *bool `json:"canGrantBadge"` + CanGrantVIP *bool `json:"canGrantVip"` + CanUpdateUserLevel *bool `json:"canUpdateUserLevel"` + CanAddBDLeader *bool `json:"canAddBdLeader"` + CanManageCrossRegion *bool `json:"canManageCrossRegion"` + LegacyBDLeaderInRegion *bool `json:"canAddBdLeaderInRegion"` + CanAddAdmin *bool `json:"canAddAdmin"` + CanAddSuperadmin *bool `json:"canAddSuperadmin"` + CanBlockUser *bool `json:"canBlockUser"` + CanTransferCountry *bool `json:"canTransferUserCountry"` } func (r updateManagerRequest) appendStatusAssignment(assignments *[]string, args *[]any) error { @@ -116,30 +120,32 @@ func (r updateManagerRequest) appendStatusAssignment(assignments *[]string, args } type managerPermissions struct { - CanGrantAvatarFrame bool - CanGrantVehicle bool - CanGrantBadge bool - CanGrantVIP bool - CanUpdateUserLevel bool - CanAddBDLeader bool - CanAddAdmin bool - CanAddSuperadmin bool - CanBlockUser bool - CanTransferCountry bool + CanGrantAvatarFrame bool + CanGrantVehicle bool + CanGrantBadge bool + CanGrantVIP bool + CanUpdateUserLevel bool + CanAddBDLeader bool + CanManageCrossRegion bool + CanAddAdmin bool + CanAddSuperadmin bool + CanBlockUser bool + CanTransferCountry bool } func (r createManagerRequest) permissionsWithDefault() managerPermissions { return managerPermissions{ - CanGrantAvatarFrame: boolDefaultTrue(r.CanGrantAvatarFrame), - CanGrantVehicle: boolDefaultTrue(r.CanGrantVehicle), - CanGrantBadge: boolDefaultTrue(r.CanGrantBadge), - CanGrantVIP: boolDefaultTrue(r.CanGrantVIP), - CanUpdateUserLevel: boolDefaultTrue(r.CanUpdateUserLevel), - CanAddBDLeader: boolDefaultTrue(r.CanAddBDLeader), - CanAddAdmin: boolDefaultTrue(r.CanAddAdmin), - CanAddSuperadmin: boolDefaultTrue(r.CanAddSuperadmin), - CanBlockUser: boolDefaultTrue(r.CanBlockUser), - CanTransferCountry: boolDefaultTrue(r.CanTransferCountry), + CanGrantAvatarFrame: boolDefaultTrue(r.CanGrantAvatarFrame), + CanGrantVehicle: boolDefaultTrue(r.CanGrantVehicle), + CanGrantBadge: boolDefaultTrue(r.CanGrantBadge), + CanGrantVIP: boolDefaultTrue(r.CanGrantVIP), + CanUpdateUserLevel: boolDefaultTrue(r.CanUpdateUserLevel), + CanAddBDLeader: boolDefaultTrue(r.CanAddBDLeader), + CanManageCrossRegion: boolDefaultFalse(firstBool(r.CanManageCrossRegion, r.LegacyBDLeaderInRegion)), + CanAddAdmin: boolDefaultTrue(r.CanAddAdmin), + CanAddSuperadmin: boolDefaultTrue(r.CanAddSuperadmin), + CanBlockUser: boolDefaultTrue(r.CanBlockUser), + CanTransferCountry: boolDefaultTrue(r.CanTransferCountry), } } @@ -150,6 +156,7 @@ func (r updateManagerRequest) appendPermissionAssignments(assignments *[]string, appendBoolAssignment(assignments, args, "can_grant_vip", r.CanGrantVIP) appendBoolAssignment(assignments, args, "can_update_user_level", r.CanUpdateUserLevel) appendBoolAssignment(assignments, args, "can_add_bd_leader", r.CanAddBDLeader) + appendBoolAssignment(assignments, args, "can_manage_cross_region", firstBool(r.CanManageCrossRegion, r.LegacyBDLeaderInRegion)) appendBoolAssignment(assignments, args, "can_add_admin", r.CanAddAdmin) appendBoolAssignment(assignments, args, "can_add_superadmin", r.CanAddSuperadmin) appendBoolAssignment(assignments, args, "can_block_user", r.CanBlockUser) @@ -168,6 +175,17 @@ func boolDefaultTrue(value *bool) bool { return value == nil || *value } +func boolDefaultFalse(value *bool) bool { + return value != nil && *value +} + +func firstBool(primary *bool, fallback *bool) *bool { + if primary != nil { + return primary + } + return fallback +} + type createBDRequest struct { CommandID string `json:"commandId" binding:"required"` TargetUserID flexibleInt64 `json:"targetUserId" binding:"required"` diff --git a/server/admin/internal/modules/hostorg/request_test.go b/server/admin/internal/modules/hostorg/request_test.go index f877bbd1..c72ca845 100644 --- a/server/admin/internal/modules/hostorg/request_test.go +++ b/server/admin/internal/modules/hostorg/request_test.go @@ -29,3 +29,29 @@ func TestFlexibleInt64AcceptsStringAndNumber(t *testing.T) { }) } } + +func TestManagerCrossRegionDefaultsClosedAndAcceptsLegacyField(t *testing.T) { + var omitted createManagerRequest + if err := json.Unmarshal([]byte(`{"targetUserId":"1003"}`), &omitted); err != nil { + t.Fatalf("unmarshal omitted permission: %v", err) + } + if omitted.permissionsWithDefault().CanManageCrossRegion { + t.Fatalf("cross-region manager must default closed") + } + + var enabled createManagerRequest + if err := json.Unmarshal([]byte(`{"targetUserId":"1003","canManageCrossRegion":true}`), &enabled); err != nil { + t.Fatalf("unmarshal enabled permission: %v", err) + } + if !enabled.permissionsWithDefault().CanManageCrossRegion { + t.Fatalf("explicit cross-region permission must be preserved") + } + + var legacy createManagerRequest + if err := json.Unmarshal([]byte(`{"targetUserId":"1003","canAddBdLeaderInRegion":true}`), &legacy); err != nil { + t.Fatalf("unmarshal legacy permission: %v", err) + } + if !legacy.permissionsWithDefault().CanManageCrossRegion { + t.Fatalf("legacy region field must map to cross-region manager during rolling upgrade") + } +} diff --git a/server/admin/internal/modules/hostorg/service.go b/server/admin/internal/modules/hostorg/service.go index cbd73e26..8aff79d7 100644 --- a/server/admin/internal/modules/hostorg/service.go +++ b/server/admin/internal/modules/hostorg/service.go @@ -29,6 +29,7 @@ const ( sortByTotalRechargeUSDT = "total_recharge_usdt" bdLeaderPositionAliasMaxRunes = 64 hostOrgInternalUserIDMinDigits = 15 + managerOperationScopeScene = "manager_operations" ) type Service struct { @@ -56,22 +57,91 @@ func (s *Service) ListAgencies(ctx context.Context, query listQuery) ([]*usercli return s.reader.ListAgencies(ctx, query) } -func (s *Service) ListManagers(ctx context.Context, query listQuery) ([]*ManagerListItem, int64, error) { +func (s *Service) ListManagers(ctx context.Context, query listQuery) ([]*ManagerListItem, int64, ManagerRoleScopePolicy, error) { query = normalizeListQuery(query) - return s.reader.ListManagers(ctx, query) + items, total, err := s.reader.ListManagers(ctx, query) + if err != nil { + return nil, 0, ManagerRoleScopePolicy{}, err + } + policy, err := s.managerRoleScopePolicy(ctx) + if err != nil { + return nil, 0, ManagerRoleScopePolicy{}, err + } + decorateManagerScopePolicy(items, policy) + return items, total, ManagerRoleScopePolicy{ + BaseScope: policy.BaseScope, + ExpandedScope: policy.ExpandedScope, + RegionExpansionConfigurable: policy.RegionExpansionConfigurable, + }, nil } func (s *Service) CreateManager(ctx context.Context, actorID int64, requestID string, req createManagerRequest) (*ManagerListItem, error) { + policy, err := s.managerRoleScopePolicy(ctx) + if err != nil { + return nil, err + } + req.CanManageCrossRegion = normalizedManagerCrossRegionPermission(firstBool(req.CanManageCrossRegion, req.LegacyBDLeaderInRegion), policy.RegionExpansionConfigurable) + req.LegacyBDLeaderInRegion = nil targetUserID, err := s.resolveDisplayUserID(ctx, requestID, req.TargetUserID.Int64()) if err != nil { return nil, err } // 经理身份只落 manager_profiles 表;权限开关同时写入 manager_profiles,供 H5 写入口逐项校验。 - return s.reader.CreateManagerProfile(ctx, targetUserID, actorID, req) + item, err := s.reader.CreateManagerProfile(ctx, targetUserID, actorID, req) + if err != nil { + return nil, err + } + decorateManagerScopePolicy([]*ManagerListItem{item}, policy) + return item, nil } func (s *Service) UpdateManager(ctx context.Context, actorID int64, userID int64, req updateManagerRequest) (*ManagerListItem, error) { - return s.reader.UpdateManagerProfile(ctx, userID, actorID, req) + policy, err := s.managerRoleScopePolicy(ctx) + if err != nil { + return nil, err + } + req.CanManageCrossRegion = normalizedManagerCrossRegionPermission(firstBool(req.CanManageCrossRegion, req.LegacyBDLeaderInRegion), policy.RegionExpansionConfigurable) + req.LegacyBDLeaderInRegion = nil + item, err := s.reader.UpdateManagerProfile(ctx, userID, actorID, req) + if err != nil { + return nil, err + } + decorateManagerScopePolicy([]*ManagerListItem{item}, policy) + return item, nil +} + +func (s *Service) managerRoleScopePolicy(ctx context.Context) (*userclient.RoleScopePolicy, error) { + return s.userClient.GetRoleScopePolicy(ctx, userclient.GetRoleScopePolicyRequest{ + Caller: "hyapp-admin-server", + Scene: managerOperationScopeScene, + }) +} + +// normalizedManagerCrossRegionPermission 让 App 基础策略决定开关是否可配置;global App 即使收到篡改请求也只保存 false。 +func normalizedManagerCrossRegionPermission(value *bool, configurable bool) *bool { + if configurable { + return value + } + disabled := false + return &disabled +} + +func decorateManagerScopePolicy(items []*ManagerListItem, policy *userclient.RoleScopePolicy) { + if policy == nil { + return + } + for _, item := range items { + if item == nil { + continue + } + item.LegacyBDLeaderInRegion = item.CanManageCrossRegion + item.ManagerOperationBaseScope = policy.BaseScope + item.ManagerCrossRegionConfigurable = policy.RegionExpansionConfigurable + item.ManagerOperationEffectiveScope = policy.BaseScope + if policy.RegionExpansionConfigurable && item.CanManageCrossRegion { + item.ManagerOperationEffectiveScope = policy.ExpandedScope + } + } } func (s *Service) ListHosts(ctx context.Context, query listQuery) ([]*userclient.HostProfile, int64, error) { diff --git a/server/admin/internal/modules/hostorg/service_test.go b/server/admin/internal/modules/hostorg/service_test.go index 11290162..7a9e93c4 100644 --- a/server/admin/internal/modules/hostorg/service_test.go +++ b/server/admin/internal/modules/hostorg/service_test.go @@ -129,6 +129,35 @@ func TestManagerListItemJSONHasNoAgencyOrParentBD(t *testing.T) { } } +func TestDecorateManagerScopePolicyUsesPolicyMetadataWithoutAppBranches(t *testing.T) { + items := []*ManagerListItem{{CanManageCrossRegion: true}} + decorateManagerScopePolicy(items, &userclient.RoleScopePolicy{ + BaseScope: "country", + ExpandedScope: "region", + RegionExpansionConfigurable: true, + }) + if items[0].ManagerOperationEffectiveScope != "region" || !items[0].ManagerCrossRegionConfigurable || !items[0].LegacyBDLeaderInRegion { + t.Fatalf("configurable policy decoration mismatch: %+v", items[0]) + } + + items[0].CanManageCrossRegion = true + decorateManagerScopePolicy(items, &userclient.RoleScopePolicy{ + BaseScope: "global", + ExpandedScope: "global", + RegionExpansionConfigurable: false, + }) + if items[0].ManagerOperationEffectiveScope != "global" || items[0].ManagerCrossRegionConfigurable { + t.Fatalf("global policy decoration mismatch: %+v", items[0]) + } + if got := normalizedManagerCrossRegionPermission(pointerBool(true), false); got == nil || *got { + t.Fatalf("non-configurable app must persist region expansion as false") + } +} + +func pointerBool(value bool) *bool { + return &value +} + // TestBDRoleFromStatusPathUsesLeaderTableWithAPIPrefix 锁定线上真实 FullPath 带 /api/v1 前缀时,BD Leader 状态仍然写独立负责人表。 func TestBDRoleFromStatusPathUsesLeaderTableWithAPIPrefix(t *testing.T) { role, target := bdRoleFromStatusPath("/api/v1/admin/bd-leaders/:user_id/status") diff --git a/server/admin/internal/modules/job/handler.go b/server/admin/internal/modules/job/handler.go index b4b82e48..356e7877 100644 --- a/server/admin/internal/modules/job/handler.go +++ b/server/admin/internal/modules/job/handler.go @@ -5,6 +5,8 @@ import ( "path/filepath" "hyapp-admin-server/internal/config" + "hyapp-admin-server/internal/middleware" + "hyapp-admin-server/internal/model" "hyapp-admin-server/internal/modules/shared" "hyapp-admin-server/internal/repository" "hyapp-admin-server/internal/response" @@ -41,6 +43,10 @@ func (h *Handler) GetJob(c *gin.Context) { response.BadRequest(c, "任务不存在") return } + if !canReadJob(c, job) { + response.Forbidden(c, "没有查看该任务的权限") + return + } response.OK(c, job) } @@ -72,6 +78,15 @@ func (h *Handler) DownloadJobArtifact(c *gin.Context) { if !ok { return } + job, err := h.service.GetJob(id) + if err != nil { + response.BadRequest(c, "任务不存在") + return + } + if !canReadJob(c, job) { + response.Forbidden(c, "没有下载该任务的权限") + return + } _, artifactPath, err := h.service.JobArtifact(id) if err != nil { response.BadRequest(c, err.Error()) @@ -81,3 +96,16 @@ func (h *Handler) DownloadJobArtifact(c *gin.Context) { c.Header("Content-Disposition", "attachment; filename="+filepath.Base(artifactPath)) c.File(artifactPath) } + +func canReadJob(c *gin.Context, job *model.AdminJob) bool { + if job == nil { + return false + } + if middleware.HasAnyPermission(c, "job:view", "country:update") { + return true + } + // app-user:export 只允许读取本人创建的用户导出,不能借任务 ID 查看其他后台任务或他人的 CSV。 + return middleware.HasPermission(c, "app-user:export") && + job.Type == "app-user-export" && + job.CreatedBy == middleware.CurrentUserID(c) +} diff --git a/server/admin/internal/modules/job/handler_test.go b/server/admin/internal/modules/job/handler_test.go new file mode 100644 index 00000000..260a3b41 --- /dev/null +++ b/server/admin/internal/modules/job/handler_test.go @@ -0,0 +1,34 @@ +package job + +import ( + "net/http/httptest" + "testing" + + "hyapp-admin-server/internal/middleware" + "hyapp-admin-server/internal/model" + + "github.com/gin-gonic/gin" +) + +func TestCanReadJobScopesAppUserExportToCreator(t *testing.T) { + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(httptest.NewRecorder()) + context.Set(middleware.ContextUserID, uint(7)) + context.Set(middleware.ContextPermissions, []string{"app-user:export"}) + + owned := &model.AdminJob{ID: 1, Type: "app-user-export", CreatedBy: 7} + if !canReadJob(context, owned) { + t.Fatal("app-user exporter must read own export job") + } + if canReadJob(context, &model.AdminJob{ID: 2, Type: "app-user-export", CreatedBy: 8}) { + t.Fatal("app-user exporter must not read another operator's export") + } + if canReadJob(context, &model.AdminJob{ID: 3, Type: "country-code-rename", CreatedBy: 7}) { + t.Fatal("app-user exporter must not read unrelated job types") + } + + context.Set(middleware.ContextPermissions, []string{"job:view"}) + if !canReadJob(context, &model.AdminJob{ID: 4, Type: "country-code-rename", CreatedBy: 8}) { + t.Fatal("job viewer must retain broad read access") + } +} diff --git a/server/admin/internal/modules/job/routes.go b/server/admin/internal/modules/job/routes.go index e7c8ea16..9e2f0d05 100644 --- a/server/admin/internal/modules/job/routes.go +++ b/server/admin/internal/modules/job/routes.go @@ -8,8 +8,8 @@ import ( func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { protected.GET("/jobs", middleware.RequirePermission("job:view"), h.ListJobs) - protected.GET("/jobs/:id", middleware.RequireAnyPermission("job:view", "country:update"), h.GetJob) + protected.GET("/jobs/:id", middleware.RequireAnyPermission("job:view", "country:update", "app-user:export"), h.GetJob) protected.POST("/jobs/:id/cancel", middleware.RequirePermission("job:cancel"), h.CancelJob) - protected.GET("/jobs/:id/artifact", middleware.RequirePermission("job:view"), h.DownloadJobArtifact) + protected.GET("/jobs/:id/artifact", middleware.RequireAnyPermission("job:view", "app-user:export"), h.DownloadJobArtifact) protected.POST("/exports/users", middleware.RequirePermission("export:create"), h.CreateUserExportJob) } diff --git a/server/admin/internal/modules/payment/finance_coin_seller_recharge.go b/server/admin/internal/modules/payment/finance_coin_seller_recharge.go index 93e403a0..f17610c0 100644 --- a/server/admin/internal/modules/payment/finance_coin_seller_recharge.go +++ b/server/admin/internal/modules/payment/finance_coin_seller_recharge.go @@ -530,11 +530,11 @@ func financeCoinSellerRechargeOrderBill(order model.CoinSellerRechargeOrder) rec CoinAmount: order.CoinAmount, USDMinorAmount: order.USDMinorAmount, ProviderAmountMinor: providerAmountMinor, - CreatedAtMS: order.CreatedAtMS, + CreatedAtMS: order.BusinessTimeMS(), ProviderCode: order.ProviderCode, UserPaidCurrencyCode: currency, UserPaidAmountMicro: userPaidAmountMicro, - PaidSyncedAtMS: order.CreatedAtMS, + PaidSyncedAtMS: order.BusinessTimeMS(), } } diff --git a/server/admin/internal/modules/payment/finance_coin_seller_recharge_test.go b/server/admin/internal/modules/payment/finance_coin_seller_recharge_test.go new file mode 100644 index 00000000..196526f0 --- /dev/null +++ b/server/admin/internal/modules/payment/finance_coin_seller_recharge_test.go @@ -0,0 +1,23 @@ +package payment + +import ( + "testing" + + "hyapp-admin-server/internal/model" +) + +func TestFinanceCoinSellerRechargeOrderBillUsesBusinessTime(t *testing.T) { + order := model.CoinSellerRechargeOrder{ + ID: 7, + AppCode: "lalu", + TargetUserID: 10001, + USDMinorAmount: 15000, + CoinAmount: 12000000, + OrderDateMS: 1_783_708_200_000, + CreatedAtMS: 1_783_881_000_000, + } + bill := financeCoinSellerRechargeOrderBill(order) + if bill.CreatedAtMS != order.OrderDateMS || bill.PaidSyncedAtMS != order.OrderDateMS { + t.Fatalf("finance bill must use selected business time: %+v", bill) + } +} diff --git a/server/admin/internal/modules/payment/google_paid_sync_worker.go b/server/admin/internal/modules/payment/google_paid_sync_worker.go new file mode 100644 index 00000000..379dd420 --- /dev/null +++ b/server/admin/internal/modules/payment/google_paid_sync_worker.go @@ -0,0 +1,141 @@ +package payment + +import ( + "context" + "log/slog" + "strings" + "sync" + "time" + + "hyapp-admin-server/internal/config" +) + +type legacyGooglePaidSyncSource interface { + AppCode() string + SupportsGooglePaidSync() bool + ListPendingGooglePaidTransactionIDs(ctx context.Context, limit int) ([]string, error) + RefreshGooglePaidDetails(ctx context.Context, transactionIDs []string) ([]googleRechargePaidDTO, error) +} + +type legacyGooglePaidSyncAttemptStore interface { + RecordLegacyGooglePaidSyncFailure(appCode string, transactionID string, message string, nowMS int64) error + ClearLegacyGooglePaidSyncFailure(appCode string, transactionID string) error +} + +// LegacyGooglePaidSyncWorker polls every 15 seconds independently of finance-page traffic. +// Successful rows leave the pending set through the paid cache; failures remain durable with backoff. +type LegacyGooglePaidSyncWorker struct { + batchSize int + pollInterval time.Duration + requestTimeout time.Duration + sources []legacyGooglePaidSyncSource + store legacyGooglePaidSyncAttemptStore + + closeOnce sync.Once + done chan struct{} + stop chan struct{} +} + +func NewLegacyGooglePaidSyncWorker(sources []RechargeBillSource, store legacyGooglePaidSyncAttemptStore, cfg config.FinanceGooglePaidSyncConfig) *LegacyGooglePaidSyncWorker { + narrow := make([]legacyGooglePaidSyncSource, 0, len(sources)) + for _, source := range sources { + if candidate, ok := source.(legacyGooglePaidSyncSource); ok && candidate.SupportsGooglePaidSync() { + narrow = append(narrow, candidate) + } + } + return newLegacyGooglePaidSyncWorker(narrow, store, cfg) +} + +func newLegacyGooglePaidSyncWorker(sources []legacyGooglePaidSyncSource, store legacyGooglePaidSyncAttemptStore, cfg config.FinanceGooglePaidSyncConfig) *LegacyGooglePaidSyncWorker { + return &LegacyGooglePaidSyncWorker{ + batchSize: cfg.BatchSize, + pollInterval: cfg.PollInterval, + requestTimeout: cfg.RequestTimeout, + sources: sources, + store: store, + done: make(chan struct{}), + stop: make(chan struct{}), + } +} + +func (w *LegacyGooglePaidSyncWorker) Start() { + if w == nil || w.store == nil || len(w.sources) == 0 { + return + } + go w.loop() +} + +func (w *LegacyGooglePaidSyncWorker) Close() { + if w == nil || w.store == nil || len(w.sources) == 0 { + return + } + w.closeOnce.Do(func() { + close(w.stop) + <-w.done + }) +} + +func (w *LegacyGooglePaidSyncWorker) loop() { + defer close(w.done) + ticker := time.NewTicker(w.pollInterval) + defer ticker.Stop() + w.runOnce(context.Background()) + for { + select { + case <-ticker.C: + w.runOnce(context.Background()) + case <-w.stop: + return + } + } +} + +func (w *LegacyGooglePaidSyncWorker) runOnce(parent context.Context) { + for _, source := range w.sources { + // Ten full batches per tick drain historical data quickly without turning the steady-state + // 15-second poller into an unbounded loop when a provider keeps returning pathological data. + for drainBatch := 0; drainBatch < 10; drainBatch++ { + ctx, cancel := context.WithTimeout(parent, w.requestTimeout) + transactionIDs, err := source.ListPendingGooglePaidTransactionIDs(ctx, w.batchSize) + if err != nil { + cancel() + slog.Error("legacy_google_paid_sync_scan_failed", "app_code", source.AppCode(), "error", err.Error()) + break + } + if len(transactionIDs) == 0 { + cancel() + break + } + results, err := source.RefreshGooglePaidDetails(ctx, transactionIDs) + cancel() + if err != nil { + slog.Error("legacy_google_paid_sync_batch_failed", "app_code", source.AppCode(), "count", len(transactionIDs), "error", err.Error()) + break + } + nowMS := time.Now().UnixMilli() + succeeded := 0 + failed := 0 + for _, result := range results { + transactionID := strings.TrimSpace(result.TransactionID) + if transactionID == "" { + continue + } + if strings.TrimSpace(result.Error) == "" { + if err := w.store.ClearLegacyGooglePaidSyncFailure(source.AppCode(), transactionID); err != nil { + slog.Error("legacy_google_paid_sync_retry_clear_failed", "app_code", source.AppCode(), "transaction_id", transactionID, "error", err.Error()) + } + succeeded++ + continue + } + failed++ + if err := w.store.RecordLegacyGooglePaidSyncFailure(source.AppCode(), transactionID, result.Error, nowMS); err != nil { + slog.Error("legacy_google_paid_sync_retry_store_failed", "app_code", source.AppCode(), "transaction_id", transactionID, "error", err.Error()) + } + } + slog.Info("legacy_google_paid_sync_batch_completed", "app_code", source.AppCode(), "claimed", len(transactionIDs), "succeeded", succeeded, "failed", failed) + if len(transactionIDs) < w.batchSize { + break + } + } + } +} diff --git a/server/admin/internal/modules/payment/google_paid_sync_worker_test.go b/server/admin/internal/modules/payment/google_paid_sync_worker_test.go new file mode 100644 index 00000000..a03de820 --- /dev/null +++ b/server/admin/internal/modules/payment/google_paid_sync_worker_test.go @@ -0,0 +1,59 @@ +package payment + +import ( + "context" + "testing" + "time" + + "hyapp-admin-server/internal/config" +) + +type fakeLegacyGooglePaidSyncSource struct { + pending []string + results []googleRechargePaidDTO +} + +func (s *fakeLegacyGooglePaidSyncSource) AppCode() string { return "yumi" } +func (s *fakeLegacyGooglePaidSyncSource) SupportsGooglePaidSync() bool { return true } +func (s *fakeLegacyGooglePaidSyncSource) ListPendingGooglePaidTransactionIDs(context.Context, int) ([]string, error) { + return append([]string(nil), s.pending...), nil +} +func (s *fakeLegacyGooglePaidSyncSource) RefreshGooglePaidDetails(context.Context, []string) ([]googleRechargePaidDTO, error) { + return append([]googleRechargePaidDTO(nil), s.results...), nil +} + +type fakeLegacyGooglePaidAttemptStore struct { + cleared []string + failed []string +} + +func (s *fakeLegacyGooglePaidAttemptStore) RecordLegacyGooglePaidSyncFailure(_ string, transactionID string, _ string, _ int64) error { + s.failed = append(s.failed, transactionID) + return nil +} +func (s *fakeLegacyGooglePaidAttemptStore) ClearLegacyGooglePaidSyncFailure(_ string, transactionID string) error { + s.cleared = append(s.cleared, transactionID) + return nil +} + +func TestLegacyGooglePaidSyncWorkerPersistsPerOrderOutcome(t *testing.T) { + source := &fakeLegacyGooglePaidSyncSource{ + pending: []string{"ok-1", "bad-1"}, + results: []googleRechargePaidDTO{{TransactionID: "ok-1", SyncedAtMS: 1}, {TransactionID: "bad-1", Error: "temporary"}}, + } + store := &fakeLegacyGooglePaidAttemptStore{} + worker := newLegacyGooglePaidSyncWorker([]legacyGooglePaidSyncSource{source}, store, config.FinanceGooglePaidSyncConfig{ + PollInterval: 15 * time.Second, + BatchSize: 100, + RequestTimeout: time.Second, + }) + + worker.runOnce(context.Background()) + + if len(store.cleared) != 1 || store.cleared[0] != "ok-1" { + t.Fatalf("cleared = %v, want ok-1", store.cleared) + } + if len(store.failed) != 1 || store.failed[0] != "bad-1" { + t.Fatalf("failed = %v, want bad-1", store.failed) + } +} diff --git a/server/admin/internal/modules/payment/handler_test.go b/server/admin/internal/modules/payment/handler_test.go index 60fba2b5..d12c34da 100644 --- a/server/admin/internal/modules/payment/handler_test.go +++ b/server/admin/internal/modules/payment/handler_test.go @@ -115,6 +115,62 @@ func TestGetRechargeBillSummaryKeepsOrdinaryThirdParty(t *testing.T) { } } +func TestFinanceRechargeOverviewReusesFinanceChannelsAndDropsWalletStockCoinSeller(t *testing.T) { + wallet := &mockPaymentWallet{} + wallet.rechargeOverviewFn = func(req *walletv1.GetRechargeBillOverviewRequest) (*walletv1.GetRechargeBillOverviewResponse, error) { + regionID := req.GetRegionId() + googleUSD, thirdPartyUSD := int64(100), int64(200) + if regionID == 7 { + googleUSD, thirdPartyUSD = 40, 60 + } + switch req.GetRechargeType() { + case "google_play_recharge": + return &walletv1.GetRechargeBillOverviewResponse{Regions: []*walletv1.RechargeBillRegionBucket{{RegionId: 7, UsdMinorAmount: googleUSD}}}, nil + case "third_party": + return &walletv1.GetRechargeBillOverviewResponse{Regions: []*walletv1.RechargeBillRegionBucket{{RegionId: 7, UsdMinorAmount: thirdPartyUSD}}}, nil + default: + return &walletv1.GetRechargeBillOverviewResponse{Daily: []*walletv1.RechargeBillDailyBucket{{ + Date: "2026-07-10", GoogleUsdMinor: googleUSD, ThirdPartyUsdMinor: thirdPartyUSD, + // wallet overview 的旧库存币商列必须被 Finance 订单/H5 聚合清零后重填。 + CoinSellerUsdMinor: 9999, + }}}, nil + } + } + handler := New(wallet, nil, nil, nil, nil) + + overview, err := handler.FinanceRechargeOverview(context.Background(), FinanceRechargeOverviewQuery{ + AppCode: "lalu", StartAtMS: 100, EndAtMS: 200, IncludeRegions: true, TZOffsetMinutes: 480, + }) + if err != nil { + t.Fatalf("FinanceRechargeOverview failed: %v", err) + } + if len(overview.Daily) != 1 || overview.Daily[0].GoogleUSDMinor != 100 || overview.Daily[0].ThirdPartyUSDMinor != 200 || overview.Daily[0].CoinSellerUSDMinor != 0 { + t.Fatalf("global Finance channels mismatch: %#v", overview.Daily) + } + if len(overview.Regions) != 1 || overview.Regions[0].RegionID != 7 || overview.Regions[0].TotalUSDMinor != 300 || overview.Regions[0].DailyAvailable || len(overview.Regions[0].Daily) != 0 { + t.Fatalf("global region Finance total mismatch: %#v", overview.Regions) + } + if len(wallet.rechargeOverviewCalls) != 3 { + t.Fatalf("default overview must not fan out by region, got %d calls: %#v", len(wallet.rechargeOverviewCalls), wallet.rechargeOverviewCalls) + } + + scoped, err := handler.FinanceRechargeOverview(context.Background(), FinanceRechargeOverviewQuery{ + AppCode: "lalu", StartAtMS: 100, EndAtMS: 200, ScopeRegionIDs: []int64{7}, IncludeRegions: true, TZOffsetMinutes: 480, + }) + if err != nil { + t.Fatalf("scoped FinanceRechargeOverview failed: %v", err) + } + if len(scoped.Daily) != 1 || scoped.Daily[0].GoogleUSDMinor != 40 || scoped.Daily[0].ThirdPartyUSDMinor != 60 { + t.Fatalf("scoped global channels mismatch: %#v", scoped.Daily) + } + if len(scoped.Regions) != 1 || !scoped.Regions[0].DailyAvailable || scoped.Regions[0].TotalUSDMinor != 100 || len(scoped.Regions[0].Daily) != 1 { + t.Fatalf("explicit region scope must include daily channels: %#v", scoped.Regions) + } + if len(wallet.rechargeOverviewCalls) != 4 { + t.Fatalf("explicit one-region scope should add only one Finance aggregation, got %d calls: %#v", len(wallet.rechargeOverviewCalls), wallet.rechargeOverviewCalls) + } +} + func TestListThirdPartyPaymentChannelsForwardsIncludeDisabledMethods(t *testing.T) { wallet := &mockPaymentWallet{thirdPartyChannelsResp: &walletv1.ListThirdPartyPaymentChannelsResponse{ Channels: []*walletv1.ThirdPartyPaymentChannel{{ @@ -784,6 +840,8 @@ type mockPaymentWallet struct { lastRechargeBills *walletv1.ListRechargeBillsRequest rechargeBillsResp *walletv1.ListRechargeBillsResponse + rechargeOverviewFn func(*walletv1.GetRechargeBillOverviewRequest) (*walletv1.GetRechargeBillOverviewResponse, error) + rechargeOverviewCalls []*walletv1.GetRechargeBillOverviewRequest rechargeSummaryResp *walletv1.GetRechargeBillSummaryResponse lastThirdPartyChannels *walletv1.ListThirdPartyPaymentChannelsRequest thirdPartyChannelsResp *walletv1.ListThirdPartyPaymentChannelsResponse @@ -816,6 +874,14 @@ func (m *mockPaymentWallet) GetRechargeBillSummary(_ context.Context, req *walle return &walletv1.GetRechargeBillSummaryResponse{}, nil } +func (m *mockPaymentWallet) GetRechargeBillOverview(_ context.Context, req *walletv1.GetRechargeBillOverviewRequest) (*walletv1.GetRechargeBillOverviewResponse, error) { + m.rechargeOverviewCalls = append(m.rechargeOverviewCalls, req) + if m.rechargeOverviewFn != nil { + return m.rechargeOverviewFn(req) + } + return &walletv1.GetRechargeBillOverviewResponse{}, nil +} + func (m *mockPaymentWallet) ListThirdPartyPaymentChannels(_ context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error) { m.lastThirdPartyChannels = req if m.thirdPartyChannelsResp != nil { diff --git a/server/admin/internal/modules/payment/legacy_bill_source.go b/server/admin/internal/modules/payment/legacy_bill_source.go index cafbd35c..1fa20c4e 100644 --- a/server/admin/internal/modules/payment/legacy_bill_source.go +++ b/server/admin/internal/modules/payment/legacy_bill_source.go @@ -9,6 +9,7 @@ import ( "sort" "strconv" "strings" + "sync" "time" "hyapp-admin-server/internal/appctx" @@ -20,6 +21,7 @@ import ( "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) // legacyRechargeBillQuery 是 legacy 账单源支持的筛选子集;用户/币商筛选属于 hyapp 语义,legacy 源不适用。 @@ -48,6 +50,8 @@ type RechargeBillSource interface { SummarizeRechargeBills(ctx context.Context, query legacyRechargeBillQuery) (rechargeBillSummaryDTO, error) // SupportsGooglePaidSync 为 true 时,RefreshGooglePaidDetails 用该 App 的 Play Console 服务账号同步实付明细。 SupportsGooglePaidSync() bool + // ListPendingGooglePaidTransactionIDs returns newest successful Google bills that still need paid-detail synchronization. + ListPendingGooglePaidTransactionIDs(ctx context.Context, limit int) ([]string, error) RefreshGooglePaidDetails(ctx context.Context, transactionIDs []string) ([]googleRechargePaidDTO, error) // Overview 返回财务概览聚合:按日趋势、区域分布与谷歌实付覆盖度。 Overview(ctx context.Context, query legacyRechargeBillQuery, tzOffsetMinutes int32) (rechargeBillOverviewDTO, error) @@ -64,6 +68,7 @@ type LegacyGooglePaidStore interface { LegacyGooglePaidDetails(appCode string, transactionIDs []string) (map[string]model.LegacyGooglePaidDetail, error) LegacyGooglePaidStatsByBillTime(appCode string, startAtMS int64, endAtMS int64) (repository.LegacyGooglePaidStats, error) LegacyGooglePaidTransactionIDs(appCode string) ([]string, error) + LegacyGooglePaidRetryBlockedTransactionIDs(appCode string, nowMS int64) ([]string, error) } // legacyGoogleOrdersClient 按 legacy App 各自的 Play Console 服务账号查订单实付。 @@ -71,6 +76,10 @@ type legacyGoogleOrdersClient interface { GetOrder(ctx context.Context, orderID string) (googleorders.Order, error) } +type legacyGoogleOrdersBatchClient interface { + GetOrders(ctx context.Context, orderIDs []string) (map[string]googleorders.Order, error) +} + // errLegacyGooglePaidUnsupported 表示该账单源未配置谷歌包名/服务账号或实付缓存存储。 var errLegacyGooglePaidUnsupported = errors.New("legacy google paid sync is not configured") @@ -143,6 +152,83 @@ func (s *MongoRechargeBillSource) SupportsGooglePaidSync() bool { return s != nil && s.paidStore != nil && s.googleOrders != nil } +// ListPendingGooglePaidTransactionIDs scans the read-only legacy Mongo source and subtracts both +// successful cache rows and failures still inside persisted backoff. Sorting newest-first keeps the +// 15-second loop current while successful removals naturally drain the complete historical backlog. +func (s *MongoRechargeBillSource) ListPendingGooglePaidTransactionIDs(ctx context.Context, limit int) ([]string, error) { + if !s.SupportsGooglePaidSync() { + return nil, errLegacyGooglePaidUnsupported + } + if limit <= 0 { + return []string{}, nil + } + if limit > 1000 { + limit = 1000 + } + filter, _, matchable, err := s.billFilter(ctx, legacyRechargeBillQuery{RechargeType: "google_play_recharge"}) + if err != nil || !matchable { + return nil, err + } + syncedIDs, err := s.paidStore.LegacyGooglePaidTransactionIDs(s.config.AppCode) + if err != nil { + return nil, fmt.Errorf("load synced legacy google transactions for %s: %w", s.config.AppCode, err) + } + blockedIDs, err := s.paidStore.LegacyGooglePaidRetryBlockedTransactionIDs(s.config.AppCode, time.Now().UnixMilli()) + if err != nil { + return nil, fmt.Errorf("load blocked legacy google transactions for %s: %w", s.config.AppCode, err) + } + excluded := append(syncedIDs, blockedIDs...) + if values := legacyTransactionIDFilterValues(excluded); len(values) > 0 { + filter["_id"] = bson.M{"$nin": values} + } + cursor, err := s.collection.Find(ctx, filter, options.Find(). + SetProjection(bson.M{"_id": 1}). + SetSort(bson.D{{Key: "createTime", Value: -1}, {Key: "_id", Value: -1}}). + SetLimit(int64(limit))) + if err != nil { + return nil, fmt.Errorf("list pending legacy google bills for %s: %w", s.config.AppCode, err) + } + defer cursor.Close(ctx) + ids := make([]string, 0, limit) + for cursor.Next(ctx) { + var row struct { + ID any `bson:"_id"` + } + if err := cursor.Decode(&row); err != nil { + return nil, fmt.Errorf("decode pending legacy google bill for %s: %w", s.config.AppCode, err) + } + if transactionID := legacyDocumentID(row.ID); transactionID != "" { + ids = append(ids, transactionID) + } + } + if err := cursor.Err(); err != nil { + return nil, fmt.Errorf("iterate pending legacy google bills for %s: %w", s.config.AppCode, err) + } + return ids, nil +} + +// legacyTransactionIDFilterValues covers both string IDs and ObjectID-backed collections. +// The paid cache stores a stable string representation, so both BSON shapes must be excluded. +func legacyTransactionIDFilterValues(transactionIDs []string) bson.A { + values := make(bson.A, 0, len(transactionIDs)*2) + seen := make(map[string]struct{}, len(transactionIDs)) + for _, transactionID := range transactionIDs { + transactionID = strings.TrimSpace(transactionID) + if transactionID == "" { + continue + } + if _, ok := seen[transactionID]; ok { + continue + } + seen[transactionID] = struct{}{} + values = append(values, transactionID) + if objectID, err := bson.ObjectIDFromHex(transactionID); err == nil { + values = append(values, objectID) + } + } + return values +} + func (s *MongoRechargeBillSource) AppCode() string { return s.config.AppCode } @@ -503,14 +589,59 @@ func (s *MongoRechargeBillSource) RefreshGooglePaidDetails(ctx context.Context, return nil, fmt.Errorf("iterate legacy bills for google paid refresh: %w", err) } - results := make([]googleRechargePaidDTO, 0, len(cleaned)) - for _, transactionID := range cleaned { - results = append(results, s.refreshGooglePaidDetail(ctx, transactionID, bills)) + // Backfills use batchGet whenever every order can be resolved in one Google request. Google rejects + // an entire batch containing one stale ID, so failures fall back to bounded concurrent single gets. + prefetched := map[string]googleorders.Order{} + batchReady := false + if batchClient, ok := s.googleOrders.(legacyGoogleOrdersBatchClient); ok { + orderIDs := make([]string, 0, len(bills)) + for _, transactionID := range cleaned { + if document, exists := bills[transactionID]; exists { + if orderID := strings.TrimSpace(document.OrderID); orderID != "" { + orderIDs = append(orderIDs, orderID) + } + } + } + if len(orderIDs) > 0 { + if orders, batchErr := batchClient.GetOrders(ctx, orderIDs); batchErr == nil { + prefetched = orders + batchReady = true + } + } } + results := make([]googleRechargePaidDTO, len(cleaned)) + if batchReady { + for index, transactionID := range cleaned { + results[index] = s.refreshGooglePaidDetailWithOrders(ctx, transactionID, bills, prefetched) + } + return results, nil + } + const fallbackConcurrency = 8 + semaphore := make(chan struct{}, fallbackConcurrency) + var wait sync.WaitGroup + for index, transactionID := range cleaned { + wait.Add(1) + go func(index int, transactionID string) { + defer wait.Done() + select { + case semaphore <- struct{}{}: + defer func() { <-semaphore }() + case <-ctx.Done(): + results[index] = googleRechargePaidDTO{TransactionID: transactionID, Error: ctx.Err().Error()} + return + } + results[index] = s.refreshGooglePaidDetail(ctx, transactionID, bills) + }(index, transactionID) + } + wait.Wait() return results, nil } func (s *MongoRechargeBillSource) refreshGooglePaidDetail(ctx context.Context, transactionID string, bills map[string]legacyPurchaseDocument) googleRechargePaidDTO { + return s.refreshGooglePaidDetailWithOrders(ctx, transactionID, bills, nil) +} + +func (s *MongoRechargeBillSource) refreshGooglePaidDetailWithOrders(ctx context.Context, transactionID string, bills map[string]legacyPurchaseDocument, prefetched map[string]googleorders.Order) googleRechargePaidDTO { result := googleRechargePaidDTO{TransactionID: transactionID} document, ok := bills[transactionID] if !ok { @@ -526,11 +657,19 @@ func (s *MongoRechargeBillSource) refreshGooglePaidDetail(ctx context.Context, t result.Error = "缺少谷歌订单号" return result } - order, err := s.googleOrders.GetOrder(ctx, orderID) - if err != nil { - result.Error = err.Error() + order, prefetchedOK := prefetched[orderID] + if prefetched != nil && !prefetchedOK { + result.Error = "google order is not found" return result } + if !prefetchedOK { + var err error + order, err = s.googleOrders.GetOrder(ctx, orderID) + if err != nil { + result.Error = err.Error() + return result + } + } syncedAt := time.Now().UnixMilli() billCreatedAtMS := int64(0) if !document.CreateTime.IsZero() { @@ -703,11 +842,7 @@ func (s *MongoRechargeBillSource) billFilter(ctx context.Context, query legacyRe if err != nil { return nil, nil, false, fmt.Errorf("load synced legacy google tx ids for %s: %w", s.config.AppCode, err) } - if len(syncedIDs) > 0 { - idValues := make(bson.A, 0, len(syncedIDs)) - for _, transactionID := range syncedIDs { - idValues = append(idValues, transactionID) - } + if idValues := legacyTransactionIDFilterValues(syncedIDs); len(idValues) > 0 { filter["_id"] = bson.M{"$nin": idValues} } } diff --git a/server/admin/internal/modules/payment/recharge_bill_overview.go b/server/admin/internal/modules/payment/recharge_bill_overview.go index 28e8027e..45952cb7 100644 --- a/server/admin/internal/modules/payment/recharge_bill_overview.go +++ b/server/admin/internal/modules/payment/recharge_bill_overview.go @@ -1,7 +1,11 @@ package payment import ( + "context" + "fmt" + "sort" "strings" + "sync" "hyapp-admin-server/internal/appctx" "hyapp-admin-server/internal/middleware" @@ -55,6 +59,44 @@ type rechargeBillOverviewDTO struct { Withdrawal rechargeBillWithdrawalStatsDTO `json:"withdrawal"` } +// FinanceRechargeOverviewQuery 是供其他后台模块复用财务充值口径的只读查询。 +// ScopeRegionIDs 非空时只汇总这些区域,避免数据范围受限的 BI 查询先读全量再裁剪。 +type FinanceRechargeOverviewQuery struct { + AppCode string + StartAtMS int64 + EndAtMS int64 + ScopeRegionIDs []int64 + IncludeRegions bool + TZOffsetMinutes int32 +} + +// FinanceRechargeDailyBucket 与 Finance 页面趋势使用同一组渠道字段和切日口径。 +type FinanceRechargeDailyBucket struct { + Date string + GoogleUSDMinor int64 + ThirdPartyUSDMinor int64 + CoinSellerUSDMinor int64 +} + +// FinanceRechargeRegionOverview 在全量查询时只返回 Finance 页面已有的区域总额; +// 仅显式区域范围查询才补齐逐日渠道,避免默认概览对所有区域做 N+1 聚合。 +type FinanceRechargeRegionOverview struct { + RegionID int64 + TotalUSDMinor int64 + DailyAvailable bool + Daily []FinanceRechargeDailyBucket +} + +type FinanceRechargeOverview struct { + Daily []FinanceRechargeDailyBucket + Regions []FinanceRechargeRegionOverview +} + +// FinanceRechargeOverviewSource 让 Social BI 依赖 Finance 的业务口径,而不是依赖 payment HTTP handler。 +type FinanceRechargeOverviewSource interface { + FinanceRechargeOverview(context.Context, FinanceRechargeOverviewQuery) (FinanceRechargeOverview, error) +} + // GetRechargeBillOverview 返回财务概览聚合:按日趋势、区域分布与谷歌实付覆盖度;筛选口径与账单列表一致。 func (h *Handler) GetRechargeBillOverview(c *gin.Context) { options := shared.ListOptions(c) @@ -80,48 +122,6 @@ func (h *Handler) GetRechargeBillOverview(c *gin.Context) { response.OK(c, rechargeBillOverviewDTO{Daily: []rechargeBillDailyBucketDTO{}, Regions: []rechargeBillRegionBucketDTO{}}) return } - if source, ok := h.billSources[appCode]; ok { - query := legacyRechargeBillQuery{ - Keyword: options.Keyword, - RechargeType: strings.TrimSpace(firstQuery(c, "recharge_type", "rechargeType")), - PaidState: strings.TrimSpace(firstQuery(c, "paid_state", "paidState")), - RegionID: queryInt64(c, "region_id", "regionId"), - StartAtMS: startAtMS, - EndAtMS: endAtMS, - } - overview := rechargeBillOverviewDTO{Daily: []rechargeBillDailyBucketDTO{}, Regions: []rechargeBillRegionBucketDTO{}} - if userFilter.IsEmpty() && sellerFilter.IsEmpty() { - var err error - overview, err = source.Overview(c.Request.Context(), query, tzOffsetMinutes) - if err != nil { - response.ServerError(c, "获取财务概览失败") - return - } - } - if err := h.applyFinanceCoinSellerOverview(c.Request.Context(), appCode, financeCoinSellerRechargeQuery{ - RechargeType: strings.TrimSpace(firstQuery(c, "recharge_type", "rechargeType")), - Status: options.Status, - PaidState: query.PaidState, - Keyword: options.Keyword, - RegionID: query.RegionID, - StartAtMS: startAtMS, - EndAtMS: endAtMS, - TzOffsetMinutes: tzOffsetMinutes, - TargetUserID: sellerUserID, - HasUserOnlyFilter: userID > 0 && sellerUserID == 0, - }, &overview); err != nil { - response.ServerError(c, "获取财务概览失败") - return - } - h.fillApprovedWithdrawalStats(&overview, appCode, startAtMS, endAtMS) - response.OK(c, overview) - return - } - - overview := rechargeBillOverviewDTO{ - Daily: []rechargeBillDailyBucketDTO{}, - Regions: []rechargeBillRegionBucketDTO{}, - } financeQuery := financeCoinSellerRechargeQuery{ RechargeType: strings.TrimSpace(firstQuery(c, "recharge_type", "rechargeType")), Status: options.Status, @@ -136,20 +136,157 @@ func (h *Handler) GetRechargeBillOverview(c *gin.Context) { RequestID: middleware.CurrentRequestID(c), UserID: userID, } - if financeWalletOverviewSupportsOrdinaryFilters(financeQuery) { - resp, err := h.wallet.GetRechargeBillOverview(c.Request.Context(), &walletv1.GetRechargeBillOverviewRequest{ - RequestId: middleware.CurrentRequestID(c), + // HTTP 页面和 Social BI 都走同一个装配函数;后续口径调整只允许在这里发生一次。 + overview, err := h.loadFinanceRechargeOverview(c.Request.Context(), appCode, financeQuery, userFilter.IsEmpty() && sellerFilter.IsEmpty(), true) + if err != nil { + writeWalletError(c, err, "获取财务概览失败") + return + } + h.fillApprovedWithdrawalStats(&overview, appCode, startAtMS, endAtMS) + response.OK(c, overview) +} + +// FinanceRechargeOverview 暴露 Finance 页面自身使用的充值聚合,供 Social BI 复用。 +// 默认只复用 Finance 已经返回的全局逐日与区域总额;显式区域筛选才按区域补逐日渠道。 +func (h *Handler) FinanceRechargeOverview(ctx context.Context, query FinanceRechargeOverviewQuery) (FinanceRechargeOverview, error) { + appCode := appctx.Normalize(query.AppCode) + if appCode == "" { + return FinanceRechargeOverview{}, fmt.Errorf("app_code is required") + } + tzOffsetMinutes := query.TZOffsetMinutes + if tzOffsetMinutes == 0 { + tzOffsetMinutes = 480 + } + regionIDs := uniquePositiveInt64s(query.ScopeRegionIDs) + baseQuery := financeCoinSellerRechargeQuery{ + StartAtMS: query.StartAtMS, + EndAtMS: query.EndAtMS, + TzOffsetMinutes: tzOffsetMinutes, + } + + if len(regionIDs) > 0 { + regions, err := h.loadFinanceRechargeRegions(ctx, appCode, baseQuery, regionIDs) + if err != nil { + return FinanceRechargeOverview{}, err + } + return FinanceRechargeOverview{Daily: mergeFinanceRechargeRegionDaily(regions), Regions: regions}, nil + } + + overview, err := h.loadFinanceRechargeOverview(ctx, appCode, baseQuery, true, query.IncludeRegions) + if err != nil { + return FinanceRechargeOverview{}, err + } + result := FinanceRechargeOverview{Daily: financeRechargeDailyBuckets(overview.Daily)} + if !query.IncludeRegions { + return result, nil + } + for _, region := range overview.Regions { + if region.RegionID <= 0 { + continue + } + result.Regions = append(result.Regions, FinanceRechargeRegionOverview{ + RegionID: region.RegionID, + TotalUSDMinor: region.UsdMinorAmount, + }) + } + sort.Slice(result.Regions, func(i, j int) bool { return result.Regions[i].RegionID < result.Regions[j].RegionID }) + return result, nil +} + +func (h *Handler) loadFinanceRechargeRegions(ctx context.Context, appCode string, baseQuery financeCoinSellerRechargeQuery, regionIDs []int64) ([]FinanceRechargeRegionOverview, error) { + type regionResult struct { + region FinanceRechargeRegionOverview + err error + } + results := make(chan regionResult, len(regionIDs)) + semaphore := make(chan struct{}, 4) + var wg sync.WaitGroup + for _, regionID := range uniquePositiveInt64s(regionIDs) { + regionID := regionID + wg.Add(1) + go func() { + defer wg.Done() + semaphore <- struct{}{} + defer func() { <-semaphore }() + regionQuery := baseQuery + regionQuery.RegionID = regionID + // 单区域默认趋势已经同时返回 Google/三方渠道;这里不再额外查询两次渠道区域分布。 + overview, err := h.loadFinanceRechargeOverview(ctx, appCode, regionQuery, true, false) + daily := financeRechargeDailyBuckets(overview.Daily) + results <- regionResult{ + region: FinanceRechargeRegionOverview{ + RegionID: regionID, + TotalUSDMinor: financeRechargeDailyTotal(daily), + DailyAvailable: true, + Daily: daily, + }, + err: err, + } + }() + } + wg.Wait() + close(results) + + regions := make([]FinanceRechargeRegionOverview, 0, len(regionIDs)) + for result := range results { + if result.err != nil { + return nil, result.err + } + regions = append(regions, result.region) + } + sort.Slice(regions, func(i, j int) bool { return regions[i].RegionID < regions[j].RegionID }) + return regions, nil +} + +func financeRechargeDailyTotal(rows []FinanceRechargeDailyBucket) int64 { + var total int64 + for _, row := range rows { + total += row.GoogleUSDMinor + row.ThirdPartyUSDMinor + row.CoinSellerUSDMinor + } + return total +} + +// loadFinanceRechargeOverview 是 Finance HTTP 页面与模块内复用入口的唯一装配路径。 +// ordinary 与币商统计仍分别由 wallet/legacy source 和 financeCoinSellerRechargeStats 提供,禁止回退旧库存币商口径。 +func (h *Handler) loadFinanceRechargeOverview(ctx context.Context, appCode string, query financeCoinSellerRechargeQuery, includeOrdinary bool, includeRegions bool) (rechargeBillOverviewDTO, error) { + overview := rechargeBillOverviewDTO{Daily: []rechargeBillDailyBucketDTO{}, Regions: []rechargeBillRegionBucketDTO{}} + if source, ok := h.billSources[appCode]; ok { + if includeOrdinary { + var err error + overview, err = source.Overview(ctx, legacyRechargeBillQuery{ + Keyword: query.Keyword, + RechargeType: query.RechargeType, + PaidState: query.PaidState, + RegionID: query.RegionID, + StartAtMS: query.StartAtMS, + EndAtMS: query.EndAtMS, + }, query.TzOffsetMinutes) + if err != nil { + return rechargeBillOverviewDTO{}, err + } + } + if err := h.applyFinanceCoinSellerOverview(ctx, appCode, query, &overview); err != nil { + return rechargeBillOverviewDTO{}, err + } + return overview, nil + } + + if includeOrdinary && financeWalletOverviewSupportsOrdinaryFilters(query) { + if h.wallet == nil { + return rechargeBillOverviewDTO{}, fmt.Errorf("wallet recharge overview is not configured") + } + resp, err := h.wallet.GetRechargeBillOverview(ctx, &walletv1.GetRechargeBillOverviewRequest{ + RequestId: query.RequestID, AppCode: appCode, - RegionId: queryInt64(c, "region_id", "regionId"), - RechargeType: financeQuery.RechargeType, - Status: options.Status, - StartAtMs: startAtMS, - EndAtMs: endAtMS, - TzOffsetMinutes: tzOffsetMinutes, + RegionId: query.RegionID, + RechargeType: query.RechargeType, + Status: query.Status, + StartAtMs: query.StartAtMS, + EndAtMs: query.EndAtMS, + TzOffsetMinutes: query.TzOffsetMinutes, }) if err != nil { - writeWalletError(c, err, "获取财务概览失败") - return + return rechargeBillOverviewDTO{}, err } overview.Daily = make([]rechargeBillDailyBucketDTO, 0, len(resp.GetDaily())) for _, bucket := range resp.GetDaily() { @@ -179,20 +316,71 @@ func (h *Handler) GetRechargeBillOverview(c *gin.Context) { overview.Withdrawal.TransferUsdMinor = transfer.GetTransferUsdMinor() } } - if normalizedRechargeType(financeQuery.RechargeType) == "" { - ordinaryRegions, err := h.walletOrdinaryRechargeOverviewRegions(c.Request.Context(), appCode, financeQuery) + if includeOrdinary && includeRegions && normalizedRechargeType(query.RechargeType) == "" { + ordinaryRegions, err := h.walletOrdinaryRechargeOverviewRegions(ctx, appCode, query) if err != nil { - writeWalletError(c, err, "获取财务概览失败") - return + return rechargeBillOverviewDTO{}, err } - overview.Regions = h.fillFinanceCoinSellerRegionNames(c.Request.Context(), appCode, ordinaryRegions) + overview.Regions = h.fillFinanceCoinSellerRegionNames(ctx, appCode, ordinaryRegions) } - if err := h.applyFinanceCoinSellerOverview(c.Request.Context(), appCode, financeQuery, &overview); err != nil { - response.ServerError(c, "获取财务概览失败") - return + if err := h.applyFinanceCoinSellerOverview(ctx, appCode, query, &overview); err != nil { + return rechargeBillOverviewDTO{}, err } - h.fillApprovedWithdrawalStats(&overview, appCode, startAtMS, endAtMS) - response.OK(c, overview) + return overview, nil +} + +func financeRechargeDailyBuckets(rows []rechargeBillDailyBucketDTO) []FinanceRechargeDailyBucket { + out := make([]FinanceRechargeDailyBucket, 0, len(rows)) + for _, row := range rows { + if strings.TrimSpace(row.Date) == "" { + continue + } + out = append(out, FinanceRechargeDailyBucket{ + Date: row.Date, + GoogleUSDMinor: row.GoogleUsdMinor, + ThirdPartyUSDMinor: row.ThirdPartyUsdMinor, + CoinSellerUSDMinor: row.CoinSellerUsdMinor, + }) + } + sort.Slice(out, func(i, j int) bool { return out[i].Date < out[j].Date }) + return out +} + +func mergeFinanceRechargeRegionDaily(regions []FinanceRechargeRegionOverview) []FinanceRechargeDailyBucket { + byDay := map[string]FinanceRechargeDailyBucket{} + for _, region := range regions { + for _, row := range region.Daily { + current := byDay[row.Date] + current.Date = row.Date + current.GoogleUSDMinor += row.GoogleUSDMinor + current.ThirdPartyUSDMinor += row.ThirdPartyUSDMinor + current.CoinSellerUSDMinor += row.CoinSellerUSDMinor + byDay[row.Date] = current + } + } + out := make([]FinanceRechargeDailyBucket, 0, len(byDay)) + for _, row := range byDay { + out = append(out, row) + } + sort.Slice(out, func(i, j int) bool { return out[i].Date < out[j].Date }) + return out +} + +func uniquePositiveInt64s(values []int64) []int64 { + seen := map[int64]struct{}{} + out := make([]int64, 0, len(values)) + for _, value := range values { + if value <= 0 { + continue + } + if _, ok := seen[value]; ok { + continue + } + seen[value] = struct{}{} + out = append(out, value) + } + sort.Slice(out, func(i, j int) bool { return out[i] < out[j] }) + return out } func (h *Handler) requireLegacyCoinSellerDayRange(c *gin.Context, query legacyRechargeBillQuery, tzOffsetMinutes int32) bool { diff --git a/server/admin/internal/modules/pointwithdrawalconfig/dto.go b/server/admin/internal/modules/pointwithdrawalconfig/dto.go new file mode 100644 index 00000000..e2aa327e --- /dev/null +++ b/server/admin/internal/modules/pointwithdrawalconfig/dto.go @@ -0,0 +1,26 @@ +package pointwithdrawalconfig + +type itemDTO struct { + AppCode string `json:"appCode"` + SellerUserID string `json:"sellerUserId"` + DisplayUserID string `json:"displayUserId"` + Nickname string `json:"nickname"` + Avatar string `json:"avatar"` + CountryName string `json:"countryName"` + SortOrder int `json:"sortOrder"` + PointAmount int64 `json:"pointAmount"` + SellerCoinAmount int64 `json:"sellerCoinAmount"` + ServiceCountryCodes []string `json:"serviceCountryCodes"` + Status string `json:"status"` + CreatedAtMS int64 `json:"createdAtMs"` + UpdatedAtMS int64 `json:"updatedAtMs"` +} + +type upsertRequest struct { + SellerUserID int64 `json:"sellerUserId" binding:"required"` + SortOrder int `json:"sortOrder"` + PointAmount int64 `json:"pointAmount" binding:"required"` + SellerCoinAmount int64 `json:"sellerCoinAmount" binding:"required"` + ServiceCountryCodes []string `json:"serviceCountryCodes"` + Status string `json:"status"` +} diff --git a/server/admin/internal/modules/pointwithdrawalconfig/handler.go b/server/admin/internal/modules/pointwithdrawalconfig/handler.go new file mode 100644 index 00000000..cdca4178 --- /dev/null +++ b/server/admin/internal/modules/pointwithdrawalconfig/handler.go @@ -0,0 +1,66 @@ +package pointwithdrawalconfig + +import ( + "database/sql" + "errors" + "strconv" + "strings" + + "hyapp-admin-server/internal/appctx" + "hyapp-admin-server/internal/modules/shared" + "hyapp-admin-server/internal/response" + + "github.com/gin-gonic/gin" +) + +type Handler struct { + service *Service + audit shared.OperationLogger +} + +func New(walletDB *sql.DB, userDB *sql.DB, audit shared.OperationLogger) *Handler { + return &Handler{service: NewService(walletDB, userDB), audit: audit} +} + +func (h *Handler) List(c *gin.Context) { + items, err := h.service.List(c.Request.Context(), appctx.FromContext(c.Request.Context())) + if err != nil { + response.ServerError(c, "获取币商提现白名单失败") + return + } + response.OK(c, map[string]any{"items": items}) +} + +func (h *Handler) Upsert(c *gin.Context) { + var req upsertRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "币商提现配置参数不正确") + return + } + item, err := h.service.Upsert(c.Request.Context(), appctx.FromContext(c.Request.Context()), req, int64(shared.ActorFromContext(c).UserID)) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + shared.OperationLogWithResourceID(c, h.audit, "point-withdrawal-coin-seller-upsert", "point_withdrawal_coin_seller_configs", item.AppCode+":"+item.SellerUserID, "success", "ratio="+strconv.FormatInt(item.PointAmount, 10)+":"+strconv.FormatInt(item.SellerCoinAmount, 10)) + response.OK(c, item) +} + +func (h *Handler) Delete(c *gin.Context) { + sellerUserID, err := strconv.ParseInt(strings.TrimSpace(c.Param("seller_user_id")), 10, 64) + if err != nil || sellerUserID <= 0 { + response.BadRequest(c, "币商 ID 不正确") + return + } + appCode := appctx.FromContext(c.Request.Context()) + if err := h.service.Delete(c.Request.Context(), appCode, sellerUserID); err != nil { + if errors.Is(err, sql.ErrNoRows) { + response.NotFound(c, "配置不存在") + return + } + response.ServerError(c, "删除币商提现配置失败") + return + } + shared.OperationLogWithResourceID(c, h.audit, "point-withdrawal-coin-seller-delete", "point_withdrawal_coin_seller_configs", appCode+":"+strconv.FormatInt(sellerUserID, 10), "success", "") + response.OK(c, map[string]any{"deleted": true}) +} diff --git a/server/admin/internal/modules/pointwithdrawalconfig/routes.go b/server/admin/internal/modules/pointwithdrawalconfig/routes.go new file mode 100644 index 00000000..aeeb556b --- /dev/null +++ b/server/admin/internal/modules/pointwithdrawalconfig/routes.go @@ -0,0 +1,16 @@ +package pointwithdrawalconfig + +import ( + "hyapp-admin-server/internal/middleware" + + "github.com/gin-gonic/gin" +) + +func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { + if h == nil { + return + } + protected.GET("/admin/point-withdrawal/coin-sellers", middleware.RequirePermission("point-withdrawal-config:view"), h.List) + protected.PUT("/admin/point-withdrawal/coin-sellers", middleware.RequirePermission("point-withdrawal-config:update"), h.Upsert) + protected.DELETE("/admin/point-withdrawal/coin-sellers/:seller_user_id", middleware.RequirePermission("point-withdrawal-config:update"), h.Delete) +} diff --git a/server/admin/internal/modules/pointwithdrawalconfig/service.go b/server/admin/internal/modules/pointwithdrawalconfig/service.go new file mode 100644 index 00000000..2a0568a8 --- /dev/null +++ b/server/admin/internal/modules/pointwithdrawalconfig/service.go @@ -0,0 +1,201 @@ +package pointwithdrawalconfig + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "sort" + "strconv" + "strings" + "time" +) + +type Service struct { + walletDB *sql.DB + userDB *sql.DB +} + +func NewService(walletDB *sql.DB, userDB *sql.DB) *Service { + return &Service{walletDB: walletDB, userDB: userDB} +} + +// List 按 app_code 读取同一套公共配置表,并补齐用户资料;孤儿配置仍返回,便于运营删除而不是静默隐藏。 +func (s *Service) List(ctx context.Context, appCode string) ([]itemDTO, error) { + if err := s.ensureSchema(ctx); err != nil { + return nil, err + } + rows, err := s.walletDB.QueryContext(ctx, ` + SELECT app_code, seller_user_id, sort_order, point_amount, seller_coin_amount, + service_country_codes, status, created_at_ms, updated_at_ms + FROM point_withdrawal_coin_seller_configs + WHERE app_code = ? + ORDER BY sort_order ASC, seller_user_id ASC`, normalizeAppCode(appCode)) + if err != nil { + return nil, err + } + defer rows.Close() + items := make([]itemDTO, 0) + for rows.Next() { + var item itemDTO + var sellerUserID int64 + var countriesJSON []byte + if err := rows.Scan(&item.AppCode, &sellerUserID, &item.SortOrder, &item.PointAmount, &item.SellerCoinAmount, &countriesJSON, &item.Status, &item.CreatedAtMS, &item.UpdatedAtMS); err != nil { + return nil, err + } + item.SellerUserID = strconv.FormatInt(sellerUserID, 10) + if err := json.Unmarshal(countriesJSON, &item.ServiceCountryCodes); err != nil { + return nil, fmt.Errorf("parse service countries for seller %d: %w", sellerUserID, err) + } + item.ServiceCountryCodes = normalizeCountries(item.ServiceCountryCodes) + displayUserID, nickname, avatar, countryName, lookupErr := s.lookupSeller(ctx, item.AppCode, sellerUserID, false) + if lookupErr != nil && !errors.Is(lookupErr, sql.ErrNoRows) { + return nil, lookupErr + } + item.DisplayUserID, item.Nickname, item.Avatar, item.CountryName = displayUserID, nickname, avatar, countryName + items = append(items, item) + } + return items, rows.Err() +} + +// Upsert 在写白名单前验证同 App 的 active 币商身份,防止普通用户被配置成账务收款方。 +func (s *Service) Upsert(ctx context.Context, appCode string, req upsertRequest, adminID int64) (itemDTO, error) { + appCode = normalizeAppCode(appCode) + if req.SellerUserID <= 0 || req.PointAmount <= 0 || req.SellerCoinAmount <= 0 || req.SortOrder < 0 { + return itemDTO{}, errors.New("币商、排序和兑换比例必须是正数") + } + status := strings.ToLower(strings.TrimSpace(req.Status)) + if status == "" { + status = "active" + } + if status != "active" && status != "disabled" { + return itemDTO{}, errors.New("状态只能是 active 或 disabled") + } + if _, _, _, _, err := s.lookupSeller(ctx, appCode, req.SellerUserID, true); err != nil { + return itemDTO{}, err + } + if err := s.ensureSchema(ctx); err != nil { + return itemDTO{}, err + } + countries := normalizeCountries(req.ServiceCountryCodes) + countriesJSON, err := json.Marshal(countries) + if err != nil { + return itemDTO{}, err + } + nowMS := time.Now().UTC().UnixMilli() + _, err = s.walletDB.ExecContext(ctx, ` + INSERT INTO point_withdrawal_coin_seller_configs ( + app_code, seller_user_id, sort_order, point_amount, seller_coin_amount, service_country_codes, + status, created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + sort_order = VALUES(sort_order), point_amount = VALUES(point_amount), + seller_coin_amount = VALUES(seller_coin_amount), service_country_codes = VALUES(service_country_codes), + status = VALUES(status), updated_by_admin_id = VALUES(updated_by_admin_id), updated_at_ms = VALUES(updated_at_ms)`, + appCode, req.SellerUserID, req.SortOrder, req.PointAmount, req.SellerCoinAmount, countriesJSON, + status, adminID, adminID, nowMS, nowMS) + if err != nil { + return itemDTO{}, err + } + return s.Get(ctx, appCode, req.SellerUserID) +} + +func (s *Service) Get(ctx context.Context, appCode string, sellerUserID int64) (itemDTO, error) { + items, err := s.List(ctx, appCode) + if err != nil { + return itemDTO{}, err + } + needle := strconv.FormatInt(sellerUserID, 10) + for _, item := range items { + if item.SellerUserID == needle { + return item, nil + } + } + return itemDTO{}, sql.ErrNoRows +} + +func (s *Service) Delete(ctx context.Context, appCode string, sellerUserID int64) error { + if sellerUserID <= 0 { + return errors.New("币商 ID 不正确") + } + if err := s.ensureSchema(ctx); err != nil { + return err + } + result, err := s.walletDB.ExecContext(ctx, `DELETE FROM point_withdrawal_coin_seller_configs WHERE app_code = ? AND seller_user_id = ?`, normalizeAppCode(appCode), sellerUserID) + if err != nil { + return err + } + affected, err := result.RowsAffected() + if err != nil { + return err + } + if affected == 0 { + return sql.ErrNoRows + } + return nil +} + +func (s *Service) lookupSeller(ctx context.Context, appCode string, sellerUserID int64, requireActive bool) (displayUserID string, nickname string, avatar string, countryName string, err error) { + if s.userDB == nil { + return "", "", "", "", errors.New("user db is not configured") + } + query := ` + SELECT COALESCE(u.current_display_user_id, ''), COALESCE(u.username, ''), COALESCE(u.avatar, ''), + COALESCE(c.country_display_name, c.country_name, u.country, '') + FROM coin_seller_profiles csp + JOIN users u ON u.user_id = csp.user_id AND u.app_code = csp.app_code + LEFT JOIN countries c ON c.app_code = u.app_code AND c.country_code = u.country + WHERE csp.app_code = ? AND csp.user_id = ?` + if requireActive { + query += " AND csp.status = 'active' AND u.status = 'active'" + } + query += " LIMIT 1" + err = s.userDB.QueryRowContext(ctx, query, appCode, sellerUserID).Scan(&displayUserID, &nickname, &avatar, &countryName) + if errors.Is(err, sql.ErrNoRows) && requireActive { + return "", "", "", "", errors.New("目标用户不是当前 App 的启用币商") + } + return +} + +func (s *Service) ensureSchema(ctx context.Context) error { + if s == nil || s.walletDB == nil { + return errors.New("wallet db is not configured") + } + _, err := s.walletDB.ExecContext(ctx, ` + CREATE TABLE IF NOT EXISTS point_withdrawal_coin_seller_configs ( + app_code VARCHAR(32) NOT NULL, seller_user_id BIGINT NOT NULL, sort_order INT NOT NULL DEFAULT 0, + point_amount BIGINT NOT NULL, seller_coin_amount BIGINT NOT NULL, service_country_codes JSON NOT NULL, + status VARCHAR(24) NOT NULL DEFAULT 'active', created_by_admin_id BIGINT UNSIGNED NOT NULL DEFAULT 0, + updated_by_admin_id BIGINT UNSIGNED NOT NULL DEFAULT 0, created_at_ms BIGINT NOT NULL, updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, seller_user_id), + KEY idx_point_withdraw_seller_list (app_code, status, sort_order, seller_user_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='POINT 提现币商白名单与兑换比例'`) + return err +} + +func normalizeCountries(values []string) []string { + seen := map[string]struct{}{} + out := make([]string, 0, len(values)) + for _, value := range values { + value = strings.ToUpper(strings.TrimSpace(value)) + if value == "" || len(value) > 16 { + continue + } + if _, exists := seen[value]; exists { + continue + } + seen[value] = struct{}{} + out = append(out, value) + } + sort.Strings(out) + return out +} + +func normalizeAppCode(value string) string { + value = strings.ToLower(strings.TrimSpace(value)) + if value == "" { + return "lalu" + } + return value +} diff --git a/server/admin/internal/modules/pointwithdrawalconfig/service_test.go b/server/admin/internal/modules/pointwithdrawalconfig/service_test.go new file mode 100644 index 00000000..be8b8b36 --- /dev/null +++ b/server/admin/internal/modules/pointwithdrawalconfig/service_test.go @@ -0,0 +1,23 @@ +package pointwithdrawalconfig + +import ( + "reflect" + "testing" +) + +func TestNormalizeCountriesCanonicalizesWhitelist(t *testing.T) { + got := normalizeCountries([]string{" sa ", "TR", "sa", "", "country-code-that-is-too-long"}) + want := []string{"SA", "TR"} + if !reflect.DeepEqual(got, want) { + t.Fatalf("normalizeCountries() = %v, want %v", got, want) + } +} + +func TestNormalizeAppCodeKeepsLegacyDefault(t *testing.T) { + if got := normalizeAppCode(""); got != "lalu" { + t.Fatalf("empty app code = %q, want lalu", got) + } + if got := normalizeAppCode(" FAMI "); got != "fami" { + t.Fatalf("Fami app code = %q, want fami", got) + } +} diff --git a/server/admin/internal/modules/policyconfig/service.go b/server/admin/internal/modules/policyconfig/service.go index 773fb706..2dd799ab 100644 --- a/server/admin/internal/modules/policyconfig/service.go +++ b/server/admin/internal/modules/policyconfig/service.go @@ -10,6 +10,7 @@ import ( "strings" "time" + mysqlDriver "github.com/go-sql-driver/mysql" "hyapp-admin-server/internal/appctx" "hyapp-admin-server/internal/integration/activityclient" activityv1 "hyapp.local/api/proto/activity/v1" @@ -64,6 +65,7 @@ type policyInstanceRow struct { type compiledPolicy struct { HostPointRatioPPM int64 + AgencyPointRatioPPM int64 PointsPerUSD int64 WithdrawFeeBPS int64 TaskRewardAssetType string @@ -496,12 +498,12 @@ func (s *Service) publishWalletRuntime(ctx context.Context, item policyInstanceR if _, err := tx.ExecContext(ctx, ` INSERT INTO wallet_policy_instances ( app_code, instance_code, template_code, template_version, region_id, status, - effective_from_ms, effective_to_ms, host_point_asset_type, host_point_ratio_ppm, + effective_from_ms, effective_to_ms, host_point_asset_type, host_point_ratio_ppm, agency_point_ratio_ppm, points_per_usd, withdraw_fee_bps, rule_json, published_by_admin_id, published_at_ms, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'POINT', ?, ?, ?, CAST(? AS JSON), ?, ?, ?, ?)`, + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'POINT', ?, ?, ?, ?, CAST(? AS JSON), ?, ?, ?, ?)`, item.AppCode, item.InstanceCode, item.TemplateCode, item.TemplateVersion, regionID, item.Status, - item.EffectiveFromMS, item.EffectiveToMS, compiled.HostPointRatioPPM, compiled.PointsPerUSD, + item.EffectiveFromMS, item.EffectiveToMS, compiled.HostPointRatioPPM, compiled.AgencyPointRatioPPM, compiled.PointsPerUSD, compiled.WithdrawFeeBPS, string(compiled.RuleJSON), actorID, nowMS, nowMS, nowMS, ); err != nil { return err @@ -563,6 +565,7 @@ func ensureWalletPolicyRuntimeTable(ctx context.Context, db *sql.DB) error { effective_to_ms BIGINT NOT NULL DEFAULT 0 COMMENT '0 表示长期有效', host_point_asset_type VARCHAR(32) NOT NULL DEFAULT 'POINT' COMMENT '主播收益积分资产', host_point_ratio_ppm BIGINT NOT NULL DEFAULT 700000 COMMENT '有效付费礼物转 POINT 比例,ppm', + agency_point_ratio_ppm BIGINT NOT NULL DEFAULT 0 COMMENT '有效付费礼物给 Agency owner 的 POINT 分成比例,ppm', points_per_usd BIGINT NOT NULL DEFAULT 100000 COMMENT 'POINT/USD 展示换算比例', withdraw_fee_bps INT NOT NULL DEFAULT 500 COMMENT '提现手续费 bps', rule_json JSON NOT NULL COMMENT '完整政策快照', @@ -573,7 +576,19 @@ func ensureWalletPolicyRuntimeTable(ctx context.Context, db *sql.DB) error { PRIMARY KEY (app_code, instance_code, region_id), KEY idx_wallet_policy_active (app_code, region_id, status, effective_from_ms, effective_to_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='钱包运行侧收益政策实例表'`) - return err + if err != nil { + return err + } + // 运行库可能是旧版本建表;幂等补列保证发布新策略时不会因列缺失中断。 + if _, err := db.ExecContext(ctx, `ALTER TABLE wallet_policy_instances ADD COLUMN agency_point_ratio_ppm BIGINT NOT NULL DEFAULT 0 COMMENT '有效付费礼物给 Agency owner 的 POINT 分成比例,ppm' AFTER host_point_ratio_ppm`); err != nil && !isDuplicateColumnError(err) { + return err + } + return nil +} + +func isDuplicateColumnError(err error) bool { + var mysqlErr *mysqlDriver.MySQLError + return errors.As(err, &mysqlErr) && mysqlErr.Number == 1060 } func (s *Service) insertPublishItems(ctx context.Context, jobID uint64, appCode string, regionIDs []int64, activitySucceeded bool, nowMS int64) error { @@ -635,11 +650,15 @@ func compilePolicy(raw json.RawMessage) (compiledPolicy, error) { pointsPerUSD := int64FromJSON(decoded["points_per_usd"], 100000) host, _ := decoded["host"].(map[string]any) hostRatioPercent := floatFromJSON(host["point_ratio_percent"], 70) + agency, _ := decoded["agency"].(map[string]any) + // Agency 比例由跨 App 共用的策略模板显式配置;旧模板缺字段时保持 0,避免发布动作隐式改变存量 App 账务。 + agencyRatioPercent := floatFromJSON(agency["point_ratio_percent"], 0) withdrawFeeBPS := int64FromJSON(host["withdraw_fee_bps"], 500) ratioPPM := int64(math.Round(hostRatioPercent * 10000)) + agencyRatioPPM := int64(math.Round(agencyRatioPercent * 10000)) tasks, _ := decoded["tasks"].(map[string]any) taskRewardAssetType := strings.ToUpper(strings.TrimSpace(stringFromJSON(tasks["reward_asset_type"], policyTaskRewardAssetCoin))) - if pointsPerUSD <= 0 || ratioPPM <= 0 || ratioPPM > 1000000 || withdrawFeeBPS < 0 || withdrawFeeBPS > 10000 { + if pointsPerUSD <= 0 || ratioPPM <= 0 || ratioPPM > 1000000 || agencyRatioPPM < 0 || agencyRatioPPM > 1000000 || ratioPPM+agencyRatioPPM > 1000000 || withdrawFeeBPS < 0 || withdrawFeeBPS > 10000 { return compiledPolicy{}, errors.New("rule_json host point policy is invalid") } if taskRewardAssetType != policyTaskRewardAssetCoin && taskRewardAssetType != "POINT" { @@ -647,6 +666,7 @@ func compilePolicy(raw json.RawMessage) (compiledPolicy, error) { } return compiledPolicy{ HostPointRatioPPM: ratioPPM, + AgencyPointRatioPPM: agencyRatioPPM, PointsPerUSD: pointsPerUSD, WithdrawFeeBPS: withdrawFeeBPS, TaskRewardAssetType: taskRewardAssetType, diff --git a/server/admin/internal/modules/policyconfig/service_test.go b/server/admin/internal/modules/policyconfig/service_test.go index 4a23b072..603c8698 100644 --- a/server/admin/internal/modules/policyconfig/service_test.go +++ b/server/admin/internal/modules/policyconfig/service_test.go @@ -3,6 +3,7 @@ package policyconfig import ( "context" "database/sql" + "encoding/json" "testing" "github.com/DATA-DOG/go-sqlmock" @@ -60,6 +61,7 @@ func TestPublishRuntimeWritesWalletRowsAndActivityTaskRewardPolicy(t *testing.T) } compiled := compiledPolicy{ HostPointRatioPPM: 700000, + AgencyPointRatioPPM: 0, PointsPerUSD: 100000, WithdrawFeeBPS: 500, TaskRewardAssetType: "POINT", @@ -67,6 +69,7 @@ func TestPublishRuntimeWritesWalletRowsAndActivityTaskRewardPolicy(t *testing.T) } walletMock.ExpectExec(`(?s)CREATE TABLE IF NOT EXISTS wallet_policy_instances`).WillReturnResult(sqlmock.NewResult(0, 0)) + walletMock.ExpectExec(`(?s)ALTER TABLE wallet_policy_instances ADD COLUMN agency_point_ratio_ppm`).WillReturnResult(sqlmock.NewResult(0, 0)) walletMock.ExpectBegin() walletMock.ExpectExec(`DELETE FROM wallet_policy_instances WHERE app_code = \? AND instance_code = \?`). WithArgs("huwaa", "huwaa-point-live"). @@ -83,6 +86,7 @@ func TestPublishRuntimeWritesWalletRowsAndActivityTaskRewardPolicy(t *testing.T) int64(1700000000000), int64(0), int64(700000), + int64(0), int64(100000), int64(500), string(compiled.RuleJSON), @@ -147,6 +151,7 @@ func TestUpdateInstanceStatusSyncsWalletRuntimeAndActivityRuntime(t *testing.T) WithArgs(policyStatusDisabled, uint(90002), sqlmock.AnyArg(), "huwaa", uint64(9)). WillReturnResult(sqlmock.NewResult(0, 1)) walletMock.ExpectExec(`(?s)CREATE TABLE IF NOT EXISTS wallet_policy_instances`).WillReturnResult(sqlmock.NewResult(0, 0)) + walletMock.ExpectExec(`(?s)ALTER TABLE wallet_policy_instances ADD COLUMN agency_point_ratio_ppm`).WillReturnResult(sqlmock.NewResult(0, 0)) walletMock.ExpectExec(`(?s)UPDATE wallet_policy_instances\s+SET status = \?, updated_at_ms = \?\s+WHERE app_code = \? AND instance_code = \?`). WithArgs(policyStatusDisabled, sqlmock.AnyArg(), "huwaa", "huwaa-point-live"). WillReturnResult(sqlmock.NewResult(0, 1)) @@ -191,6 +196,24 @@ func TestUpdateInstanceStatusSyncsWalletRuntimeAndActivityRuntime(t *testing.T) } } +func TestCompilePolicyUsesExplicitAgencyRatioAndKeepsLegacyDefaultZero(t *testing.T) { + ruleJSON := json.RawMessage(`{"host":{"point_ratio_percent":70}}`) + fami, err := compilePolicy(ruleJSON) + if err != nil { + t.Fatalf("compile Fami policy failed: %v", err) + } + if fami.AgencyPointRatioPPM != 0 { + t.Fatalf("legacy policy without agency section must remain zero: %+v", fami) + } + configured, err := compilePolicy(json.RawMessage(`{"host":{"point_ratio_percent":70},"agency":{"point_ratio_percent":20}}`)) + if err != nil { + t.Fatalf("compile configured policy failed: %v", err) + } + if configured.AgencyPointRatioPPM != 200000 { + t.Fatalf("configured agency ratio mismatch: %+v", configured) + } +} + type fakePolicyActivityClient struct { activityclient.Client last *activityv1.PublishTaskRewardPolicyRequest diff --git a/server/admin/internal/modules/resource/handler.go b/server/admin/internal/modules/resource/handler.go index 4bc906ae..53ce8041 100644 --- a/server/admin/internal/modules/resource/handler.go +++ b/server/admin/internal/modules/resource/handler.go @@ -639,6 +639,10 @@ func (h *Handler) GrantResource(c *gin.Context) { response.BadRequest(c, err.Error()) return } + if err := h.validateGenericGrantResource(c, req.ResourceID); err != nil { + response.BadRequest(c, err.Error()) + return + } resp, err := h.wallet.GrantResource(c.Request.Context(), &walletv1.GrantResourceRequest{ CommandId: strings.TrimSpace(req.CommandID), AppCode: appctx.FromContext(c.Request.Context()), @@ -670,6 +674,10 @@ func (h *Handler) GrantResourceGroup(c *gin.Context) { response.BadRequest(c, err.Error()) return } + if err := h.validateGenericGrantGroup(c, req.GroupID); err != nil { + response.BadRequest(c, err.Error()) + return + } resp, err := h.wallet.GrantResourceGroup(c.Request.Context(), &walletv1.GrantResourceGroupRequest{ CommandId: strings.TrimSpace(req.CommandID), AppCode: appctx.FromContext(c.Request.Context()), @@ -688,6 +696,53 @@ func (h *Handler) GrantResourceGroup(c *gin.Context) { response.Created(c, grant) } +func (h *Handler) validateGenericGrantResource(c *gin.Context, resourceID int64) error { + resp, err := h.wallet.GetResource(c.Request.Context(), &walletv1.GetResourceRequest{ + RequestId: middleware.CurrentRequestID(c), + AppCode: appctx.FromContext(c.Request.Context()), + ResourceId: resourceID, + }) + if err != nil { + return err + } + // VIP 体验卡除了资源权益,还必须同步写入卡片等级、独立失效时间和 VIP 状态投影; + // 通用资源赠送只会创建 entitlement,放行会产生“背包可见但无法生效”的半成品数据。 + if resp.GetResource().GetResourceType() == resourceTypeVIPTrialCard { + return fmt.Errorf("VIP 体验卡必须通过 VIP 配置页赠送") + } + return nil +} + +func (h *Handler) validateGenericGrantGroup(c *gin.Context, groupID int64) error { + resp, err := h.wallet.GetResourceGroup(c.Request.Context(), &walletv1.GetResourceGroupRequest{ + RequestId: middleware.CurrentRequestID(c), + AppCode: appctx.FromContext(c.Request.Context()), + GroupId: groupID, + }) + if err != nil { + return err + } + for _, item := range resp.GetGroup().GetItems() { + resourceType := item.GetResource().GetResourceType() + // 老资源组数据可能只返回 resource_id;逐项回查可防止因嵌套 Resource 缺失而绕过体验卡专用发放链路。 + if resourceType == "" && item.GetResourceId() > 0 { + resourceResp, err := h.wallet.GetResource(c.Request.Context(), &walletv1.GetResourceRequest{ + RequestId: middleware.CurrentRequestID(c), + AppCode: appctx.FromContext(c.Request.Context()), + ResourceId: item.GetResourceId(), + }) + if err != nil { + return err + } + resourceType = resourceResp.GetResource().GetResourceType() + } + if resourceType == resourceTypeVIPTrialCard { + return fmt.Errorf("包含 VIP 体验卡的资源组必须通过 VIP 配置页赠送") + } + } + return nil +} + func (h *Handler) RevokeResourceGrant(c *gin.Context) { grantID := strings.TrimSpace(c.Param("grant_id")) if grantID == "" { diff --git a/server/admin/internal/modules/resource/handler_test.go b/server/admin/internal/modules/resource/handler_test.go index 5ff4c77c..5924530e 100644 --- a/server/admin/internal/modules/resource/handler_test.go +++ b/server/admin/internal/modules/resource/handler_test.go @@ -220,6 +220,73 @@ func TestIdentityAutoGrantConfigRouteDoesNotHitResourceGroupID(t *testing.T) { } } +func TestGenericResourceGrantRejectsVIPTrialCard(t *testing.T) { + wallet := &mockResourceWallet{resources: map[int64]*walletv1.Resource{ + 99: {AppCode: "lalu", ResourceId: 99, ResourceCode: "vip_trial_5", ResourceType: resourceTypeVIPTrialCard, Name: "VIP5体验卡"}, + }} + router := newResourceHandlerTestRouter(New(wallet, nil, nil, time.Second, nil)) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPost, "/admin/resource-grants/resource", strings.NewReader(`{"commandId":"grant-1","targetUserId":"318705991371722752","resourceId":99,"quantity":1,"durationMs":86400000,"reason":"manual"}`)) + request.Header.Set("Content-Type", "application/json") + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusBadRequest || !strings.Contains(recorder.Body.String(), "VIP 体验卡必须通过 VIP 配置页赠送") { + t.Fatalf("generic grant must reject vip trial card: status=%d body=%s", recorder.Code, recorder.Body.String()) + } +} + +func TestGenericResourceGroupGrantRejectsVIPTrialCardItem(t *testing.T) { + wallet := &mockResourceWallet{resourceGroups: map[int64]*walletv1.ResourceGroup{ + 88: { + AppCode: "lalu", + GroupId: 88, + Items: []*walletv1.ResourceGroupItem{{ + ResourceId: 99, + Resource: &walletv1.Resource{ResourceId: 99, ResourceType: resourceTypeVIPTrialCard}, + }}, + }, + }} + router := newResourceHandlerTestRouter(New(wallet, nil, nil, time.Second, nil)) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPost, "/admin/resource-grants/group", strings.NewReader(`{"commandId":"grant-group-1","targetUserId":"318705991371722752","groupId":88,"reason":"manual"}`)) + request.Header.Set("Content-Type", "application/json") + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusBadRequest || !strings.Contains(recorder.Body.String(), "包含 VIP 体验卡的资源组必须通过 VIP 配置页赠送") { + t.Fatalf("generic group grant must reject vip trial card: status=%d body=%s", recorder.Code, recorder.Body.String()) + } +} + +func TestVIPGrantPermissionCanLookupResourceGrantTarget(t *testing.T) { + db, sqlMock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sql mock failed: %v", err) + } + defer db.Close() + sqlMock.ExpectQuery(`(?s)SELECT u\.user_id.*FROM users u.*LIMIT 1`). + WillReturnRows(sqlmock.NewRows([]string{"user_id", "current_display_user_id", "username", "avatar"}).AddRow(int64(318705991371722752), "111", "target", "avatar.png")) + + gin.SetMode(gin.TestMode) + router := gin.New() + router.Use(func(c *gin.Context) { + c.Request = c.Request.WithContext(appctx.WithContext(c.Request.Context(), "lalu")) + c.Set(middleware.ContextRequestID, "vip-target-test") + c.Set(middleware.ContextPermissions, []string{"vip-config:grant"}) + c.Next() + }) + RegisterRoutes(router.Group(""), New(&mockResourceWallet{}, nil, db, time.Second, nil)) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/admin/resource-grants/target?keyword=111", nil) + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK || !strings.Contains(recorder.Body.String(), `"userId":"318705991371722752"`) { + t.Fatalf("vip grant permission should resolve target: status=%d body=%s", recorder.Code, recorder.Body.String()) + } + if err := sqlMock.ExpectationsWereMet(); err != nil { + t.Fatalf("lookup sql mismatch: %v", err) + } +} + func TestListResourceGrantsResolvesDisplayIDBeforeWalletFilter(t *testing.T) { db, sqlMock, err := sqlmock.New() if err != nil { @@ -304,6 +371,7 @@ func newResourceHandlerTestRouter(handler *Handler) *gin.Engine { type mockResourceWallet struct { walletclient.Client resources map[int64]*walletv1.Resource + resourceGroups map[int64]*walletv1.ResourceGroup updates []*walletv1.UpdateResourceRequest deletedResources []*walletv1.DeleteResourceRequest batchDeletedResources []*walletv1.BatchDeleteResourcesRequest @@ -320,6 +388,14 @@ func (m *mockResourceWallet) GetResource(ctx context.Context, req *walletv1.GetR return &walletv1.GetResourceResponse{Resource: resource}, nil } +func (m *mockResourceWallet) GetResourceGroup(_ context.Context, req *walletv1.GetResourceGroupRequest) (*walletv1.GetResourceGroupResponse, error) { + group := m.resourceGroups[req.GetGroupId()] + if group == nil { + return nil, fmt.Errorf("resource group not found") + } + return &walletv1.GetResourceGroupResponse{Group: group}, nil +} + func (m *mockResourceWallet) UpdateResource(ctx context.Context, req *walletv1.UpdateResourceRequest) (*walletv1.ResourceResponse, error) { m.updates = append(m.updates, req) return &walletv1.ResourceResponse{Resource: &walletv1.Resource{ diff --git a/server/admin/internal/modules/resource/request.go b/server/admin/internal/modules/resource/request.go index 4680e45a..c10ae0be 100644 --- a/server/admin/internal/modules/resource/request.go +++ b/server/admin/internal/modules/resource/request.go @@ -18,11 +18,12 @@ import ( const dayMillis int64 = 24 * 60 * 60 * 1000 const ( - resourceTypeCoin = "coin" - resourceTypeAvatarFrame = "avatar_frame" - resourceTypeBadge = "badge" - resourceTypeEmojiPack = "emoji_pack" - resourceTypeProfileCard = "profile_card" + resourceTypeCoin = "coin" + resourceTypeAvatarFrame = "avatar_frame" + resourceTypeBadge = "badge" + resourceTypeEmojiPack = "emoji_pack" + resourceTypeProfileCard = "profile_card" + resourceTypeVIPTrialCard = "vip_trial_card" ) const ( diff --git a/server/admin/internal/modules/resource/routes.go b/server/admin/internal/modules/resource/routes.go index ce6aeaf3..0c4b6e76 100644 --- a/server/admin/internal/modules/resource/routes.go +++ b/server/admin/internal/modules/resource/routes.go @@ -49,7 +49,8 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { protected.POST("/admin/resource-grants/resource", middleware.RequirePermission("resource-grant:create"), h.GrantResource) protected.POST("/admin/resource-grants/group", middleware.RequirePermission("resource-grant:create"), h.GrantResourceGroup) protected.POST("/admin/resource-grants/:grant_id/revoke", middleware.RequirePermission("resource-grant:revoke"), h.RevokeResourceGrant) - protected.GET("/admin/resource-grants/target", middleware.RequirePermission("resource-grant:create"), h.LookupResourceGrantTarget) + // VIP 赠送复用同一目标用户解析入口;仅有 vip-config:grant 的运营也必须能把短号解析成内部 user_id。 + protected.GET("/admin/resource-grants/target", middleware.RequireAnyPermission("resource-grant:create", "vip-config:grant"), h.LookupResourceGrantTarget) protected.GET("/admin/resource-grants", middleware.RequirePermission("resource-grant:view"), h.ListResourceGrants) protected.GET("/admin/resource-shop/purchase-orders", middleware.RequirePermission("resource-shop:view"), h.ListResourceShopPurchaseOrders) diff --git a/server/admin/internal/modules/shared/audit.go b/server/admin/internal/modules/shared/audit.go index 3d283b3b..0eba41f0 100644 --- a/server/admin/internal/modules/shared/audit.go +++ b/server/admin/internal/modules/shared/audit.go @@ -42,7 +42,25 @@ func OperationLogWithResourceID(c *gin.Context, logger OperationLogger, action s } // MarkAuditLogged keeps middleware from creating a second generic row for this write. middleware.MarkAuditLogged(c) - _ = logger.LogOperation(OperationEvent{ + _ = logger.LogOperation(operationEvent(c, action, resource, resourceID, status, detail)) +} + +// OperationLogWithResourceIDStrict 是 fail-closed 版本:审计写失败时返回错误、且不抑制 +// middleware 的兜底审计行。用于“操作本身在别处不留任何痕迹”的高危动作(如重签用户 token)—— +// 这类动作宁可失败,也不能在没有任何审计记录的情况下成功。 +func OperationLogWithResourceIDStrict(c *gin.Context, logger OperationLogger, action string, resource string, resourceID string, status string, detail string) error { + if logger == nil { + return nil + } + if err := logger.LogOperation(operationEvent(c, action, resource, resourceID, status, detail)); err != nil { + return err + } + middleware.MarkAuditLogged(c) + return nil +} + +func operationEvent(c *gin.Context, action string, resource string, resourceID string, status string, detail string) OperationEvent { + return OperationEvent{ RequestID: middleware.CurrentRequestID(c), UserID: middleware.CurrentUserID(c), Username: middleware.CurrentUsername(c), @@ -56,5 +74,5 @@ func OperationLogWithResourceID(c *gin.Context, logger OperationLogger, action s Status: status, HTTPStatus: c.Writer.Status(), Detail: detail, - }) + } } diff --git a/server/admin/internal/modules/vipconfig/handler.go b/server/admin/internal/modules/vipconfig/handler.go index e8e30488..d0bf5a66 100644 --- a/server/admin/internal/modules/vipconfig/handler.go +++ b/server/admin/internal/modules/vipconfig/handler.go @@ -27,6 +27,10 @@ type configRequest struct { Levels []vipLevelDTO `json:"levels"` } +type programRequest struct { + Config *vipProgramConfigDTO `json:"config"` +} + type grantVipRequest struct { CommandID string `json:"commandId"` TargetUserID any `json:"targetUserId"` @@ -34,9 +38,25 @@ type grantVipRequest struct { Reason string `json:"reason"` } +type grantVipTrialCardRequest struct { + CommandID string `json:"commandId"` + TargetUserID any `json:"targetUserId"` + Level int32 `json:"level"` + DurationMS int64 `json:"durationMs"` + ResourceID *int64 `json:"resourceId"` + Reason string `json:"reason"` +} + type configDTO struct { - Levels []vipLevelDTO `json:"levels"` - ServerTimeMS int64 `json:"serverTimeMs"` + Levels []vipLevelDTO `json:"levels"` + ProgramConfig vipProgramConfigDTO `json:"programConfig"` + ServerTimeMS int64 `json:"serverTimeMs"` +} + +type programDTO struct { + Config vipProgramConfigDTO `json:"config"` + Benefits []vipBenefitDTO `json:"benefits"` + ServerTimeMS int64 `json:"serverTimeMs"` } type grantVipDTO struct { @@ -46,30 +66,103 @@ type grantVipDTO struct { ServerTimeMS int64 `json:"serverTimeMs"` } +type grantVipTrialCardDTO struct { + TrialCard vipTrialCardDTO `json:"trialCard"` + State vipStateDTO `json:"state"` + ServerTimeMS int64 `json:"serverTimeMs"` +} + type userVipDTO struct { - UserID string `json:"userId"` - Level int32 `json:"level"` - Name string `json:"name"` - Active bool `json:"active"` - StartedAtMS int64 `json:"startedAtMs"` - ExpiresAtMS int64 `json:"expiresAtMs"` - UpdatedAtMS int64 `json:"updatedAtMs"` + UserID string `json:"userId"` + Level int32 `json:"level"` + Name string `json:"name"` + Active bool `json:"active"` + StartedAtMS int64 `json:"startedAtMs"` + ExpiresAtMS int64 `json:"expiresAtMs"` + UpdatedAtMS int64 `json:"updatedAtMs"` + ProgramType string `json:"programType"` + ConfigVersion int64 `json:"configVersion"` } type vipLevelDTO struct { - Level int32 `json:"level"` - Name string `json:"name"` - Status string `json:"status"` - PriceCoin int64 `json:"priceCoin"` - DurationMS int64 `json:"durationMs"` - RewardResourceGroupID int64 `json:"rewardResourceGroupId"` - SortOrder int32 `json:"sortOrder"` - RechargeGateRequired bool `json:"rechargeGateRequired"` - RequiredRechargeCoinAmount int64 `json:"requiredRechargeCoinAmount"` - UserRechargeCoinAmount int64 `json:"userRechargeCoinAmount"` - PurchaseLockedReason string `json:"purchaseLockedReason"` - CreatedAtMS int64 `json:"createdAtMs"` - UpdatedAtMS int64 `json:"updatedAtMs"` + Level int32 `json:"level"` + Name string `json:"name"` + Status string `json:"status"` + PriceCoin int64 `json:"priceCoin"` + DurationMS int64 `json:"durationMs"` + RewardResourceGroupID int64 `json:"rewardResourceGroupId"` + SortOrder int32 `json:"sortOrder"` + RechargeGateRequired bool `json:"rechargeGateRequired"` + RequiredRechargeCoinAmount int64 `json:"requiredRechargeCoinAmount"` + UserRechargeCoinAmount int64 `json:"userRechargeCoinAmount"` + PurchaseLockedReason string `json:"purchaseLockedReason"` + CreatedAtMS int64 `json:"createdAtMs"` + UpdatedAtMS int64 `json:"updatedAtMs"` + Benefits []vipBenefitDTO `json:"benefits"` + ConfigVersion int64 `json:"configVersion"` +} + +type vipProgramConfigDTO struct { + AppCode string `json:"appCode"` + ProgramType string `json:"programType"` + LevelCount int32 `json:"levelCount"` + SameLevelExpiryPolicy string `json:"sameLevelExpiryPolicy"` + UpgradeExpiryPolicy string `json:"upgradeExpiryPolicy"` + DowngradePurchasePolicy string `json:"downgradePurchasePolicy"` + BenefitInheritancePolicy string `json:"benefitInheritancePolicy"` + GrantMode string `json:"grantMode"` + TrialCardEnabled bool `json:"trialCardEnabled"` + Status string `json:"status"` + ConfigVersion int64 `json:"configVersion"` + UpdatedByAdminID int64 `json:"updatedByAdminId"` + CreatedAtMS int64 `json:"createdAtMs"` + UpdatedAtMS int64 `json:"updatedAtMs"` +} + +type vipBenefitDTO struct { + BenefitCode string `json:"benefitCode"` + Name string `json:"name"` + BenefitType string `json:"benefitType"` + UnlockLevel int32 `json:"unlockLevel"` + Status string `json:"status"` + TrialEnabled bool `json:"trialEnabled"` + ResourceID int64 `json:"resourceId"` + ResourceType string `json:"resourceType"` + ExecutionScope string `json:"executionScope"` + AutoEquip bool `json:"autoEquip"` + SortOrder int32 `json:"sortOrder"` + MetadataJSON string `json:"metadataJson"` + CreatedAtMS int64 `json:"createdAtMs"` + UpdatedAtMS int64 `json:"updatedAtMs"` +} + +type vipTrialCardDTO struct { + TrialCardID string `json:"trialCardId"` + EntitlementID string `json:"entitlementId"` + ResourceID int64 `json:"resourceId"` + UserID string `json:"userId"` + Level int32 `json:"level"` + Name string `json:"name"` + Status string `json:"status"` + Equipped bool `json:"equipped"` + DurationMS int64 `json:"durationMs"` + EffectiveAtMS int64 `json:"effectiveAtMs"` + ExpiresAtMS int64 `json:"expiresAtMs"` + RemainingDurationMS int64 `json:"remainingDurationMs"` + GrantSource string `json:"grantSource"` + SourceGrantID string `json:"sourceGrantId"` + CreatedAtMS int64 `json:"createdAtMs"` + UpdatedAtMS int64 `json:"updatedAtMs"` +} + +type vipStateDTO struct { + PaidVIP userVipDTO `json:"paidVip"` + EquippedTrialCard vipTrialCardDTO `json:"equippedTrialCard"` + EffectiveVIP userVipDTO `json:"effectiveVip"` + EffectiveSource string `json:"effectiveSource"` + EffectiveBenefits []vipBenefitDTO `json:"effectiveBenefits"` + EvaluatedAtMS int64 `json:"evaluatedAtMs"` + ProgramConfig vipProgramConfigDTO `json:"programConfig"` } type vipRewardItemDTO struct { @@ -81,6 +174,59 @@ type vipRewardItemDTO struct { ExpiresAtMS int64 `json:"expiresAtMs"` } +func (h *Handler) GetProgram(c *gin.Context) { + resp, err := h.wallet.GetVipProgramConfig(c.Request.Context(), &walletv1.GetVipProgramConfigRequest{ + RequestId: middleware.CurrentRequestID(c), + AppCode: appctx.FromContext(c.Request.Context()), + }) + if err != nil { + response.ServerError(c, "获取 VIP 体系失败") + return + } + // 体系规则与权益目录同次返回,保证后台切换 App 后不会把上一 App 的权益矩阵误带入当前表单。 + response.OK(c, programDTO{ + Config: vipProgramConfigFromProto(resp.GetConfig()), + Benefits: vipBenefitsFromProto(resp.GetBenefits()), + ServerTimeMS: resp.GetServerTimeMs(), + }) +} + +func (h *Handler) UpdateProgram(c *gin.Context) { + var req programRequest + if err := c.ShouldBindJSON(&req); err != nil || req.Config == nil { + response.BadRequest(c, "VIP 体系参数不正确") + return + } + config := req.Config.toProto() + if config.GetLevelCount() <= 0 || strings.TrimSpace(config.GetProgramType()) == "" || strings.TrimSpace(config.GetStatus()) == "" { + response.BadRequest(c, "VIP 体系参数不正确") + return + } + appCode := appctx.FromContext(c.Request.Context()) + // AppCode、版本和审计操作者只取服务端上下文;即使客户端提交同名字段也不能跨 App 或伪造审计信息。 + config.AppCode = appCode + config.ConfigVersion = 0 + config.UpdatedByAdminId = 0 + config.CreatedAtMs = 0 + config.UpdatedAtMs = 0 + resp, err := h.wallet.UpdateAdminVipProgramConfig(c.Request.Context(), &walletv1.UpdateAdminVipProgramConfigRequest{ + RequestId: middleware.CurrentRequestID(c), + AppCode: appCode, + Config: config, + OperatorUserId: int64(middleware.CurrentUserID(c)), + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + shared.OperationLogWithResourceID(c, h.audit, "update-vip-program", "vip_program_configs", appCode, "success", resp.GetConfig().GetProgramType()) + response.OK(c, programDTO{ + Config: vipProgramConfigFromProto(resp.GetConfig()), + Benefits: []vipBenefitDTO{}, + ServerTimeMS: resp.GetServerTimeMs(), + }) +} + func (h *Handler) ListLevels(c *gin.Context) { resp, err := h.wallet.ListAdminVipLevels(c.Request.Context(), &walletv1.ListAdminVipLevelsRequest{ RequestId: middleware.CurrentRequestID(c), @@ -90,7 +236,11 @@ func (h *Handler) ListLevels(c *gin.Context) { response.ServerError(c, "获取 VIP 配置失败") return } - response.OK(c, configDTO{Levels: vipLevelsFromProto(resp.GetLevels()), ServerTimeMS: resp.GetServerTimeMs()}) + response.OK(c, configDTO{ + Levels: vipLevelsFromProto(resp.GetLevels()), + ProgramConfig: vipProgramConfigFromProto(resp.GetProgramConfig()), + ServerTimeMS: resp.GetServerTimeMs(), + }) } func (h *Handler) GrantVIP(c *gin.Context) { @@ -130,6 +280,46 @@ func (h *Handler) GrantVIP(c *gin.Context) { response.Created(c, grantVipFromProto(resp)) } +func (h *Handler) GrantVIPTrialCard(c *gin.Context) { + var req grantVipTrialCardRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "VIP 体验卡赠送参数不正确") + return + } + targetUserID, ok := parseFlexibleUserID(req.TargetUserID) + if !ok || req.Level <= 0 || req.DurationMS <= 0 || (req.ResourceID != nil && *req.ResourceID <= 0) { + response.BadRequest(c, "VIP 体验卡赠送参数不正确") + return + } + reason := strings.TrimSpace(req.Reason) + if reason == "" { + response.BadRequest(c, "赠送原因不能为空") + return + } + commandID := strings.TrimSpace(req.CommandID) + if commandID == "" { + commandID = "admin_vip_trial_card_grant:" + middleware.CurrentRequestID(c) + } + resp, err := h.wallet.GrantVipTrialCard(c.Request.Context(), &walletv1.GrantVipTrialCardRequest{ + CommandId: commandID, + AppCode: appctx.FromContext(c.Request.Context()), + TargetUserId: targetUserID, + Level: req.Level, + DurationMs: req.DurationMS, + ResourceId: req.ResourceID, + GrantSource: "admin_grant", + OperatorUserId: int64(middleware.CurrentUserID(c)), + Reason: reason, + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + // 体验卡是独立背包实例,审计目标使用 trial_card_id,后续切换佩戴不会覆盖本次赠送记录。 + shared.OperationLogWithResourceID(c, h.audit, "grant-vip-trial-card", "user_vip_trial_cards", resp.GetTrialCard().GetTrialCardId(), "success", "target_user_id="+strconv.FormatInt(targetUserID, 10)) + response.Created(c, grantVipTrialCardFromProto(resp)) +} + func (h *Handler) UpdateLevels(c *gin.Context) { var req configRequest if err := c.ShouldBindJSON(&req); err != nil { @@ -138,6 +328,10 @@ func (h *Handler) UpdateLevels(c *gin.Context) { } input := make([]*walletv1.AdminVipLevelInput, 0, len(req.Levels)) for _, level := range req.Levels { + benefits := make([]*walletv1.VipBenefit, 0, len(level.Benefits)) + for _, benefit := range level.Benefits { + benefits = append(benefits, benefit.toProto()) + } input = append(input, &walletv1.AdminVipLevelInput{ Level: level.Level, Name: strings.TrimSpace(level.Name), @@ -148,6 +342,7 @@ func (h *Handler) UpdateLevels(c *gin.Context) { // VIP 累充门槛已下线;后台旧请求即使携带旧字段,也统一写 0,避免旧页面或缓存把门槛重新保存回来。 RequiredRechargeCoinAmount: 0, SortOrder: level.SortOrder, + Benefits: benefits, }) } resp, err := h.wallet.UpdateAdminVipLevels(c.Request.Context(), &walletv1.UpdateAdminVipLevelsRequest{ @@ -161,7 +356,11 @@ func (h *Handler) UpdateLevels(c *gin.Context) { return } shared.OperationLogWithResourceID(c, h.audit, "update-vip-config", "vip_levels", appctx.FromContext(c.Request.Context()), "success", strconv.Itoa(len(resp.GetLevels()))) - response.OK(c, configDTO{Levels: vipLevelsFromProto(resp.GetLevels()), ServerTimeMS: resp.GetServerTimeMs()}) + response.OK(c, configDTO{ + Levels: vipLevelsFromProto(resp.GetLevels()), + ProgramConfig: vipProgramConfigFromProto(resp.GetProgramConfig()), + ServerTimeMS: resp.GetServerTimeMs(), + }) } func vipLevelsFromProto(items []*walletv1.VipLevel) []vipLevelDTO { @@ -185,11 +384,92 @@ func vipLevelsFromProto(items []*walletv1.VipLevel) []vipLevelDTO { PurchaseLockedReason: item.GetPurchaseLockedReason(), CreatedAtMS: item.GetCreatedAtMs(), UpdatedAtMS: item.GetUpdatedAtMs(), + Benefits: vipBenefitsFromProto(item.GetBenefits()), + ConfigVersion: item.GetConfigVersion(), }) } return levels } +func vipProgramConfigFromProto(item *walletv1.VipProgramConfig) vipProgramConfigDTO { + if item == nil { + return vipProgramConfigDTO{} + } + return vipProgramConfigDTO{ + AppCode: item.GetAppCode(), + ProgramType: item.GetProgramType(), + LevelCount: item.GetLevelCount(), + SameLevelExpiryPolicy: item.GetSameLevelExpiryPolicy(), + UpgradeExpiryPolicy: item.GetUpgradeExpiryPolicy(), + DowngradePurchasePolicy: item.GetDowngradePurchasePolicy(), + BenefitInheritancePolicy: item.GetBenefitInheritancePolicy(), + GrantMode: item.GetGrantMode(), + TrialCardEnabled: item.GetTrialCardEnabled(), + Status: item.GetStatus(), + ConfigVersion: item.GetConfigVersion(), + UpdatedByAdminID: item.GetUpdatedByAdminId(), + CreatedAtMS: item.GetCreatedAtMs(), + UpdatedAtMS: item.GetUpdatedAtMs(), + } +} + +func (item vipProgramConfigDTO) toProto() *walletv1.VipProgramConfig { + return &walletv1.VipProgramConfig{ + ProgramType: strings.TrimSpace(item.ProgramType), + LevelCount: item.LevelCount, + SameLevelExpiryPolicy: strings.TrimSpace(item.SameLevelExpiryPolicy), + UpgradeExpiryPolicy: strings.TrimSpace(item.UpgradeExpiryPolicy), + DowngradePurchasePolicy: strings.TrimSpace(item.DowngradePurchasePolicy), + BenefitInheritancePolicy: strings.TrimSpace(item.BenefitInheritancePolicy), + GrantMode: strings.TrimSpace(item.GrantMode), + TrialCardEnabled: item.TrialCardEnabled, + Status: strings.TrimSpace(item.Status), + } +} + +func vipBenefitsFromProto(items []*walletv1.VipBenefit) []vipBenefitDTO { + benefits := make([]vipBenefitDTO, 0, len(items)) + for _, item := range items { + if item == nil { + continue + } + benefits = append(benefits, vipBenefitDTO{ + BenefitCode: item.GetBenefitCode(), + Name: item.GetName(), + BenefitType: item.GetBenefitType(), + UnlockLevel: item.GetUnlockLevel(), + Status: item.GetStatus(), + TrialEnabled: item.GetTrialEnabled(), + ResourceID: item.GetResourceId(), + ResourceType: item.GetResourceType(), + ExecutionScope: item.GetExecutionScope(), + AutoEquip: item.GetAutoEquip(), + SortOrder: item.GetSortOrder(), + MetadataJSON: item.GetMetadataJson(), + CreatedAtMS: item.GetCreatedAtMs(), + UpdatedAtMS: item.GetUpdatedAtMs(), + }) + } + return benefits +} + +func (item vipBenefitDTO) toProto() *walletv1.VipBenefit { + return &walletv1.VipBenefit{ + BenefitCode: strings.TrimSpace(item.BenefitCode), + Name: strings.TrimSpace(item.Name), + BenefitType: strings.TrimSpace(item.BenefitType), + UnlockLevel: item.UnlockLevel, + Status: strings.TrimSpace(item.Status), + TrialEnabled: item.TrialEnabled, + ResourceId: item.ResourceID, + ResourceType: strings.TrimSpace(item.ResourceType), + ExecutionScope: strings.TrimSpace(item.ExecutionScope), + AutoEquip: item.AutoEquip, + SortOrder: item.SortOrder, + MetadataJson: strings.TrimSpace(item.MetadataJSON), + } +} + func grantVipFromProto(resp *walletv1.GrantVipResponse) grantVipDTO { if resp == nil { return grantVipDTO{} @@ -207,13 +487,65 @@ func userVipFromProto(item *walletv1.UserVip) userVipDTO { return userVipDTO{} } return userVipDTO{ - UserID: strconv.FormatInt(item.GetUserId(), 10), - Level: item.GetLevel(), - Name: item.GetName(), - Active: item.GetActive(), - StartedAtMS: item.GetStartedAtMs(), - ExpiresAtMS: item.GetExpiresAtMs(), - UpdatedAtMS: item.GetUpdatedAtMs(), + UserID: strconv.FormatInt(item.GetUserId(), 10), + Level: item.GetLevel(), + Name: item.GetName(), + Active: item.GetActive(), + StartedAtMS: item.GetStartedAtMs(), + ExpiresAtMS: item.GetExpiresAtMs(), + UpdatedAtMS: item.GetUpdatedAtMs(), + ProgramType: item.GetProgramType(), + ConfigVersion: item.GetConfigVersion(), + } +} + +func grantVipTrialCardFromProto(resp *walletv1.GrantVipTrialCardResponse) grantVipTrialCardDTO { + if resp == nil { + return grantVipTrialCardDTO{} + } + return grantVipTrialCardDTO{ + TrialCard: vipTrialCardFromProto(resp.GetTrialCard()), + State: vipStateFromProto(resp.GetState()), + ServerTimeMS: resp.GetServerTimeMs(), + } +} + +func vipTrialCardFromProto(item *walletv1.VipTrialCard) vipTrialCardDTO { + if item == nil { + return vipTrialCardDTO{} + } + return vipTrialCardDTO{ + TrialCardID: item.GetTrialCardId(), + EntitlementID: item.GetEntitlementId(), + ResourceID: item.GetResourceId(), + UserID: strconv.FormatInt(item.GetUserId(), 10), + Level: item.GetLevel(), + Name: item.GetName(), + Status: item.GetStatus(), + Equipped: item.GetEquipped(), + DurationMS: item.GetDurationMs(), + EffectiveAtMS: item.GetEffectiveAtMs(), + ExpiresAtMS: item.GetExpiresAtMs(), + RemainingDurationMS: item.GetRemainingDurationMs(), + GrantSource: item.GetGrantSource(), + SourceGrantID: item.GetSourceGrantId(), + CreatedAtMS: item.GetCreatedAtMs(), + UpdatedAtMS: item.GetUpdatedAtMs(), + } +} + +func vipStateFromProto(item *walletv1.VipState) vipStateDTO { + if item == nil { + return vipStateDTO{EffectiveBenefits: []vipBenefitDTO{}} + } + return vipStateDTO{ + PaidVIP: userVipFromProto(item.GetPaidVip()), + EquippedTrialCard: vipTrialCardFromProto(item.GetEquippedTrialCard()), + EffectiveVIP: userVipFromProto(item.GetEffectiveVip()), + EffectiveSource: item.GetEffectiveSource(), + EffectiveBenefits: vipBenefitsFromProto(item.GetEffectiveBenefits()), + EvaluatedAtMS: item.GetEvaluatedAtMs(), + ProgramConfig: vipProgramConfigFromProto(item.GetProgramConfig()), } } diff --git a/server/admin/internal/modules/vipconfig/handler_test.go b/server/admin/internal/modules/vipconfig/handler_test.go new file mode 100644 index 00000000..514c2746 --- /dev/null +++ b/server/admin/internal/modules/vipconfig/handler_test.go @@ -0,0 +1,201 @@ +package vipconfig + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "hyapp-admin-server/internal/appctx" + "hyapp-admin-server/internal/integration/walletclient" + "hyapp-admin-server/internal/middleware" + walletv1 "hyapp.local/api/proto/wallet/v1" + + "github.com/gin-gonic/gin" +) + +func TestGetProgramReturnsCamelCaseCompleteConfigAndBenefits(t *testing.T) { + wallet := &fakeVIPConfigWallet{ + programResponse: &walletv1.GetVipProgramConfigResponse{ + Config: &walletv1.VipProgramConfig{ + AppCode: "fami", + ProgramType: "tiered_privilege_v1", + LevelCount: 9, + SameLevelExpiryPolicy: "extend_remaining", + UpgradeExpiryPolicy: "replace_from_now", + DowngradePurchasePolicy: "reject", + BenefitInheritancePolicy: "target_only", + GrantMode: "trial_card", + TrialCardEnabled: true, + Status: "active", + ConfigVersion: 3, + UpdatedByAdminId: 77, + CreatedAtMs: 100, + UpdatedAtMs: 200, + }, + Benefits: []*walletv1.VipBenefit{{ + BenefitCode: "anti_kick", + Name: "防踢", + BenefitType: "function", + UnlockLevel: 9, + Status: "active", + TrialEnabled: true, + ExecutionScope: "room", + MetadataJson: "{}", + CreatedAtMs: 101, + UpdatedAtMs: 201, + }}, + ServerTimeMs: 300, + }, + } + recorder := serveVIPConfigRequest(t, wallet, http.MethodGet, "/admin/activity/vip-program", "") + if recorder.Code != http.StatusOK { + t.Fatalf("get program status = %d, body=%s", recorder.Code, recorder.Body.String()) + } + body := recorder.Body.String() + for _, fragment := range []string{ + `"programType":"tiered_privilege_v1"`, + `"upgradeExpiryPolicy":"replace_from_now"`, + `"updatedByAdminId":77`, + `"benefitCode":"anti_kick"`, + `"executionScope":"room"`, + `"serverTimeMs":300`, + } { + if !strings.Contains(body, fragment) { + t.Fatalf("get program response missing %s: %s", fragment, body) + } + } + if strings.Contains(body, "program_type") || strings.Contains(body, "benefit_code") { + t.Fatalf("admin contract must stay camelCase: %s", body) + } + if wallet.getProgramRequest.GetAppCode() != "fami" || wallet.getProgramRequest.GetRequestId() != "vip-config-test" { + t.Fatalf("get program request mismatch: %+v", wallet.getProgramRequest) + } +} + +func TestUpdateProgramUsesContextAppAndAuthenticatedOperator(t *testing.T) { + wallet := &fakeVIPConfigWallet{updateProgramResponse: &walletv1.UpdateAdminVipProgramConfigResponse{ + Config: &walletv1.VipProgramConfig{AppCode: "fami", ProgramType: "tiered_privilege_v1", LevelCount: 9, Status: "active", ConfigVersion: 4}, + ServerTimeMs: 400, + }} + body := `{"config":{"appCode":"lalu","programType":"tiered_privilege_v1","levelCount":9,"sameLevelExpiryPolicy":"extend_remaining","upgradeExpiryPolicy":"replace_from_now","downgradePurchasePolicy":"reject","benefitInheritancePolicy":"target_only","grantMode":"trial_card","trialCardEnabled":true,"status":"active","configVersion":999,"updatedByAdminId":999}}` + recorder := serveVIPConfigRequest(t, wallet, http.MethodPut, "/admin/activity/vip-program", body) + if recorder.Code != http.StatusOK { + t.Fatalf("update program status = %d, body=%s", recorder.Code, recorder.Body.String()) + } + req := wallet.updateProgramRequest + if req.GetAppCode() != "fami" || req.GetOperatorUserId() != 77 { + t.Fatalf("program scope/operator mismatch: %+v", req) + } + if req.GetConfig().GetAppCode() != "fami" || req.GetConfig().GetConfigVersion() != 0 || req.GetConfig().GetUpdatedByAdminId() != 0 { + t.Fatalf("client-controlled program audit fields leaked through: %+v", req.GetConfig()) + } + if !strings.Contains(recorder.Body.String(), `"configVersion":4`) || !strings.Contains(recorder.Body.String(), `"benefits":[]`) { + t.Fatalf("update program response mismatch: %s", recorder.Body.String()) + } +} + +func TestUpdateLevelsForwardsExplicitBenefitsAndReturnsProgram(t *testing.T) { + wallet := &fakeVIPConfigWallet{updateLevelsResponse: &walletv1.UpdateAdminVipLevelsResponse{ + Levels: []*walletv1.VipLevel{{ + Level: 2, Name: "VIP2", Status: "active", DurationMs: 2_592_000_000, + Benefits: []*walletv1.VipBenefit{{BenefitCode: "room_entry_notice", Name: "进房通知", Status: "active", ExecutionScope: "room", MetadataJson: "{}"}}, + }}, + ProgramConfig: &walletv1.VipProgramConfig{AppCode: "fami", ProgramType: "tiered_privilege_v1", LevelCount: 9}, + ServerTimeMs: 500, + }} + body := `{"levels":[{"level":2,"name":"VIP2","status":"active","priceCoin":2000,"durationMs":2592000000,"rewardResourceGroupId":22,"sortOrder":20,"requiredRechargeCoinAmount":123,"benefits":[{"benefitCode":"room_entry_notice","name":"进房通知","benefitType":"function","unlockLevel":2,"status":"active","trialEnabled":true,"resourceId":0,"resourceType":"","executionScope":"room","autoEquip":false,"sortOrder":10,"metadataJson":"{}"}]}]}` + recorder := serveVIPConfigRequest(t, wallet, http.MethodPut, "/admin/activity/vip-levels", body) + if recorder.Code != http.StatusOK { + t.Fatalf("update levels status = %d, body=%s", recorder.Code, recorder.Body.String()) + } + req := wallet.updateLevelsRequest + if req.GetAppCode() != "fami" || req.GetOperatorUserId() != 77 || len(req.GetLevels()) != 1 { + t.Fatalf("update levels request mismatch: %+v", req) + } + level := req.GetLevels()[0] + if level.GetRequiredRechargeCoinAmount() != 0 || len(level.GetBenefits()) != 1 || level.GetBenefits()[0].GetBenefitCode() != "room_entry_notice" { + t.Fatalf("explicit level benefits mismatch: %+v", level) + } + if !strings.Contains(recorder.Body.String(), `"programConfig":{"appCode":"fami","programType":"tiered_privilege_v1","levelCount":9`) { + t.Fatalf("level response must include program config: %s", recorder.Body.String()) + } +} + +func TestGrantTrialCardLeavesOptionalResourceUnsetAndUsesAuthenticatedOperator(t *testing.T) { + wallet := &fakeVIPConfigWallet{grantTrialResponse: &walletv1.GrantVipTrialCardResponse{ + TrialCard: &walletv1.VipTrialCard{TrialCardId: "trial-1", EntitlementId: "ent-1", UserId: 318705991371722752, Level: 5, DurationMs: 1_728_000_000, ExpiresAtMs: 2_000}, + State: &walletv1.VipState{EffectiveSource: "none", EffectiveBenefits: []*walletv1.VipBenefit{}}, + ServerTimeMs: 1_000, + }} + body := `{"targetUserId":"318705991371722752","level":5,"durationMs":1728000000,"reason":"manual"}` + recorder := serveVIPConfigRequest(t, wallet, http.MethodPost, "/admin/activity/vip-trial-card-grants", body) + if recorder.Code != http.StatusCreated { + t.Fatalf("grant trial card status = %d, body=%s", recorder.Code, recorder.Body.String()) + } + req := wallet.grantTrialRequest + if req.GetCommandId() != "admin_vip_trial_card_grant:vip-config-test" || req.GetAppCode() != "fami" || req.GetOperatorUserId() != 77 { + t.Fatalf("trial grant command context mismatch: %+v", req) + } + if req.ResourceId != nil || req.GetTargetUserId() != 318705991371722752 || req.GetDurationMs() != 1_728_000_000 { + t.Fatalf("trial grant payload mismatch: %+v", req) + } + if !strings.Contains(recorder.Body.String(), `"trialCardId":"trial-1"`) || !strings.Contains(recorder.Body.String(), `"userId":"318705991371722752"`) { + t.Fatalf("trial grant response mismatch: %s", recorder.Body.String()) + } +} + +func serveVIPConfigRequest(t *testing.T, wallet walletclient.Client, method string, path string, body string) *httptest.ResponseRecorder { + t.Helper() + gin.SetMode(gin.TestMode) + router := gin.New() + router.Use(func(c *gin.Context) { + c.Request = c.Request.WithContext(appctx.WithContext(c.Request.Context(), "fami")) + c.Set(middleware.ContextRequestID, "vip-config-test") + c.Set(middleware.ContextUserID, uint(77)) + c.Set(middleware.ContextUsername, "vip-operator") + c.Set(middleware.ContextPermissions, []string{"vip-config:view", "vip-config:update", "vip-config:grant"}) + c.Next() + }) + RegisterRoutes(router.Group(""), New(wallet, nil)) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(method, path, strings.NewReader(body)) + if body != "" { + request.Header.Set("Content-Type", "application/json") + } + router.ServeHTTP(recorder, request) + return recorder +} + +type fakeVIPConfigWallet struct { + walletclient.Client + programResponse *walletv1.GetVipProgramConfigResponse + updateProgramResponse *walletv1.UpdateAdminVipProgramConfigResponse + updateLevelsResponse *walletv1.UpdateAdminVipLevelsResponse + grantTrialResponse *walletv1.GrantVipTrialCardResponse + getProgramRequest *walletv1.GetVipProgramConfigRequest + updateProgramRequest *walletv1.UpdateAdminVipProgramConfigRequest + updateLevelsRequest *walletv1.UpdateAdminVipLevelsRequest + grantTrialRequest *walletv1.GrantVipTrialCardRequest +} + +func (f *fakeVIPConfigWallet) GetVipProgramConfig(_ context.Context, req *walletv1.GetVipProgramConfigRequest) (*walletv1.GetVipProgramConfigResponse, error) { + f.getProgramRequest = req + return f.programResponse, nil +} + +func (f *fakeVIPConfigWallet) UpdateAdminVipProgramConfig(_ context.Context, req *walletv1.UpdateAdminVipProgramConfigRequest) (*walletv1.UpdateAdminVipProgramConfigResponse, error) { + f.updateProgramRequest = req + return f.updateProgramResponse, nil +} + +func (f *fakeVIPConfigWallet) UpdateAdminVipLevels(_ context.Context, req *walletv1.UpdateAdminVipLevelsRequest) (*walletv1.UpdateAdminVipLevelsResponse, error) { + f.updateLevelsRequest = req + return f.updateLevelsResponse, nil +} + +func (f *fakeVIPConfigWallet) GrantVipTrialCard(_ context.Context, req *walletv1.GrantVipTrialCardRequest) (*walletv1.GrantVipTrialCardResponse, error) { + f.grantTrialRequest = req + return f.grantTrialResponse, nil +} diff --git a/server/admin/internal/modules/vipconfig/routes.go b/server/admin/internal/modules/vipconfig/routes.go index e9b331a0..3919557b 100644 --- a/server/admin/internal/modules/vipconfig/routes.go +++ b/server/admin/internal/modules/vipconfig/routes.go @@ -11,7 +11,11 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { return } + protected.GET("/admin/activity/vip-program", middleware.RequirePermission("vip-config:view"), h.GetProgram) + protected.PUT("/admin/activity/vip-program", middleware.RequirePermission("vip-config:update"), h.UpdateProgram) protected.GET("/admin/activity/vip-levels", middleware.RequirePermission("vip-config:view"), h.ListLevels) protected.PUT("/admin/activity/vip-levels", middleware.RequirePermission("vip-config:update"), h.UpdateLevels) + protected.POST("/admin/activity/vip-trial-card-grants", middleware.RequirePermission("vip-config:grant"), h.GrantVIPTrialCard) + // 旧 App 仍使用直接会员赠送,保留原路由直到其 program.grant_mode 切换为 trial_card。 protected.POST("/admin/activity/vip-grants", middleware.RequirePermission("vip-config:grant"), h.GrantVIP) } diff --git a/server/admin/internal/repository/admin_ops.go b/server/admin/internal/repository/admin_ops.go index c8949ce9..9e66fbaa 100644 --- a/server/admin/internal/repository/admin_ops.go +++ b/server/admin/internal/repository/admin_ops.go @@ -191,16 +191,51 @@ func (s *Store) LeaseNextJob(workerID string, lease time.Duration, maxAttempts i return &leased, nil } -func (s *Store) CompleteJobWithArtifact(id uint, resultJSON string, artifactPath string) error { +func (s *Store) CompleteJobWithArtifact(id uint, workerID string, resultJSON string, artifactPath string) error { nowMS := time.Now().UTC().UnixMilli() - return s.db.Model(&model.AdminJob{}).Where("id = ?", id).Updates(map[string]any{ - "status": model.JobStatusSucceeded, - "result_json": resultJSON, - "artifact_path": artifactPath, - "locked_by": "", - "locked_until_ms": nil, - "finished_at_ms": &nowMS, - }).Error + result := s.db.Model(&model.AdminJob{}). + Where("id = ? AND status = ? AND locked_by = ?", id, model.JobStatusRunning, strings.TrimSpace(workerID)). + Updates(map[string]any{ + "status": model.JobStatusSucceeded, + "result_json": resultJSON, + "artifact_path": artifactPath, + "locked_by": "", + "locked_until_ms": nil, + "finished_at_ms": &nowMS, + }) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return errors.New("job lease is no longer owned by worker") + } + return nil +} + +// RenewJobLease 延长仍属于 worker 的 running 任务;RowsAffected=0 说明任务已完成或租约已经被其他实例接管。 +func (s *Store) RenewJobLease(id uint, workerID string, lease time.Duration) error { + if strings.TrimSpace(workerID) == "" || lease <= 0 { + return errors.New("worker id and positive lease are required") + } + nowMS := time.Now().UTC().UnixMilli() + result := s.db.Model(&model.AdminJob{}). + Where("id = ? AND status = ? AND locked_by = ?", id, model.JobStatusRunning, strings.TrimSpace(workerID)). + Update("locked_until_ms", nowMS+lease.Milliseconds()) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + var job model.AdminJob + if err := s.db.Select("status", "locked_by").First(&job, id).Error; err != nil { + return err + } + // handler 完成提交与 heartbeat tick 可能恰好相邻;终态无需续租,不能把成功任务误报为失败。 + if job.Status != model.JobStatusRunning { + return nil + } + return errors.New("job lease is no longer owned by worker") + } + return nil } type CountryCodeAdminReferenceCounts struct { @@ -234,9 +269,9 @@ func (s *Store) RenameCountryCodeAdminReferences(appCode string, oldCountryCode return counts, nil } -func (s *Store) FailJobAttempt(id uint, message string) error { +func (s *Store) FailJobAttempt(id uint, workerID string, message string) error { var job model.AdminJob - if err := s.db.First(&job, id).Error; err != nil { + if err := s.db.Where("id = ? AND status = ? AND locked_by = ?", id, model.JobStatusRunning, strings.TrimSpace(workerID)).First(&job).Error; err != nil { return err } nowMS := time.Now().UTC().UnixMilli() @@ -246,23 +281,39 @@ func (s *Store) FailJobAttempt(id uint, message string) error { status = model.JobStatusFailed finishedAt = &nowMS } - return s.db.Model(&model.AdminJob{}).Where("id = ?", id).Updates(map[string]any{ - "status": status, - "error": message, - "locked_by": "", - "locked_until_ms": nil, - "finished_at_ms": finishedAt, - }).Error + result := s.db.Model(&model.AdminJob{}). + Where("id = ? AND status = ? AND locked_by = ?", id, model.JobStatusRunning, strings.TrimSpace(workerID)). + Updates(map[string]any{ + "status": status, + "error": message, + "locked_by": "", + "locked_until_ms": nil, + "finished_at_ms": finishedAt, + }) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return errors.New("job lease is no longer owned by worker") + } + return nil } -func (s *Store) ReleaseJobLease(id uint) error { - return s.db.Model(&model.AdminJob{}). - Where("id = ? AND status = ?", id, model.JobStatusRunning). +func (s *Store) ReleaseJobLease(id uint, workerID string) error { + result := s.db.Model(&model.AdminJob{}). + Where("id = ? AND status = ? AND locked_by = ?", id, model.JobStatusRunning, strings.TrimSpace(workerID)). Updates(map[string]any{ "status": model.JobStatusPending, "locked_by": "", "locked_until_ms": nil, - }).Error + }) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return errors.New("job lease is no longer owned by worker") + } + return nil } func (s *Store) CancelJob(id uint) error { diff --git a/server/admin/internal/repository/app_config_repository.go b/server/admin/internal/repository/app_config_repository.go index 54ceb22f..b3476b29 100644 --- a/server/admin/internal/repository/app_config_repository.go +++ b/server/admin/internal/repository/app_config_repository.go @@ -1,11 +1,13 @@ package repository import ( + "sort" "strings" "time" "hyapp-admin-server/internal/model" + "gorm.io/gorm" "gorm.io/gorm/clause" ) @@ -23,13 +25,14 @@ type AppExploreTabListOptions struct { func (s *Store) ListAppConfigs(group string) ([]model.AppConfig, error) { var items []model.AppConfig - err := s.db.Where("`group` = ?", strings.TrimSpace(group)).Order("`key` ASC").Find(&items).Error + // 旧的通用配置继续使用 app_code='' 基线行;H5 配置必须走 scoped API,避免覆盖行被其他旧调用方误读。 + err := s.db.Where("app_code = '' AND `group` = ?", strings.TrimSpace(group)).Order("`key` ASC").Find(&items).Error return items, err } func (s *Store) GetAppConfig(group string, key string) (model.AppConfig, error) { var item model.AppConfig - err := s.db.Where("`group` = ? AND `key` = ?", strings.TrimSpace(group), strings.TrimSpace(key)).First(&item).Error + err := s.db.Where("app_code = '' AND `group` = ? AND `key` = ?", strings.TrimSpace(group), strings.TrimSpace(key)).First(&item).Error return item, err } @@ -42,7 +45,7 @@ func (s *Store) UpdateAppConfig(item *model.AppConfig) error { } func (s *Store) DeleteAppConfig(group string, key string) error { - return s.db.Where("`group` = ? AND `key` = ?", strings.TrimSpace(group), strings.TrimSpace(key)).Delete(&model.AppConfig{}).Error + return s.db.Where("app_code = '' AND `group` = ? AND `key` = ?", strings.TrimSpace(group), strings.TrimSpace(key)).Delete(&model.AppConfig{}).Error } func (s *Store) UpsertAppConfigs(items []model.AppConfig) error { @@ -52,8 +55,10 @@ func (s *Store) UpsertAppConfigs(items []model.AppConfig) error { nowMS := time.Now().UTC().UnixMilli() for index := range items { + items[index].AppCode = "" items[index].Group = strings.TrimSpace(items[index].Group) items[index].Key = strings.TrimSpace(items[index].Key) + items[index].IsDeleted = false items[index].UpdatedAtMS = nowMS if items[index].CreatedAtMS == 0 { items[index].CreatedAtMS = nowMS @@ -62,13 +67,172 @@ func (s *Store) UpsertAppConfigs(items []model.AppConfig) error { return s.db.Clauses(clause.OnConflict{ Columns: []clause.Column{ + {Name: "app_code"}, {Name: "group"}, {Name: "key"}, }, - DoUpdates: clause.AssignmentColumns([]string{"value", "description", "updated_at_ms"}), + DoUpdates: clause.AssignmentColumns([]string{"value", "description", "is_deleted", "updated_at_ms"}), }).Create(&items).Error } +// ListScopedAppConfigs 返回当前 App 的有效配置视图:scoped 行覆盖 legacy 基线,墓碑显式屏蔽基线。 +// legacy 基线保持只读,因此迁移后未知的新 App 也能继续获得原配置,同时任意 App 的写操作不会污染其他 App。 +func (s *Store) ListScopedAppConfigs(appCode string, group string) ([]model.AppConfig, error) { + appCode = normalizeScopedAppCode(appCode) + var rows []model.AppConfig + err := s.db. + Where("`group` = ? AND app_code IN ?", strings.TrimSpace(group), []string{"", appCode}). + Order("`key` ASC, app_code ASC"). + Find(&rows).Error + if err != nil { + return nil, err + } + return mergeScopedAppConfigs(rows, appCode), nil +} + +// GetScopedAppConfig 和列表使用同一覆盖规则,防止更新/删除误操作其他 App 或 legacy 基线行。 +func (s *Store) GetScopedAppConfig(appCode string, group string, key string) (model.AppConfig, error) { + appCode = normalizeScopedAppCode(appCode) + var rows []model.AppConfig + err := s.db. + Where("`group` = ? AND `key` = ? AND app_code IN ?", strings.TrimSpace(group), strings.TrimSpace(key), []string{"", appCode}). + Order("app_code ASC"). + Find(&rows).Error + if err != nil { + return model.AppConfig{}, err + } + items := mergeScopedAppConfigs(rows, appCode) + if len(items) == 0 { + return model.AppConfig{}, gorm.ErrRecordNotFound + } + return items[0], nil +} + +// ListOwnedAppConfigs 只返回当前 App 自己持有的配置行。H5 等产品入口不能继承空 app_code +// 基线,否则新增 App 在尚未配置时会直接暴露默认 App 的运营链接,形成跨 App 内容串用。 +func (s *Store) ListOwnedAppConfigs(appCode string, group string) ([]model.AppConfig, error) { + var items []model.AppConfig + err := s.db. + Where("app_code = ? AND `group` = ? AND is_deleted = FALSE", normalizeScopedAppCode(appCode), strings.TrimSpace(group)). + Order("`key` ASC"). + Find(&items).Error + return items, err +} + +// GetOwnedAppConfig 和列表保持同一强隔离语义;更新与删除不能通过 legacy 基线找到其他 +// App 可见的同名 key,也不能把墓碑当成可编辑配置。 +func (s *Store) GetOwnedAppConfig(appCode string, group string, key string) (model.AppConfig, error) { + var item model.AppConfig + err := s.db. + Where("app_code = ? AND `group` = ? AND `key` = ? AND is_deleted = FALSE", normalizeScopedAppCode(appCode), strings.TrimSpace(group), strings.TrimSpace(key)). + First(&item).Error + return item, err +} + +// DeleteOwnedAppConfig 物理删除当前 App 的配置。强隔离配置不存在跨 App 基线回退,因此无需保留 +// 墓碑;删除后当前 App 应立即回到“未配置”状态。 +func (s *Store) DeleteOwnedAppConfig(appCode string, group string, key string) error { + return s.db. + Where("app_code = ? AND `group` = ? AND `key` = ?", normalizeScopedAppCode(appCode), strings.TrimSpace(group), strings.TrimSpace(key)). + Delete(&model.AppConfig{}).Error +} + +// UpsertScopedAppConfigs 只写当前 app_code;调用方无法通过请求体覆盖租户作用域。 +func (s *Store) UpsertScopedAppConfigs(appCode string, items []model.AppConfig) error { + if len(items) == 0 { + return nil + } + appCode = normalizeScopedAppCode(appCode) + nowMS := time.Now().UTC().UnixMilli() + for index := range items { + items[index].AppCode = appCode + items[index].Group = strings.TrimSpace(items[index].Group) + items[index].Key = strings.TrimSpace(items[index].Key) + items[index].IsDeleted = false + items[index].UpdatedAtMS = nowMS + if items[index].CreatedAtMS == 0 { + items[index].CreatedAtMS = nowMS + } + } + return s.db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "app_code"}, {Name: "group"}, {Name: "key"}}, + DoUpdates: clause.AssignmentColumns([]string{"value", "description", "is_deleted", "updated_at_ms"}), + }).Create(&items).Error +} + +// TombstoneScopedAppConfig 不物理删除 scoped 行。若配置来自 legacy 基线,墓碑会阻止它在删除后重新出现。 +func (s *Store) TombstoneScopedAppConfig(appCode string, group string, key string) error { + appCode = normalizeScopedAppCode(appCode) + nowMS := time.Now().UTC().UnixMilli() + item := model.AppConfig{ + AppCode: appCode, + Group: strings.TrimSpace(group), + Key: strings.TrimSpace(key), + IsDeleted: true, + CreatedAtMS: nowMS, + UpdatedAtMS: nowMS, + } + return s.db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "app_code"}, {Name: "group"}, {Name: "key"}}, + DoUpdates: clause.Assignments(map[string]any{ + "is_deleted": true, + "updated_at_ms": nowMS, + }), + }).Create(&item).Error +} + +func normalizeScopedAppCode(value string) string { + return strings.ToLower(strings.TrimSpace(value)) +} + +func mergeScopedAppConfigs(rows []model.AppConfig, appCode string) []model.AppConfig { + appCode = normalizeScopedAppCode(appCode) + baseline := make(map[string]model.AppConfig) + overrides := make(map[string]model.AppConfig) + for _, item := range rows { + item.AppCode = normalizeScopedAppCode(item.AppCode) + item.Group = strings.TrimSpace(item.Group) + item.Key = strings.TrimSpace(item.Key) + if item.Key == "" { + continue + } + switch item.AppCode { + case "": + baseline[item.Key] = item + case appCode: + overrides[item.Key] = item + } + } + + keys := make(map[string]struct{}, len(baseline)+len(overrides)) + for key := range baseline { + keys[key] = struct{}{} + } + for key := range overrides { + keys[key] = struct{}{} + } + sortedKeys := make([]string, 0, len(keys)) + for key := range keys { + sortedKeys = append(sortedKeys, key) + } + sort.Strings(sortedKeys) + + items := make([]model.AppConfig, 0, len(sortedKeys)) + for _, key := range sortedKeys { + item, overridden := overrides[key] + if !overridden { + item = baseline[key] + } + if item.IsDeleted { + continue + } + // 对外有效视图始终归属于当前 App;baseline 的空作用域不应泄漏给响应或后续写操作。 + item.AppCode = appCode + items = append(items, item) + } + return items +} + func (s *Store) ListAppVersions(options AppVersionListOptions) ([]model.AppVersion, error) { db := s.db.Where("app_code = ?", strings.TrimSpace(options.AppCode)) if platform := strings.TrimSpace(options.Platform); platform != "" { diff --git a/server/admin/internal/repository/app_config_repository_test.go b/server/admin/internal/repository/app_config_repository_test.go new file mode 100644 index 00000000..9c58fa26 --- /dev/null +++ b/server/admin/internal/repository/app_config_repository_test.go @@ -0,0 +1,102 @@ +package repository + +import ( + "testing" + + "hyapp-admin-server/internal/model" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestMergeScopedAppConfigsDoesNotLeakOverridesOrTombstonesAcrossApps(t *testing.T) { + rows := []model.AppConfig{ + {AppCode: "", Group: "runtime-config", Key: "admin", Description: "Legacy Admin", Value: "https://legacy.example.com/admin"}, + {AppCode: "", Group: "runtime-config", Key: "host", Description: "Legacy Host", Value: "https://legacy.example.com/host"}, + {AppCode: "yumi", Group: "runtime-config", Key: "admin", Description: "Yumi Admin", Value: "https://yumi.example.com/admin"}, + {AppCode: "yumi", Group: "runtime-config", Key: "host", IsDeleted: true}, + {AppCode: "fami", Group: "runtime-config", Key: "admin", Description: "Fami Admin", Value: "https://fami.example.com/admin"}, + } + + yumi := mergeScopedAppConfigs(rows, "yumi") + if len(yumi) != 1 || yumi[0].AppCode != "yumi" || yumi[0].Description != "Yumi Admin" { + t.Fatalf("yumi effective config mismatch: %+v", yumi) + } + fami := mergeScopedAppConfigs(rows, "fami") + if len(fami) != 2 || fami[0].AppCode != "fami" || fami[0].Description != "Fami Admin" || fami[1].Value != "https://legacy.example.com/host" { + t.Fatalf("fami must not inherit yumi override/tombstone: %+v", fami) + } + future := mergeScopedAppConfigs(rows, "future-app") + if len(future) != 2 || future[0].Description != "Legacy Admin" || future[1].Description != "Legacy Host" { + t.Fatalf("future app must inherit only legacy baseline: %+v", future) + } +} + +func TestTombstoneScopedAppConfigWritesOnlyCurrentApp(t *testing.T) { + store, mock, closeStore := newRepositorySQLMock(t) + defer closeStore() + + mock.ExpectBegin() + mock.ExpectExec("INSERT INTO `admin_app_configs`"). + WithArgs("yumi", "runtime-config", "host", "", "", true, sqlmock.AnyArg(), sqlmock.AnyArg(), true, sqlmock.AnyArg()). + WillReturnResult(sqlmock.NewResult(9, 1)) + mock.ExpectCommit() + + if err := store.TombstoneScopedAppConfig(" YUMI ", "runtime-config", "host"); err != nil { + t.Fatalf("write scoped tombstone failed: %v", err) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("sql expectations mismatch: %v", err) + } +} + +func TestListScopedAppConfigsOverlaysBaselineAndHonorsTombstone(t *testing.T) { + store, mock, closeStore := newRepositorySQLMock(t) + defer closeStore() + + mock.ExpectQuery("SELECT \\* FROM `admin_app_configs` WHERE `group` = \\? AND app_code IN \\(\\?,\\?\\) ORDER BY `key` ASC, app_code ASC"). + WithArgs("runtime-config", "", "yumi"). + WillReturnRows(sqlmock.NewRows([]string{"id", "app_code", "group", "key", "value", "description", "is_deleted", "created_at_ms", "updated_at_ms"}). + AddRow(1, "", "runtime-config", "admin", "https://legacy.example.com/admin", "Admin", false, int64(100), int64(100)). + AddRow(2, "yumi", "runtime-config", "admin", "https://yumi.example.com/admin", "Yumi Admin", false, int64(200), int64(200)). + AddRow(3, "", "runtime-config", "host", "https://legacy.example.com/host", "Host", false, int64(100), int64(100)). + AddRow(4, "yumi", "runtime-config", "host", "", "", true, int64(300), int64(300)). + AddRow(5, "", "runtime-config", "public", "https://legacy.example.com/public", "Public", false, int64(100), int64(100))) + + items, err := store.ListScopedAppConfigs(" YUMI ", "runtime-config") + if err != nil { + t.Fatalf("list scoped app configs failed: %v", err) + } + if len(items) != 2 { + t.Fatalf("unexpected effective configs: %+v", items) + } + if items[0].AppCode != "yumi" || items[0].Key != "admin" || items[0].Description != "Yumi Admin" { + t.Fatalf("override mismatch: %+v", items[0]) + } + if items[1].AppCode != "yumi" || items[1].Key != "public" || items[1].Value != "https://legacy.example.com/public" { + t.Fatalf("baseline projection mismatch: %+v", items[1]) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("sql expectations mismatch: %v", err) + } +} + +func TestListOwnedAppConfigsDoesNotReadLegacyOrOtherAppRows(t *testing.T) { + store, mock, closeStore := newRepositorySQLMock(t) + defer closeStore() + + mock.ExpectQuery("SELECT \\* FROM `admin_app_configs` WHERE app_code = \\? AND `group` = \\? AND is_deleted = FALSE ORDER BY `key` ASC"). + WithArgs("yumi", "h5-links"). + WillReturnRows(sqlmock.NewRows([]string{"id", "app_code", "group", "key", "value", "description", "is_deleted", "created_at_ms", "updated_at_ms"}). + AddRow(2, "yumi", "h5-links", "admin", "https://yumi.example.com/admin", "Yumi Admin", false, int64(200), int64(200))) + + items, err := store.ListOwnedAppConfigs(" YUMI ", "h5-links") + if err != nil { + t.Fatalf("list owned app configs failed: %v", err) + } + if len(items) != 1 || items[0].AppCode != "yumi" || items[0].Key != "admin" { + t.Fatalf("owned config scope mismatch: %+v", items) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("sql expectations mismatch: %v", err) + } +} diff --git a/server/admin/internal/repository/coin_seller_recharge_order_repository.go b/server/admin/internal/repository/coin_seller_recharge_order_repository.go index 1499ccc0..e7c90935 100644 --- a/server/admin/internal/repository/coin_seller_recharge_order_repository.go +++ b/server/admin/internal/repository/coin_seller_recharge_order_repository.go @@ -16,6 +16,9 @@ var ( ErrCoinSellerRechargeOrderGranted = errors.New("coin seller recharge order already granted") ) +// coinSellerRechargeBusinessTimeSQL 让新订单按运营选择的业务时间归档,同时保持历史 order_date_ms=0 数据仍按录入时间查询。 +const coinSellerRechargeBusinessTimeSQL = "CASE WHEN order_date_ms > 0 THEN order_date_ms ELSE created_at_ms END" + type CoinSellerRechargeOrderListOptions struct { Page int PageSize int @@ -121,7 +124,7 @@ func (s *Store) ListCoinSellerRechargeOrders(options CoinSellerRechargeOrderList } var orders []model.CoinSellerRechargeOrder err := query. - Order("created_at_ms DESC, id DESC"). + Order(coinSellerRechargeBusinessTimeSQL + " DESC, id DESC"). Limit(pageSize). Offset((page - 1) * pageSize). Find(&orders).Error @@ -375,10 +378,10 @@ func applyCoinSellerRechargeOrderFilters(query *gorm.DB, options CoinSellerRecha query = query.Where("operator_user_id = ?", options.OperatorID) } if options.CreatedFromMS > 0 { - query = query.Where("created_at_ms >= ?", options.CreatedFromMS) + query = query.Where(coinSellerRechargeBusinessTimeSQL+" >= ?", options.CreatedFromMS) } if options.CreatedToMS > 0 { - query = query.Where("created_at_ms < ?", options.CreatedToMS) + query = query.Where(coinSellerRechargeBusinessTimeSQL+" < ?", options.CreatedToMS) } if keyword := strings.TrimSpace(options.Keyword); keyword != "" { like := "%" + keyword + "%" @@ -406,10 +409,10 @@ func applyCoinSellerRechargeOrderStatsFilters(query *gorm.DB, options CoinSeller query = query.Where("target_country_id = ?", options.CountryID) } if options.CreatedFromMS > 0 { - query = query.Where("created_at_ms >= ?", options.CreatedFromMS) + query = query.Where(coinSellerRechargeBusinessTimeSQL+" >= ?", options.CreatedFromMS) } if options.CreatedToMS > 0 { - query = query.Where("created_at_ms < ?", options.CreatedToMS) + query = query.Where(coinSellerRechargeBusinessTimeSQL+" < ?", options.CreatedToMS) } if keyword := strings.TrimSpace(options.Keyword); keyword != "" { like := "%" + keyword + "%" @@ -434,9 +437,9 @@ func (s *Store) scanCoinSellerRechargeOrderDailyStats(options CoinSellerRecharge } var rows []row tzOffsetMS := int64(options.TzOffsetMinutes) * 60_000 - // 财务筛选按 created_at_ms 取数;日趋势按前端传入的统计时区偏移切日,避免北京时间财务页跨日错桶。 + // 财务筛选和趋势都按订单业务时间取数;历史记录自动回退录入时间,并继续按前端统计时区偏移切日。 if err := query.Select(` - DATE_FORMAT(FROM_UNIXTIME((created_at_ms + ?) / 1000), '%Y-%m-%d') AS date, + DATE_FORMAT(FROM_UNIXTIME((`+coinSellerRechargeBusinessTimeSQL+` + ?) / 1000), '%Y-%m-%d') AS date, COUNT(*) AS count, COALESCE(SUM(usd_minor_amount), 0) AS usd_minor, COALESCE(SUM(coin_amount), 0) AS coin_amount`, tzOffsetMS). diff --git a/server/admin/internal/repository/legacy_google_paid_repository.go b/server/admin/internal/repository/legacy_google_paid_repository.go index 1705cfe6..325250d1 100644 --- a/server/admin/internal/repository/legacy_google_paid_repository.go +++ b/server/admin/internal/repository/legacy_google_paid_repository.go @@ -2,12 +2,18 @@ package repository import ( "strings" + "time" "hyapp-admin-server/internal/model" "gorm.io/gorm/clause" ) +const ( + legacyGooglePaidRetryBaseMS = int64((15 * time.Second) / time.Millisecond) + legacyGooglePaidRetryMaxMS = int64((15 * time.Minute) / time.Millisecond) +) + // UpsertLegacyGooglePaidDetail 写入或覆盖 legacy 谷歌账单实付明细;同一账单重复同步以最新结果为准。 func (s *Store) UpsertLegacyGooglePaidDetail(detail model.LegacyGooglePaidDetail) error { detail.AppCode = strings.ToLower(strings.TrimSpace(detail.AppCode)) @@ -67,6 +73,47 @@ func (s *Store) LegacyGooglePaidTransactionIDs(appCode string) ([]string, error) return ids, err } +// LegacyGooglePaidRetryBlockedTransactionIDs returns failures whose persisted backoff has not expired. +// Due attempts are intentionally omitted so the next 15-second scan can retry them without manual intervention. +func (s *Store) LegacyGooglePaidRetryBlockedTransactionIDs(appCode string, nowMS int64) ([]string, error) { + ids := []string{} + err := s.db.Model(&model.LegacyGooglePaidSyncAttempt{}). + Where("app_code = ? AND next_retry_at_ms > ?", strings.ToLower(strings.TrimSpace(appCode)), nowMS). + Pluck("transaction_id", &ids).Error + return ids, err +} + +// RecordLegacyGooglePaidSyncFailure atomically advances retry state with exponential backoff. +// The error text is bounded before persistence so provider responses cannot inflate the admin database. +func (s *Store) RecordLegacyGooglePaidSyncFailure(appCode string, transactionID string, message string, nowMS int64) error { + appCode = strings.ToLower(strings.TrimSpace(appCode)) + transactionID = strings.TrimSpace(transactionID) + message = strings.TrimSpace(message) + if len(message) > 512 { + message = message[:512] + } + return s.db.Exec(` + INSERT INTO admin_legacy_google_paid_sync_attempts ( + app_code, transaction_id, attempt_count, last_error, next_retry_at_ms, created_at_ms, updated_at_ms + ) VALUES (?, ?, 1, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + last_error = VALUES(last_error), + next_retry_at_ms = VALUES(updated_at_ms) + LEAST(?, ? * POW(2, LEAST(attempt_count, 6))), + attempt_count = attempt_count + 1, + updated_at_ms = VALUES(updated_at_ms)`, + appCode, transactionID, message, nowMS+legacyGooglePaidRetryBaseMS, nowMS, nowMS, + legacyGooglePaidRetryMaxMS, legacyGooglePaidRetryBaseMS, + ).Error +} + +// ClearLegacyGooglePaidSyncFailure removes obsolete retry state after the paid-detail upsert succeeds. +func (s *Store) ClearLegacyGooglePaidSyncFailure(appCode string, transactionID string) error { + return s.db.Where( + "app_code = ? AND transaction_id = ?", + strings.ToLower(strings.TrimSpace(appCode)), strings.TrimSpace(transactionID), + ).Delete(&model.LegacyGooglePaidSyncAttempt{}).Error +} + // LegacyGooglePaidDetails 按交易号批量读取实付明细,供财务明细列表按页回填。 func (s *Store) LegacyGooglePaidDetails(appCode string, transactionIDs []string) (map[string]model.LegacyGooglePaidDetail, error) { details := map[string]model.LegacyGooglePaidDetail{} diff --git a/server/admin/internal/repository/repository.go b/server/admin/internal/repository/repository.go index 9bbfe196..c1e62a6c 100644 --- a/server/admin/internal/repository/repository.go +++ b/server/admin/internal/repository/repository.go @@ -87,6 +87,7 @@ func (s *Store) AutoMigrate() error { &model.UserMoneyScope{}, &model.TemporaryPaymentLinkOwner{}, &model.LegacyGooglePaidDetail{}, + &model.LegacyGooglePaidSyncAttempt{}, &model.FinanceApplication{}, &model.CoinSellerRechargeOrder{}, &model.UserWithdrawalApplication{}, diff --git a/server/admin/internal/repository/seed.go b/server/admin/internal/repository/seed.go index 19cbaa61..9391c566 100644 --- a/server/admin/internal/repository/seed.go +++ b/server/admin/internal/repository/seed.go @@ -21,8 +21,11 @@ var defaultPermissions = []model.Permission{ {Name: "团队更新", Code: "team:update", Kind: "button"}, {Name: "App 用户查看", Code: "app-user:view", Kind: "menu"}, {Name: "App 用户更新", Code: "app-user:update", Kind: "button"}, + {Name: "App 用户等级调整", Code: "app-user:level", Kind: "button"}, + {Name: "App 用户导出", Code: "app-user:export", Kind: "button"}, {Name: "App 用户状态", Code: "app-user:status", Kind: "button"}, {Name: "App 用户设置密码", Code: "app-user:password", Kind: "button"}, + {Name: "App 用户获取 Token", Code: "app-user:token", Kind: "button"}, {Name: "等级配置查看", Code: "level-config:view", Kind: "menu"}, {Name: "等级配置更新", Code: "level-config:update", Kind: "button"}, {Name: "靓号管理查看", Code: "pretty-id:view", Kind: "menu"}, @@ -639,7 +642,7 @@ func defaultRolePermissionCodes(code string) []string { "overview:view", "user:view", "user:status", "team:view", "team:create", "team:update", - "app-user:view", "app-user:update", "app-user:status", "app-user:password", + "app-user:view", "app-user:update", "app-user:level", "app-user:export", "app-user:status", "app-user:password", "app-user:token", "level-config:view", "level-config:update", "pretty-id:view", "pretty-id:update", "pretty-id:generate", "pretty-id:grant", "risk-config:view", "risk-config:update", @@ -752,7 +755,7 @@ func defaultRolePermissionMigrationCodes(code string) []string { case "ops-admin": return []string{ "team:view", "team:create", "team:update", - "app-user:update", "app-user:status", "app-user:password", + "app-user:update", "app-user:level", "app-user:export", "app-user:status", "app-user:password", "app-user:token", "room:view", "room:update", "room:delete", "room-pin:view", "room-pin:create", "room-pin:cancel", "room-config:view", "room-config:update", "room-whitelist:view", "room-whitelist:update", "room-robot:view", "room-robot:create", "room-robot:update", "app-config:view", "app-config:update", "app-version:view", "app-version:create", "app-version:update", "app-version:delete", diff --git a/server/admin/internal/router/router.go b/server/admin/internal/router/router.go index 0873fc71..3b2b0324 100644 --- a/server/admin/internal/router/router.go +++ b/server/admin/internal/router/router.go @@ -37,6 +37,7 @@ import ( "hyapp-admin-server/internal/modules/menu" "hyapp-admin-server/internal/modules/opscenter" "hyapp-admin-server/internal/modules/payment" + "hyapp-admin-server/internal/modules/pointwithdrawalconfig" "hyapp-admin-server/internal/modules/policyconfig" "hyapp-admin-server/internal/modules/prettyid" "hyapp-admin-server/internal/modules/rbac" @@ -66,62 +67,63 @@ import ( ) type Handlers struct { - AdminUser *adminuser.Handler - AgencyOpening *agencyopening.Handler - AchievementConfig *achievementconfig.Handler - AppConfig *appconfig.Handler - AppRegistry *appregistry.Handler - AppUser *appuser.Handler - Audit *audit.Handler - Auth *authroutes.Handler - CoinLedger *coinledger.Handler - CountryRegion *countryregion.Handler - CPRelation *cprelation.Handler - CPWeeklyRank *cpweeklyrank.Handler - CumulativeRecharge *cumulativerechargereward.Handler - DailyTask *dailytask.Handler - Dashboard *dashboard.Handler - Databi *databi.Handler - FirstRechargeReward *firstrechargereward.Handler - FinanceApplication *financeapplication.Handler - FinanceOrder *financeorder.Handler - FinanceWithdrawal *financewithdrawal.Handler - FullServerNotice *fullservernotice.Handler - Game *gamemanagement.Handler - GiftDiamond *giftdiamond.Handler - Health *health.Handler - HostAgencyPolicy *hostagencypolicy.Handler - HostOrg *hostorg.Handler - HostSalarySettlement *hostsalarysettlement.Handler - HostWithdrawal *hostwithdrawal.Handler - InviteActivityReward *inviteactivityreward.Handler - Job *job.Handler - LevelConfig *levelconfig.Handler - Menu *menu.Handler - OpsCenter *opscenter.Handler - Payment *payment.Handler - PolicyConfig *policyconfig.Handler - PrettyID *prettyid.Handler - RBAC *rbac.Handler - RedPacket *redpacket.Handler - Report *reportmodule.Handler - RegistrationReward *registrationreward.Handler - RegionBlock *regionblock.Handler - Resource *resourcemodule.Handler - RiskConfig *riskconfig.Handler - RoomAdmin *roomadmin.Handler - RoomRocket *roomrocket.Handler - RoomTurnoverReward *roomturnoverreward.Handler - Search *search.Handler - SevenDayCheckIn *sevendaycheckin.Handler - Team *team.Handler - TeamSalaryPolicy *teamsalarypolicy.Handler - TeamSalarySettlement *teamsalarysettlement.Handler - Upload *upload.Handler - UserLeaderboard *userleaderboard.Handler - VIPConfig *vipconfig.Handler - WeeklyStar *weeklystar.Handler - Wheel *wheel.Handler + AdminUser *adminuser.Handler + AgencyOpening *agencyopening.Handler + AchievementConfig *achievementconfig.Handler + AppConfig *appconfig.Handler + AppRegistry *appregistry.Handler + AppUser *appuser.Handler + Audit *audit.Handler + Auth *authroutes.Handler + CoinLedger *coinledger.Handler + CountryRegion *countryregion.Handler + CPRelation *cprelation.Handler + CPWeeklyRank *cpweeklyrank.Handler + CumulativeRecharge *cumulativerechargereward.Handler + DailyTask *dailytask.Handler + Dashboard *dashboard.Handler + Databi *databi.Handler + FirstRechargeReward *firstrechargereward.Handler + FinanceApplication *financeapplication.Handler + FinanceOrder *financeorder.Handler + FinanceWithdrawal *financewithdrawal.Handler + FullServerNotice *fullservernotice.Handler + Game *gamemanagement.Handler + GiftDiamond *giftdiamond.Handler + Health *health.Handler + HostAgencyPolicy *hostagencypolicy.Handler + HostOrg *hostorg.Handler + HostSalarySettlement *hostsalarysettlement.Handler + HostWithdrawal *hostwithdrawal.Handler + InviteActivityReward *inviteactivityreward.Handler + Job *job.Handler + LevelConfig *levelconfig.Handler + Menu *menu.Handler + OpsCenter *opscenter.Handler + Payment *payment.Handler + PolicyConfig *policyconfig.Handler + PointWithdrawalConfig *pointwithdrawalconfig.Handler + PrettyID *prettyid.Handler + RBAC *rbac.Handler + RedPacket *redpacket.Handler + Report *reportmodule.Handler + RegistrationReward *registrationreward.Handler + RegionBlock *regionblock.Handler + Resource *resourcemodule.Handler + RiskConfig *riskconfig.Handler + RoomAdmin *roomadmin.Handler + RoomRocket *roomrocket.Handler + RoomTurnoverReward *roomturnoverreward.Handler + Search *search.Handler + SevenDayCheckIn *sevendaycheckin.Handler + Team *team.Handler + TeamSalaryPolicy *teamsalarypolicy.Handler + TeamSalarySettlement *teamsalarysettlement.Handler + Upload *upload.Handler + UserLeaderboard *userleaderboard.Handler + VIPConfig *vipconfig.Handler + WeeklyStar *weeklystar.Handler + Wheel *wheel.Handler } func New(cfg config.Config, auth *service.AuthService, h Handlers) *gin.Engine { @@ -176,6 +178,7 @@ func New(cfg config.Config, auth *service.AuthService, h Handlers) *gin.Engine { audit.RegisterRoutes(protected, h.Audit) payment.RegisterRoutes(protected, h.Payment) policyconfig.RegisterRoutes(protected, h.PolicyConfig) + pointwithdrawalconfig.RegisterRoutes(protected, h.PointWithdrawalConfig) prettyid.RegisterRoutes(protected, h.PrettyID) job.RegisterRoutes(protected, h.Job) levelconfig.RegisterRoutes(protected, h.LevelConfig) diff --git a/server/admin/migrations/085_app_user_level_export_permissions.sql b/server/admin/migrations/085_app_user_level_export_permissions.sql new file mode 100644 index 00000000..ac5b6945 --- /dev/null +++ b/server/admin/migrations/085_app_user_level_export_permissions.sql @@ -0,0 +1,21 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- 等级调整会跨 activity/wallet 执行奖励副作用,用户导出会读取筛选快照和生成文件; +-- 两者都必须与普通资料更新解耦,便于运营角色按职责最小授权。 +SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED); + +INSERT INTO admin_permissions (name, code, kind, description, created_at_ms, updated_at_ms) VALUES + ('App 用户等级调整', 'app-user:level', 'button', '允许临时调整 App 用户富豪或魅力等级', @now_ms, @now_ms), + ('App 用户导出', 'app-user:export', 'button', '允许按当前筛选快照异步导出 App 用户 CSV', @now_ms, @now_ms) +ON DUPLICATE KEY UPDATE + name = VALUES(name), + kind = VALUES(kind), + description = VALUES(description), + updated_at_ms = @now_ms; + +INSERT IGNORE INTO admin_role_permissions (role_id, permission_id) +SELECT admin_role.id, admin_permission.id +FROM admin_roles admin_role +JOIN admin_permissions admin_permission +WHERE admin_role.code IN ('platform-admin', 'ops-admin') + AND admin_permission.code IN ('app-user:level', 'app-user:export'); diff --git a/server/admin/migrations/086_app_config_app_scope.sql b/server/admin/migrations/086_app_config_app_scope.sql new file mode 100644 index 00000000..dae03bfe --- /dev/null +++ b/server/admin/migrations/086_app_config_app_scope.sql @@ -0,0 +1,11 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- 历史 admin_app_configs 没有租户列,所有 App 共同读取同一行。迁移把这些行保留为 app_code='' +-- 的只读默认基线:未知或未来新增 App 仍能读取原配置,后续写操作只创建当前 App 的 scoped override。 +-- 同一个 key 可以同时存在一行 legacy 基线和多行 App override;三元唯一键是隔离写入的最终兜底。 +ALTER TABLE admin_app_configs + ADD COLUMN app_code VARCHAR(32) NOT NULL DEFAULT '' COMMENT '应用编码;空值仅表示迁移前的只读默认基线' AFTER id, + ADD COLUMN is_deleted BOOLEAN NOT NULL DEFAULT FALSE COMMENT '当前 App 删除墓碑;用于屏蔽默认基线' AFTER description, + DROP INDEX uk_admin_app_configs_group_key, + ADD UNIQUE KEY uk_admin_app_config_app_group_key (app_code, `group`, `key`), + ADD KEY idx_admin_app_configs_app_group (app_code, `group`); diff --git a/server/admin/migrations/087_legacy_google_paid_sync_attempts.sql b/server/admin/migrations/087_legacy_google_paid_sync_attempts.sql new file mode 100644 index 00000000..c4ed8d61 --- /dev/null +++ b/server/admin/migrations/087_legacy_google_paid_sync_attempts.sql @@ -0,0 +1,15 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- Google paid-detail failures are not paid facts. Keep retry state in a separate table so the +-- finance list/overview can continue treating admin_legacy_google_paid_details as successful cache only. +CREATE TABLE IF NOT EXISTS admin_legacy_google_paid_sync_attempts ( + app_code VARCHAR(32) NOT NULL COMMENT 'legacy App code', + transaction_id VARCHAR(96) NOT NULL COMMENT 'in_app_purchase_details._id', + attempt_count INT NOT NULL DEFAULT 0 COMMENT 'consecutive failed attempts', + last_error VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'last sanitized synchronization error', + next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT 'next retry time, UTC epoch ms', + created_at_ms BIGINT NOT NULL COMMENT 'created time, UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT 'updated time, UTC epoch ms', + PRIMARY KEY (app_code, transaction_id), + KEY idx_legacy_google_paid_retry (app_code, next_retry_at_ms, updated_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='legacy Google paid-detail retry state'; diff --git a/server/admin/migrations/088_fami_guild_revenue_policy.sql b/server/admin/migrations/088_fami_guild_revenue_policy.sql new file mode 100644 index 00000000..8937fe65 --- /dev/null +++ b/server/admin/migrations/088_fami_guild_revenue_policy.sql @@ -0,0 +1,66 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED); + +-- 收益策略模板是 Lalu/Huwaa/Fami 共用的产品配置入口。存量模板显式补 0,避免旧实例重发时产生新分成。 +UPDATE admin_policy_templates +SET rule_json = JSON_SET(rule_json, '$.agency.point_ratio_percent', 0), + updated_at_ms = @now_ms +WHERE template_code = 'first_google70000_coin_seller_92000_100000_v1' + AND template_version = 'v1' + AND JSON_EXTRACT(rule_json, '$.agency.point_ratio_percent') IS NULL; + +UPDATE admin_policy_template_versions +SET rule_json = JSON_SET(rule_json, '$.agency.point_ratio_percent', 0), + updated_at_ms = @now_ms +WHERE template_code = 'first_google70000_coin_seller_92000_100000_v1' + AND template_version = 'v1' + AND JSON_EXTRACT(rule_json, '$.agency.point_ratio_percent') IS NULL; + +-- Fami 从同一份公共政策结构派生独立模板,默认 20%;运营可在 Admin 编辑并发布,不需要改代码。 +INSERT INTO admin_policy_templates ( + template_code, template_version, name, status, rule_json, description, + created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms +) +SELECT + 'fami_guild_revenue_policy', 'v1', 'Fami 公会收益政策', 'active', + JSON_SET(rule_json, '$.agency.point_ratio_percent', 20), + 'Fami 主播与 Agency POINT 收益政策;Agency 默认 20%,以发布后的运行快照为准。', + 0, 0, @now_ms, @now_ms +FROM admin_policy_templates +WHERE template_code = 'first_google70000_coin_seller_92000_100000_v1' AND template_version = 'v1' +ON DUPLICATE KEY UPDATE + name = VALUES(name), + status = VALUES(status), + description = VALUES(description), + updated_at_ms = @now_ms; + +INSERT INTO admin_policy_template_versions ( + template_code, template_version, status, rule_json, + created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms +) +SELECT + template_code, template_version, status, rule_json, + created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms +FROM admin_policy_templates +WHERE template_code = 'fami_guild_revenue_policy' AND template_version = 'v1' +ON DUPLICATE KEY UPDATE + status = VALUES(status), + rule_json = VALUES(rule_json), + updated_at_ms = @now_ms; + +INSERT INTO admin_policy_instances ( + app_code, instance_code, template_code, template_version, region_scope, status, + effective_from_ms, effective_to_ms, publish_status, created_by_admin_id, updated_by_admin_id, + created_at_ms, updated_at_ms +) VALUES ( + 'fami', 'fami_guild_revenue_policy', 'fami_guild_revenue_policy', 'v1', + 'all_active_regions', 'active', @now_ms, 0, 'draft', 0, 0, @now_ms, @now_ms +) +ON DUPLICATE KEY UPDATE + template_code = VALUES(template_code), + template_version = VALUES(template_version), + region_scope = VALUES(region_scope), + status = VALUES(status), + effective_to_ms = VALUES(effective_to_ms), + updated_at_ms = @now_ms; diff --git a/server/admin/migrations/089_point_withdrawal_coin_seller_config.sql b/server/admin/migrations/089_point_withdrawal_coin_seller_config.sql new file mode 100644 index 00000000..6148a72b --- /dev/null +++ b/server/admin/migrations/089_point_withdrawal_coin_seller_config.sql @@ -0,0 +1,33 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- 币商提现白名单是按当前后台 App 范围编辑的公共能力;不在表中复制 Who/Sofun 等固定开关, +-- 新增 App 时复用 X-App-Code 作用域即可,避免每增加一套公会页面就增加一列。 +SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED); + +INSERT INTO admin_permissions (name, code, kind, description, created_at_ms, updated_at_ms) VALUES + ('币商提现白名单查看', 'point-withdrawal-config:view', 'menu', '查看当前 App 的 POINT 提现币商、服务国家和兑换比例', @now_ms, @now_ms), + ('币商提现白名单编辑', 'point-withdrawal-config:update', 'button', '新增、编辑、禁用或删除当前 App 的 POINT 提现币商配置', @now_ms, @now_ms) +ON DUPLICATE KEY UPDATE + name = VALUES(name), kind = VALUES(kind), description = VALUES(description), updated_at_ms = @now_ms; + +INSERT INTO admin_menus (parent_id, title, code, path, icon, permission_code, sort, visible, created_at_ms, updated_at_ms) +SELECT parent.id, '币商提现白名单', 'host-org-point-withdrawal-config', '/host/point-withdrawal-config', 'wallet', 'point-withdrawal-config:view', 89, TRUE, @now_ms, @now_ms +FROM admin_menus parent +WHERE parent.code = 'host-org' +ON DUPLICATE KEY UPDATE + parent_id = VALUES(parent_id), title = VALUES(title), path = VALUES(path), icon = VALUES(icon), + permission_code = VALUES(permission_code), sort = VALUES(sort), visible = VALUES(visible), updated_at_ms = @now_ms; + +INSERT IGNORE INTO admin_role_permissions (role_id, permission_id) +SELECT role.id, permission.id +FROM admin_roles role +JOIN admin_permissions permission +WHERE role.code IN ('platform-admin', 'ops-admin') + AND permission.code IN ('point-withdrawal-config:view', 'point-withdrawal-config:update'); + +INSERT IGNORE INTO admin_role_permissions (role_id, permission_id) +SELECT role.id, permission.id +FROM admin_roles role +JOIN admin_permissions permission +WHERE role.code IN ('auditor', 'readonly') + AND permission.code = 'point-withdrawal-config:view'; diff --git a/server/admin/migrations/090_point_withdrawal_policy_snapshot.sql b/server/admin/migrations/090_point_withdrawal_policy_snapshot.sql new file mode 100644 index 00000000..8ff3ad29 --- /dev/null +++ b/server/admin/migrations/090_point_withdrawal_policy_snapshot.sql @@ -0,0 +1,9 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- POINT 提现审核必须使用用户提交时的已发布政策快照;否则 Admin 后续修改汇率/手续费会让冻结、展示和审核扣款口径不一致。 +ALTER TABLE admin_user_withdrawal_applications + ADD COLUMN point_fee_amount BIGINT NOT NULL DEFAULT 0 COMMENT 'POINT 提现手续费积分快照' AFTER withdraw_amount_minor, + ADD COLUMN point_net_amount BIGINT NOT NULL DEFAULT 0 COMMENT 'POINT 提现预计到账积分快照' AFTER point_fee_amount, + ADD COLUMN points_per_usd BIGINT NOT NULL DEFAULT 100000 COMMENT 'POINT/USD 汇率快照' AFTER point_net_amount, + ADD COLUMN point_fee_bps INT NOT NULL DEFAULT 500 COMMENT 'POINT 提现手续费 bps 快照' AFTER points_per_usd, + ADD COLUMN point_policy_instance_code VARCHAR(96) NOT NULL DEFAULT '' COMMENT '已发布钱包政策实例快照' AFTER point_fee_bps; diff --git a/server/admin/migrations/091_h5_config_strict_app_scope.sql b/server/admin/migrations/091_h5_config_strict_app_scope.sql new file mode 100644 index 00000000..b40055ea --- /dev/null +++ b/server/admin/migrations/091_h5_config_strict_app_scope.sql @@ -0,0 +1,18 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- 086 曾把迁移前的 H5 行保留为 app_code='' 全局基线,导致所有未配置 App 都继承同一批链接。 +-- 历史后台默认 App 是 lalu:若 lalu 已有同名 override,保留 override 并删除旧基线;其余基线归属 lalu。 +DELETE legacy +FROM admin_app_configs legacy +JOIN admin_app_configs scoped + ON scoped.app_code = 'lalu' + AND scoped.`group` = legacy.`group` + AND scoped.`key` = legacy.`key` +WHERE legacy.app_code = '' + AND legacy.`group` = 'h5-links'; + +UPDATE admin_app_configs +SET app_code = 'lalu', + is_deleted = FALSE +WHERE app_code = '' + AND `group` = 'h5-links'; diff --git a/services/activity-service/cmd/audit-wheel-rewards/main.go b/services/activity-service/cmd/audit-wheel-rewards/main.go new file mode 100644 index 00000000..dd20d692 --- /dev/null +++ b/services/activity-service/cmd/audit-wheel-rewards/main.go @@ -0,0 +1,698 @@ +// Command audit-wheel-rewards performs a read-only, item-by-item reconciliation +// between unsettled WheelRewardSettlement facts and wallet owner receipts. +package main + +import ( + "context" + "crypto/sha256" + "database/sql" + "encoding/hex" + "encoding/json" + "errors" + "flag" + "fmt" + "log" + "os" + "sort" + "strconv" + "strings" + "time" + "unicode" + + _ "github.com/go-sql-driver/mysql" + "hyapp/pkg/appcode" + "hyapp/services/activity-service/internal/config" + domain "hyapp/services/activity-service/internal/domain/wheel" +) + +type outboxRow struct { + AppCode string + OutboxID string + Payload []byte + Status string + RetryCount int + CreatedMS int64 +} + +type rewardPayload struct { + AppCode string `json:"app_code"` + DrawID string `json:"draw_id"` + DrawIDs []string `json:"draw_ids"` + CommandID string `json:"command_id"` + UserID int64 `json:"user_id"` + WheelID string `json:"wheel_id"` + SelectedTierID string `json:"selected_tier_id"` + RewardType string `json:"reward_type"` + RewardID string `json:"reward_id"` + RewardCount int64 `json:"reward_count"` + RewardCoins int64 `json:"reward_coins"` + MetadataJSON string `json:"metadata_json"` + Rewards []domain.DrawReward `json:"rewards"` + VisibleRegionID int64 `json:"visible_region_id"` +} + +type itemAudit struct { + CommandID string `json:"command_id"` + Type string `json:"type"` + State string `json:"state"` + ReceiptID string `json:"receipt_id,omitempty"` + Reason string `json:"reason,omitempty"` +} + +type rowAudit struct { + AppCode string `json:"app_code"` + OutboxID string `json:"outbox_id"` + Status string `json:"outbox_status"` + State string `json:"state"` + Items []itemAudit `json:"items"` +} + +type auditSummary struct { + Rows int `json:"rows"` + FullyPaid int `json:"fully_paid"` + FullyUnpaid int `json:"fully_unpaid"` + PartiallyPaid int `json:"partially_paid"` + Conflict int `json:"conflict"` + PaidItems int `json:"paid_items"` + UnpaidItems int `json:"unpaid_items"` + ConflictItems int `json:"conflict_items"` + AuditSHA256 string `json:"audit_sha256"` +} + +// outboxAuditIndex 是 FORCE INDEX 唯一允许接受的封闭枚举。information_schema 返回的是数据库数据, +// 不能把其中的任意字符串直接拼接进 SQL;查询构造器会再次按用途校验枚举并只输出下列编译期常量。 +type outboxAuditIndex uint8 + +const ( + outboxAuditIndexUnknown outboxAuditIndex = iota + outboxAuditIndexEventRetry + outboxAuditIndexLuckyRetry + outboxAuditIndexEventLock + outboxAuditIndexLuckyLock +) + +type outboxAuditIndexes struct { + Retry outboxAuditIndex + Lock outboxAuditIndex +} + +type appCodeFlags []string + +func (values *appCodeFlags) String() string { + return strings.Join(*values, ",") +} + +func (values *appCodeFlags) Set(value string) error { + value = strings.TrimSpace(value) + if value == "" { + return fmt.Errorf("app-code must not be empty") + } + *values = append(*values, value) + return nil +} + +func main() { + configPath := flag.String("config", "services/activity-service/configs/config.yaml", "path to activity-service config") + walletDatabase := flag.String("wallet-database", "hyapp_wallet", "wallet owner database on the same MySQL cluster") + limit := flag.Int("limit", 500, "maximum unsettled rows, 1..500") + details := flag.Bool("details", false, "emit one JSON reconciliation record per outbox row") + var requestedAppCodes appCodeFlags + flag.Var(&requestedAppCodes, "app-code", "required App partition to audit; repeat for multiple Apps, for example --app-code lalu --app-code fami") + flag.Parse() + + if *limit <= 0 || *limit > 500 { + log.Fatal("limit must be between 1 and 500") + } + walletDB := normalizedDatabaseName(*walletDatabase) + if walletDB == "" { + log.Fatal("wallet-database contains invalid characters") + } + appCodes, err := normalizeAuditAppCodes(requestedAppCodes) + if err != nil { + log.Fatal(err) + } + cfg, err := config.Load(*configPath) + if err != nil { + log.Fatal(err) + } + db, err := sql.Open("mysql", cfg.MySQLDSN) + if err != nil { + log.Fatal(err) + } + defer func() { _ = db.Close() }() + db.SetMaxOpenConns(2) + db.SetMaxIdleConns(1) + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + if err := db.PingContext(ctx); err != nil { + log.Fatal(err) + } + indexes, err := discoverOutboxAuditIndexes(ctx, db) + if err != nil { + log.Fatal(err) + } + rows, err := loadUnsettledRows(ctx, db, indexes, appCodes, *limit) + if err != nil { + log.Fatal(err) + } + audits := make([]rowAudit, 0, len(rows)) + for _, row := range rows { + audit, err := auditRow(ctx, db, walletDB, row) + if err != nil { + log.Fatalf("audit app_code=%s outbox_id=%s: %v", row.AppCode, row.OutboxID, err) + } + audits = append(audits, audit) + } + summary := summarize(audits) + if *details { + encoder := json.NewEncoder(os.Stdout) + for _, audit := range audits { + if err := encoder.Encode(audit); err != nil { + log.Fatal(err) + } + } + } + encoded, err := json.Marshal(summary) + if err != nil { + log.Fatal(err) + } + fmt.Println(string(encoded)) +} + +func loadUnsettledRows(ctx context.Context, db *sql.DB, indexes outboxAuditIndexes, requestedAppCodes []string, limit int) ([]outboxRow, error) { + if limit <= 0 || limit > 500 { + return nil, fmt.Errorf("limit must be between 1 and 500") + } + appCodes, err := normalizeAuditAppCodes(requestedAppCodes) + if err != nil { + return nil, err + } + retryQuery, err := unsettledRetryQuery(indexes.Retry) + if err != nil { + return nil, err + } + deliveringQuery, err := unsettledDeliveringQuery(indexes.Lock) + if err != nil { + return nil, err + } + // activity_outbox 的线上索引都以 app_code 开头。每个 App 拆成 retry/lock 两个等值分区查询, + // 既能强制使用对应索引,也避免 status OR 让优化器退化为全表扫描。每个分支只取 global remaining, + // 合并出该 App 最早的一页后再进入下一 App,因此返回量严格不超过 limit,数据库返回行数也被约束在约 2*limit 内。 + result := make([]outboxRow, 0, limit) + for _, appCodeValue := range appCodes { + remaining := limit - len(result) + if remaining <= 0 { + break + } + branches := []struct { + query string + args []any + }{ + { + query: retryQuery, + args: []any{ + appCodeValue, + domain.EventTypeWheelRewardSettlement, + domain.OutboxStatusPending, + domain.OutboxStatusRetryable, + remaining, + }, + }, + { + query: deliveringQuery, + args: []any{ + appCodeValue, + domain.EventTypeWheelRewardSettlement, + domain.OutboxStatusDelivering, + remaining, + }, + }, + } + appCandidates := make([]outboxRow, 0, remaining) + seen := make(map[string]struct{}, remaining) + for _, branch := range branches { + rows, err := loadUnsettledBranch(ctx, db, branch.query, branch.args...) + if err != nil { + return nil, fmt.Errorf("load app_code=%s: %w", appCodeValue, err) + } + for _, row := range rows { + key := row.AppCode + "\x00" + row.OutboxID + if _, exists := seen[key]; exists { + continue + } + seen[key] = struct{}{} + appCandidates = append(appCandidates, row) + } + } + sort.Slice(appCandidates, func(i, j int) bool { + if appCandidates[i].CreatedMS != appCandidates[j].CreatedMS { + return appCandidates[i].CreatedMS < appCandidates[j].CreatedMS + } + if appCandidates[i].OutboxID != appCandidates[j].OutboxID { + return appCandidates[i].OutboxID < appCandidates[j].OutboxID + } + return appCandidates[i].Status < appCandidates[j].Status + }) + if len(appCandidates) > remaining { + appCandidates = appCandidates[:remaining] + } + result = append(result, appCandidates...) + } + return result, nil +} + +func discoverOutboxAuditIndexes(ctx context.Context, db *sql.DB) (outboxAuditIndexes, error) { + rows, err := db.QueryContext(ctx, outboxAuditIndexDiscoveryQuery()) + if err != nil { + return outboxAuditIndexes{}, fmt.Errorf("discover activity_outbox audit indexes: %w", err) + } + defer rows.Close() + + type indexPrefix struct { + columns [3]string + seen [3]bool + invalid bool + } + prefixes := make(map[outboxAuditIndex]*indexPrefix, 4) + for rows.Next() { + var indexName, columnName string + var sequence int + if err := rows.Scan(&indexName, &sequence, &columnName); err != nil { + return outboxAuditIndexes{}, fmt.Errorf("scan activity_outbox audit index: %w", err) + } + index, ok := outboxAuditIndexByName(indexName) + if !ok || sequence < 1 || sequence > 3 { + continue + } + prefix := prefixes[index] + if prefix == nil { + prefix = &indexPrefix{} + prefixes[index] = prefix + } + position := sequence - 1 + if prefix.seen[position] { + prefix.invalid = true + continue + } + prefix.seen[position] = true + prefix.columns[position] = strings.ToLower(strings.TrimSpace(columnName)) + } + if err := rows.Err(); err != nil { + return outboxAuditIndexes{}, fmt.Errorf("iterate activity_outbox audit indexes: %w", err) + } + + validPrefix := func(index outboxAuditIndex) bool { + prefix := prefixes[index] + if prefix == nil || prefix.invalid { + return false + } + expected := [3]string{"app_code", "event_type", "status"} + return prefix.seen == [3]bool{true, true, true} && prefix.columns == expected + } + pick := func(preferred, legacy outboxAuditIndex) outboxAuditIndex { + if validPrefix(preferred) { + return preferred + } + if validPrefix(legacy) { + return legacy + } + return outboxAuditIndexUnknown + } + indexes := outboxAuditIndexes{ + Retry: pick(outboxAuditIndexEventRetry, outboxAuditIndexLuckyRetry), + Lock: pick(outboxAuditIndexEventLock, outboxAuditIndexLuckyLock), + } + // 缺失或列前缀不匹配时必须中止。核账命令不能静默退回无索引查询,否则线上只读审计也可能拖垮主库。 + if indexes.Retry == outboxAuditIndexUnknown || indexes.Lock == outboxAuditIndexUnknown { + return outboxAuditIndexes{}, fmt.Errorf("activity_outbox requires whitelisted retry and lock indexes with prefix (app_code,event_type,status)") + } + return indexes, nil +} + +func outboxAuditIndexDiscoveryQuery() string { + return ` + SELECT index_name, seq_in_index, column_name + FROM information_schema.statistics + WHERE table_schema = DATABASE() + AND table_name = 'activity_outbox' + AND index_name IN ( + 'idx_activity_outbox_event_retry', + 'idx_activity_outbox_lucky_retry', + 'idx_activity_outbox_event_lock', + 'idx_activity_outbox_lucky_lock' + ) + AND seq_in_index <= 3 + ORDER BY index_name, seq_in_index` +} + +func outboxAuditIndexByName(value string) (outboxAuditIndex, bool) { + switch strings.TrimSpace(value) { + case "idx_activity_outbox_event_retry": + return outboxAuditIndexEventRetry, true + case "idx_activity_outbox_lucky_retry": + return outboxAuditIndexLuckyRetry, true + case "idx_activity_outbox_event_lock": + return outboxAuditIndexEventLock, true + case "idx_activity_outbox_lucky_lock": + return outboxAuditIndexLuckyLock, true + default: + return outboxAuditIndexUnknown, false + } +} + +func unsettledRetryQuery(index outboxAuditIndex) (string, error) { + var indexName string + switch index { + case outboxAuditIndexEventRetry: + indexName = "idx_activity_outbox_event_retry" + case outboxAuditIndexLuckyRetry: + indexName = "idx_activity_outbox_lucky_retry" + default: + return "", fmt.Errorf("retry FORCE INDEX is not whitelisted") + } + return fmt.Sprintf(` + SELECT app_code, outbox_id, payload, status, retry_count, created_at_ms + FROM activity_outbox FORCE INDEX (%s) + WHERE app_code = ? AND event_type = ? AND status IN (?, ?) + ORDER BY created_at_ms, outbox_id + LIMIT ?`, indexName), nil +} + +func unsettledDeliveringQuery(index outboxAuditIndex) (string, error) { + var indexName string + switch index { + case outboxAuditIndexEventLock: + indexName = "idx_activity_outbox_event_lock" + case outboxAuditIndexLuckyLock: + indexName = "idx_activity_outbox_lucky_lock" + default: + return "", fmt.Errorf("lock FORCE INDEX is not whitelisted") + } + return fmt.Sprintf(` + SELECT app_code, outbox_id, payload, status, retry_count, created_at_ms + FROM activity_outbox FORCE INDEX (%s) + WHERE app_code = ? AND event_type = ? AND status = ? + ORDER BY created_at_ms, outbox_id + LIMIT ?`, indexName), nil +} + +func loadUnsettledBranch(ctx context.Context, db *sql.DB, query string, args ...any) ([]outboxRow, error) { + rows, err := db.QueryContext(ctx, query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + result := make([]outboxRow, 0) + for rows.Next() { + var row outboxRow + if err := rows.Scan(&row.AppCode, &row.OutboxID, &row.Payload, &row.Status, &row.RetryCount, &row.CreatedMS); err != nil { + return nil, err + } + result = append(result, row) + } + return result, rows.Err() +} + +func normalizeAuditAppCodes(values []string) ([]string, error) { + if len(values) == 0 { + return nil, fmt.Errorf("at least one --app-code is required") + } + const maxAuditAppCodes = 16 + seen := make(map[string]struct{}, len(values)) + result := make([]string, 0, len(values)) + for _, value := range values { + if strings.TrimSpace(value) == "" { + return nil, fmt.Errorf("app-code must not be empty") + } + value = appcode.Normalize(value) + if _, exists := seen[value]; exists { + continue + } + seen[value] = struct{}{} + result = append(result, value) + } + if len(result) == 0 { + return nil, fmt.Errorf("at least one --app-code is required") + } + if len(result) > maxAuditAppCodes { + return nil, fmt.Errorf("at most %d app-code values are allowed", maxAuditAppCodes) + } + sort.Strings(result) + return result, nil +} + +func auditRow(ctx context.Context, db *sql.DB, walletDB string, row outboxRow) (rowAudit, error) { + var payload rewardPayload + if err := json.Unmarshal(row.Payload, &payload); err != nil { + return rowAudit{}, fmt.Errorf("decode immutable payload: %w", err) + } + row.AppCode = appcode.Normalize(row.AppCode) + if payload.AppCode != "" && appcode.Normalize(payload.AppCode) != row.AppCode { + return rowAudit{}, fmt.Errorf("payload app_code does not match outbox partition") + } + payload.DrawID = strings.TrimSpace(payload.DrawID) + payload.WheelID = strings.TrimSpace(payload.WheelID) + if payload.DrawID == "" || payload.WheelID == "" || payload.UserID <= 0 { + return rowAudit{}, fmt.Errorf("payload identity is incomplete") + } + rewards := payload.Rewards + if len(rewards) == 0 { + rewards = []domain.DrawReward{{ + SelectedTierID: payload.SelectedTierID, + RewardType: payload.RewardType, + RewardID: payload.RewardID, + RewardCount: payload.RewardCount, + RewardCoins: payload.RewardCoins, + MetadataJSON: payload.MetadataJSON, + }} + } + if len(rewards) == 0 { + return rowAudit{}, fmt.Errorf("payload reward snapshot is empty") + } + + audit := rowAudit{AppCode: row.AppCode, OutboxID: row.OutboxID, Status: row.Status, Items: make([]itemAudit, 0, len(rewards))} + for index, reward := range rewards { + reward.RewardType = strings.ToLower(strings.TrimSpace(reward.RewardType)) + var item itemAudit + var err error + switch reward.RewardType { + case domain.RewardTypeCoin: + item, err = auditCoin(ctx, db, walletDB, row.AppCode, payload, reward, index, len(rewards)) + case domain.RewardTypeGift, domain.RewardTypeProp, domain.RewardTypeDress: + item, err = auditResource(ctx, db, walletDB, row.AppCode, payload, reward, index, len(rewards)) + default: + item = itemAudit{Type: reward.RewardType, State: "conflict", Reason: "unsupported reward type"} + } + if err != nil { + return rowAudit{}, err + } + audit.Items = append(audit.Items, item) + } + audit.State = aggregateRowState(audit.Items) + return audit, nil +} + +func auditCoin(ctx context.Context, db *sql.DB, walletDB string, app string, payload rewardPayload, reward domain.DrawReward, index int, total int) (itemAudit, error) { + commandID := rewardCommandID("wheel_reward", payload.DrawID, index, total) + item := itemAudit{CommandID: commandID, Type: domain.RewardTypeCoin, State: "unpaid"} + query := coinAuditQuery(walletDB) + var transactionID, bizType, statusValue, metadataJSON string + err := db.QueryRowContext(ctx, query, app, commandID).Scan(&transactionID, &bizType, &statusValue, &metadataJSON) + if err != nil { + // database/sql 和 driver/observer 包装都可能给 ErrNoRows 增加上下文;审计必须把这种稳定语义归为 unpaid,而不是中止整批核账。 + if errors.Is(err, sql.ErrNoRows) { + return item, nil + } + return itemAudit{}, err + } + metadata := map[string]any{} + if err := json.Unmarshal([]byte(metadataJSON), &metadata); err != nil { + item.State, item.Reason = "conflict", "wallet metadata is invalid" + return item, nil + } + // wallet_transactions 和 resource_grants 共用 succeeded 终态;completed 从未是当前账本状态,不能把真实已发金币误判为冲突。 + if bizType != "wheel_reward" || statusValue != "succeeded" || jsonInt64(metadata, "target_user_id") != payload.UserID || jsonInt64(metadata, "amount") != reward.RewardCoins || jsonString(metadata, "draw_id") != payload.DrawID || jsonString(metadata, "wheel_id") != payload.WheelID || jsonString(metadata, "selected_tier_id") != strings.TrimSpace(reward.SelectedTierID) || jsonString(metadata, "reason") != "wheel_reward" || jsonInt64(metadata, "visible_region_id") != payload.VisibleRegionID { + item.State, item.Reason = "conflict", "existing wallet transaction does not match immutable reward snapshot" + return item, nil + } + item.State = "paid" + item.ReceiptID = transactionID + return item, nil +} + +func coinAuditQuery(walletDB string) string { + return fmt.Sprintf(` + SELECT transaction_id, biz_type, status, COALESCE(CAST(metadata_json AS CHAR), '{}') + FROM %s.wallet_transactions + WHERE app_code = ? AND command_id = ?`, walletDB) +} + +func auditResource(ctx context.Context, db *sql.DB, walletDB string, app string, payload rewardPayload, reward domain.DrawReward, index int, total int) (itemAudit, error) { + commandID := rewardCommandID("wheel_resource", payload.DrawID, index, total) + item := itemAudit{CommandID: commandID, Type: reward.RewardType, State: "unpaid"} + resourceID := rewardResourceID(reward) + quantity := reward.RewardCount + if quantity <= 0 { + quantity = 1 + } + durationMS := metadataInt64(reward.MetadataJSON, "duration_ms", "durationMs", "validity_ms", "validityMs") + query := resourceAuditQuery(walletDB) + var grantID, statusValue, grantSource, subjectType, subjectID, reason string + var targetUserID, operatorUserID, actualQuantity, actualDurationMS, actualResourceID int64 + err := db.QueryRowContext(ctx, query, app, commandID).Scan(&grantID, &statusValue, &targetUserID, &grantSource, &subjectType, &subjectID, &reason, &operatorUserID, &actualQuantity, &actualDurationMS, &actualResourceID) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return item, nil + } + return itemAudit{}, err + } + if resourceID <= 0 || statusValue != "succeeded" || targetUserID != payload.UserID || grantSource != "wheel_reward" || subjectType != "resource" || subjectID != strconv.FormatInt(resourceID, 10) || reason != "wheel_reward" || operatorUserID != payload.UserID || actualQuantity != quantity || actualDurationMS != durationMS || actualResourceID != resourceID { + item.State, item.Reason = "conflict", "existing resource grant does not match immutable reward snapshot" + return item, nil + } + item.State = "paid" + item.ReceiptID = grantID + return item, nil +} + +func resourceAuditQuery(walletDB string) string { + // 每个 SELECT 列只出现一次且与 Scan 顺序一一对应;显式逐列排版避免核账命令在生产才暴露漏逗号或重复列。 + return fmt.Sprintf(` + SELECT rg.grant_id, + rg.status, + rg.target_user_id, + rg.grant_source, + rg.grant_subject_type, + rg.grant_subject_id, + rg.reason, + rg.operator_user_id, + COALESCE(rgi.quantity, 0), + COALESCE(rgi.duration_ms, 0), + COALESCE(rgi.resource_id, 0) + FROM %s.resource_grants rg + LEFT JOIN %s.resource_grant_items rgi + ON rgi.app_code = rg.app_code AND rgi.grant_id = rg.grant_id + WHERE rg.app_code = ? AND rg.command_id = ? + ORDER BY rgi.grant_item_id + LIMIT 1`, walletDB, walletDB) +} + +func aggregateRowState(items []itemAudit) string { + paid, unpaid, conflict := 0, 0, 0 + for _, item := range items { + switch item.State { + case "paid": + paid++ + case "unpaid": + unpaid++ + default: + conflict++ + } + } + if conflict > 0 { + return "conflict" + } + if paid == len(items) { + return "fully_paid" + } + if unpaid == len(items) { + return "fully_unpaid" + } + return "partially_paid" +} + +func summarize(audits []rowAudit) auditSummary { + summary := auditSummary{Rows: len(audits)} + canonical := make([]string, 0, len(audits)) + for _, audit := range audits { + switch audit.State { + case "fully_paid": + summary.FullyPaid++ + case "fully_unpaid": + summary.FullyUnpaid++ + case "partially_paid": + summary.PartiallyPaid++ + default: + summary.Conflict++ + } + for _, item := range audit.Items { + switch item.State { + case "paid": + summary.PaidItems++ + case "unpaid": + summary.UnpaidItems++ + default: + summary.ConflictItems++ + } + } + encoded, _ := json.Marshal(audit) + canonical = append(canonical, string(encoded)) + } + sort.Strings(canonical) + sum := sha256.Sum256([]byte(strings.Join(canonical, "\n"))) + summary.AuditSHA256 = hex.EncodeToString(sum[:]) + return summary +} + +func rewardCommandID(prefix string, drawID string, index int, total int) string { + if total <= 1 { + return prefix + ":" + drawID + } + return fmt.Sprintf("%s:%s:%d", prefix, drawID, index+1) +} + +func rewardResourceID(reward domain.DrawReward) int64 { + if value := metadataInt64(reward.MetadataJSON, "resource_id", "resourceId"); value > 0 { + return value + } + value, _ := strconv.ParseInt(strings.TrimSpace(reward.RewardID), 10, 64) + return value +} + +func metadataInt64(raw string, keys ...string) int64 { + metadata := map[string]any{} + if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &metadata); err != nil { + return 0 + } + for _, key := range keys { + if value := jsonInt64(metadata, key); value > 0 { + return value + } + } + return 0 +} + +func jsonInt64(values map[string]any, key string) int64 { + switch value := values[key].(type) { + case float64: + return int64(value) + case json.Number: + parsed, _ := value.Int64() + return parsed + case string: + parsed, _ := strconv.ParseInt(strings.TrimSpace(value), 10, 64) + return parsed + default: + return 0 + } +} + +func jsonString(values map[string]any, key string) string { + value, _ := values[key].(string) + return strings.TrimSpace(value) +} + +func normalizedDatabaseName(value string) string { + value = strings.TrimSpace(value) + if value == "" { + return "" + } + for _, char := range value { + if char == '_' || unicode.IsDigit(char) || unicode.IsLetter(char) { + continue + } + return "" + } + return value +} diff --git a/services/activity-service/cmd/audit-wheel-rewards/main_test.go b/services/activity-service/cmd/audit-wheel-rewards/main_test.go new file mode 100644 index 00000000..35e0d360 --- /dev/null +++ b/services/activity-service/cmd/audit-wheel-rewards/main_test.go @@ -0,0 +1,398 @@ +package main + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + domain "hyapp/services/activity-service/internal/domain/wheel" +) + +func TestAuditRowCoinStates(t *testing.T) { + tests := []struct { + name string + prepare func(sqlmock.Sqlmock, rewardPayload) + wantState string + wantItem string + }{ + { + name: "fully paid", + prepare: func(mock sqlmock.Sqlmock, payload rewardPayload) { + metadata, _ := json.Marshal(map[string]any{ + "target_user_id": payload.UserID, + "amount": int64(200), + "draw_id": payload.DrawID, + "wheel_id": payload.WheelID, + "selected_tier_id": "coin-200", + "reason": "wheel_reward", + "visible_region_id": payload.VisibleRegionID, + }) + mock.ExpectQuery(regexp.QuoteMeta(coinAuditQuery("hyapp_wallet"))). + WithArgs("fami", "wheel_reward:draw-1"). + WillReturnRows(coinAuditRows().AddRow("wtx-1", "wheel_reward", "succeeded", string(metadata))) + }, + wantState: "fully_paid", + wantItem: "paid", + }, + { + name: "fully unpaid uses errors Is", + prepare: func(mock sqlmock.Sqlmock, _ rewardPayload) { + mock.ExpectQuery(regexp.QuoteMeta(coinAuditQuery("hyapp_wallet"))). + WithArgs("fami", "wheel_reward:draw-1"). + WillReturnError(fmt.Errorf("wrapped wallet miss: %w", sql.ErrNoRows)) + }, + wantState: "fully_unpaid", + wantItem: "unpaid", + }, + { + name: "conflict on immutable amount", + prepare: func(mock sqlmock.Sqlmock, payload rewardPayload) { + metadata, _ := json.Marshal(map[string]any{ + "target_user_id": payload.UserID, + "amount": int64(201), + "draw_id": payload.DrawID, + "wheel_id": payload.WheelID, + "selected_tier_id": "coin-200", + "reason": "wheel_reward", + "visible_region_id": payload.VisibleRegionID, + }) + mock.ExpectQuery(regexp.QuoteMeta(coinAuditQuery("hyapp_wallet"))). + WithArgs("fami", "wheel_reward:draw-1"). + WillReturnRows(coinAuditRows().AddRow("wtx-conflict", "wheel_reward", "succeeded", string(metadata))) + }, + wantState: "conflict", + wantItem: "conflict", + }, + { + name: "conflict on biz type", + prepare: func(mock sqlmock.Sqlmock, payload rewardPayload) { + metadata := matchingCoinMetadata(payload) + mock.ExpectQuery(regexp.QuoteMeta(coinAuditQuery("hyapp_wallet"))). + WithArgs("fami", "wheel_reward:draw-1"). + WillReturnRows(coinAuditRows().AddRow("wtx-wrong-biz", "task_reward", "succeeded", metadata)) + }, + wantState: "conflict", + wantItem: "conflict", + }, + { + name: "conflict on visible region", + prepare: func(mock sqlmock.Sqlmock, payload rewardPayload) { + metadata := matchingCoinMetadata(payload) + var values map[string]any + _ = json.Unmarshal([]byte(metadata), &values) + values["visible_region_id"] = int64(99) + mismatch, _ := json.Marshal(values) + mock.ExpectQuery(regexp.QuoteMeta(coinAuditQuery("hyapp_wallet"))). + WithArgs("fami", "wheel_reward:draw-1"). + WillReturnRows(coinAuditRows().AddRow("wtx-wrong-region", "wheel_reward", "succeeded", string(mismatch))) + }, + wantState: "conflict", + wantItem: "conflict", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + payload := rewardPayload{ + AppCode: "fami", + DrawID: "draw-1", + UserID: 1001, + WheelID: "classic", + VisibleRegionID: 9, + Rewards: []domain.DrawReward{{SelectedTierID: "coin-200", RewardType: domain.RewardTypeCoin, RewardCoins: 200}}, + } + test.prepare(mock, payload) + audit, err := auditRow(context.Background(), db, "hyapp_wallet", auditOutboxRow(t, payload)) + if err != nil { + t.Fatalf("auditRow returned error: %v", err) + } + if audit.State != test.wantState || len(audit.Items) != 1 || audit.Items[0].State != test.wantItem { + t.Fatalf("unexpected audit: %+v", audit) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("unmet SQL expectations: %v", err) + } + }) + } +} + +func TestAuditRowResourceStates(t *testing.T) { + tests := []struct { + name string + prepare func(sqlmock.Sqlmock) + wantState string + wantItem string + }{ + { + name: "fully paid", + prepare: func(mock sqlmock.Sqlmock) { + mock.ExpectQuery(regexp.QuoteMeta(resourceAuditQuery("hyapp_wallet"))). + WithArgs("fami", "wheel_resource:draw-1"). + WillReturnRows(resourceAuditRows().AddRow("rgr-1", "succeeded", int64(1001), "wheel_reward", "resource", "326", "wheel_reward", int64(1001), int64(2), int64(5000), int64(326))) + }, + wantState: "fully_paid", + wantItem: "paid", + }, + { + name: "fully unpaid uses errors Is", + prepare: func(mock sqlmock.Sqlmock) { + mock.ExpectQuery(regexp.QuoteMeta(resourceAuditQuery("hyapp_wallet"))). + WithArgs("fami", "wheel_resource:draw-1"). + WillReturnError(fmt.Errorf("wrapped resource miss: %w", sql.ErrNoRows)) + }, + wantState: "fully_unpaid", + wantItem: "unpaid", + }, + { + name: "conflict rejects legacy active status", + prepare: func(mock sqlmock.Sqlmock) { + mock.ExpectQuery(regexp.QuoteMeta(resourceAuditQuery("hyapp_wallet"))). + WithArgs("fami", "wheel_resource:draw-1"). + WillReturnRows(resourceAuditRows().AddRow("rgr-conflict", "active", int64(1001), "wheel_reward", "resource", "326", "wheel_reward", int64(1001), int64(2), int64(5000), int64(326))) + }, + wantState: "conflict", + wantItem: "conflict", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + payload := rewardPayload{ + AppCode: "fami", + DrawID: "draw-1", + UserID: 1001, + WheelID: "classic", + Rewards: []domain.DrawReward{{ + SelectedTierID: "gift-326", + RewardType: domain.RewardTypeGift, + RewardID: "326", + RewardCount: 2, + MetadataJSON: `{"resource_id":326,"duration_ms":5000}`, + }}, + } + test.prepare(mock) + audit, err := auditRow(context.Background(), db, "hyapp_wallet", auditOutboxRow(t, payload)) + if err != nil { + t.Fatalf("auditRow returned error: %v", err) + } + if audit.State != test.wantState || len(audit.Items) != 1 || audit.Items[0].State != test.wantItem { + t.Fatalf("unexpected audit: %+v", audit) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("unmet SQL expectations: %v", err) + } + }) + } +} + +func TestLoadUnsettledRowsUsesAppScopedIndexesAndGlobalLimit(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + const limit = 3 + columns := []string{"app_code", "outbox_id", "payload", "status", "retry_count", "created_at_ms"} + indexes := outboxAuditIndexes{Retry: outboxAuditIndexLuckyRetry, Lock: outboxAuditIndexLuckyLock} + retryQuery, err := unsettledRetryQuery(indexes.Retry) + if err != nil { + t.Fatalf("build retry query: %v", err) + } + deliveringQuery, err := unsettledDeliveringQuery(indexes.Lock) + if err != nil { + t.Fatalf("build delivering query: %v", err) + } + + // normalizeAuditAppCodes sorts and deduplicates, so SQL order is deterministic regardless of flag order. + mock.ExpectQuery(regexp.QuoteMeta(retryQuery)). + WithArgs("fami", domain.EventTypeWheelRewardSettlement, domain.OutboxStatusPending, domain.OutboxStatusRetryable, limit). + WillReturnRows(sqlmock.NewRows(columns). + AddRow("fami", "fami-pending", []byte(`{}`), domain.OutboxStatusPending, 0, int64(20))) + mock.ExpectQuery(regexp.QuoteMeta(deliveringQuery)). + WithArgs("fami", domain.EventTypeWheelRewardSettlement, domain.OutboxStatusDelivering, limit). + WillReturnRows(sqlmock.NewRows(columns). + AddRow("fami", "fami-delivering", []byte(`{}`), domain.OutboxStatusDelivering, 1, int64(10))) + mock.ExpectQuery(regexp.QuoteMeta(retryQuery)). + WithArgs("lalu", domain.EventTypeWheelRewardSettlement, domain.OutboxStatusPending, domain.OutboxStatusRetryable, 1). + WillReturnRows(sqlmock.NewRows(columns). + AddRow("lalu", "lalu-oldest", []byte(`{}`), domain.OutboxStatusRetryable, 2, int64(5))) + mock.ExpectQuery(regexp.QuoteMeta(deliveringQuery)). + WithArgs("lalu", domain.EventTypeWheelRewardSettlement, domain.OutboxStatusDelivering, 1). + WillReturnRows(sqlmock.NewRows(columns). + AddRow("lalu", "lalu-delivering", []byte(`{}`), domain.OutboxStatusDelivering, 1, int64(15))) + + rows, err := loadUnsettledRows(context.Background(), db, indexes, []string{"lalu", "fami", "fami"}, limit) + if err != nil { + t.Fatalf("loadUnsettledRows returned error: %v", err) + } + if len(rows) != limit { + t.Fatalf("global limit mismatch: got=%d want=%d rows=%+v", len(rows), limit, rows) + } + wantIDs := []string{"fami-delivering", "fami-pending", "lalu-oldest"} + for index, wantID := range wantIDs { + if rows[index].OutboxID != wantID { + t.Fatalf("deterministic order[%d]=%q want=%q rows=%+v", index, rows[index].OutboxID, wantID, rows) + } + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("unmet SQL expectations: %v", err) + } +} + +func TestDiscoverOutboxAuditIndexes(t *testing.T) { + indexColumns := []string{"index_name", "seq_in_index", "column_name"} + addPrefix := func(rows *sqlmock.Rows, indexName string, columns ...string) *sqlmock.Rows { + for position, column := range columns { + rows.AddRow(indexName, position+1, column) + } + return rows + } + tests := []struct { + name string + rows func() *sqlmock.Rows + want outboxAuditIndexes + wantError bool + }{ + { + name: "prefers current event indexes", + rows: func() *sqlmock.Rows { + rows := sqlmock.NewRows(indexColumns) + addPrefix(rows, "idx_activity_outbox_lucky_retry", "app_code", "event_type", "status") + addPrefix(rows, "idx_activity_outbox_event_retry", "app_code", "event_type", "status") + addPrefix(rows, "idx_activity_outbox_lucky_lock", "app_code", "event_type", "status") + addPrefix(rows, "idx_activity_outbox_event_lock", "app_code", "event_type", "status") + return rows + }, + want: outboxAuditIndexes{Retry: outboxAuditIndexEventRetry, Lock: outboxAuditIndexEventLock}, + }, + { + name: "falls back to deployed lucky indexes", + rows: func() *sqlmock.Rows { + rows := sqlmock.NewRows(indexColumns) + addPrefix(rows, "idx_activity_outbox_lucky_retry", "app_code", "event_type", "status") + addPrefix(rows, "idx_activity_outbox_lucky_lock", "app_code", "event_type", "status") + return rows + }, + want: outboxAuditIndexes{Retry: outboxAuditIndexLuckyRetry, Lock: outboxAuditIndexLuckyLock}, + }, + { + name: "rejects wrong prefix without an index-free fallback", + rows: func() *sqlmock.Rows { + rows := sqlmock.NewRows(indexColumns) + addPrefix(rows, "idx_activity_outbox_event_retry", "event_type", "app_code", "status") + addPrefix(rows, "idx_activity_outbox_event_lock", "app_code", "event_type", "status") + return rows + }, + wantError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + mock.ExpectQuery(regexp.QuoteMeta(outboxAuditIndexDiscoveryQuery())).WillReturnRows(test.rows()) + got, err := discoverOutboxAuditIndexes(context.Background(), db) + if test.wantError { + if err == nil { + t.Fatalf("discoverOutboxAuditIndexes returned indexes=%+v, want error", got) + } + } else if err != nil || got != test.want { + t.Fatalf("discoverOutboxAuditIndexes=(%+v, %v), want=(%+v, nil)", got, err, test.want) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("unmet SQL expectations: %v", err) + } + }) + } +} + +func TestUnsettledQueriesRejectNonWhitelistedIndexes(t *testing.T) { + if _, err := unsettledRetryQuery(outboxAuditIndexLuckyLock); err == nil { + t.Fatal("retry query accepted a lock index") + } + if _, err := unsettledDeliveringQuery(outboxAuditIndexLuckyRetry); err == nil { + t.Fatal("delivering query accepted a retry index") + } + if _, err := unsettledRetryQuery(outboxAuditIndex(255)); err == nil { + t.Fatal("retry query accepted an arbitrary identifier enum") + } +} + +func TestNormalizeAuditAppCodesRequiresRepeatedExplicitScope(t *testing.T) { + if _, err := normalizeAuditAppCodes(nil); err == nil { + t.Fatal("missing --app-code must be rejected before opening or querying MySQL") + } + values, err := normalizeAuditAppCodes([]string{" LALU ", "fami", "lalu", "huwaa"}) + if err != nil { + t.Fatalf("normalizeAuditAppCodes returned error: %v", err) + } + want := []string{"fami", "huwaa", "lalu"} + if len(values) != len(want) { + t.Fatalf("normalized app count=%d want=%d values=%+v", len(values), len(want), values) + } + for index := range want { + if values[index] != want[index] { + t.Fatalf("normalized app[%d]=%q want=%q", index, values[index], want[index]) + } + } +} + +func auditOutboxRow(t *testing.T, payload rewardPayload) outboxRow { + t.Helper() + body, err := json.Marshal(payload) + if err != nil { + t.Fatalf("marshal payload: %v", err) + } + return outboxRow{AppCode: payload.AppCode, OutboxID: "wheel_reward_" + payload.DrawID, Payload: body, Status: domain.OutboxStatusPending} +} + +func resourceAuditRows() *sqlmock.Rows { + return sqlmock.NewRows([]string{ + "grant_id", + "status", + "target_user_id", + "grant_source", + "grant_subject_type", + "grant_subject_id", + "reason", + "operator_user_id", + "quantity", + "duration_ms", + "resource_id", + }) +} + +func coinAuditRows() *sqlmock.Rows { + return sqlmock.NewRows([]string{"transaction_id", "biz_type", "status", "metadata_json"}) +} + +func matchingCoinMetadata(payload rewardPayload) string { + body, _ := json.Marshal(map[string]any{ + "target_user_id": payload.UserID, + "amount": int64(200), + "draw_id": payload.DrawID, + "wheel_id": payload.WheelID, + "selected_tier_id": "coin-200", + "reason": "wheel_reward", + "visible_region_id": payload.VisibleRegionID, + }) + return string(body) +} diff --git a/services/activity-service/configs/config.docker.yaml b/services/activity-service/configs/config.docker.yaml index d7d648f9..7ceff219 100644 --- a/services/activity-service/configs/config.docker.yaml +++ b/services/activity-service/configs/config.docker.yaml @@ -30,6 +30,13 @@ user_leaderboard_worker: redis_password: "" redis_db: 0 key_prefix: "activity:user_leaderboard" +wheel_reward_worker: + enabled: true + app_codes: ["lalu", "fami", "huwaa"] + poll_interval: "1s" + batch_size: 50 + lock_ttl: "30s" + max_retry: 16 message_action_confirm_worker: enabled: true outbox_poll_interval: "1s" @@ -50,6 +57,7 @@ tencent_im: request_timeout: "5s" broadcast: enabled: true + app_codes: ["lalu", "fami", "huwaa"] super_gift_min_value: 100000 worker_poll_interval: "1s" worker_batch_size: 100 @@ -70,6 +78,11 @@ rocketmq: topic: "hyapp_room_outbox" consumer_group: "hyapp-activity-room-outbox" consumer_max_reconsume_times: 16 + lucky_gift_outbox: + enabled: true + topic: "hyapp_lucky_gift_outbox" + consumer_group: "hyapp-activity-lucky-gift-outbox" + consumer_max_reconsume_times: 16 wallet_outbox: enabled: true topic: "hyapp_wallet_outbox" @@ -78,6 +91,7 @@ rocketmq: cumulative_recharge_consumer_group: "hyapp-activity-cumulative-recharge-wallet-outbox" invite_activity_consumer_group: "hyapp-activity-invite-wallet-outbox" red_packet_broadcast_consumer_group: "hyapp-activity-red-packet-wallet-outbox" + vip_rebate_notice_consumer_group: "hyapp-activity-vip-rebate-notice-wallet-outbox" task_consumer_group: "hyapp-activity-task-wallet-outbox" user_leaderboard_consumer_group: "hyapp-activity-user-leaderboard-wallet-outbox" consumer_max_reconsume_times: 16 diff --git a/services/activity-service/configs/config.tencent.example.yaml b/services/activity-service/configs/config.tencent.example.yaml index f9a811ee..ec6d0461 100644 --- a/services/activity-service/configs/config.tencent.example.yaml +++ b/services/activity-service/configs/config.tencent.example.yaml @@ -30,6 +30,13 @@ user_leaderboard_worker: redis_password: "TENCENT_REDIS_PASSWORD" redis_db: 0 key_prefix: "activity:user_leaderboard" +wheel_reward_worker: + enabled: true + app_codes: ["lalu", "fami", "huwaa"] + poll_interval: "1s" + batch_size: 50 + lock_ttl: "30s" + max_retry: 16 message_action_confirm_worker: enabled: true outbox_poll_interval: "1s" @@ -50,6 +57,8 @@ tencent_im: request_timeout: "5s" broadcast: enabled: true + # 共享腾讯 IM SDKAppID 时必须显式列出全部业务租户,确保空数据 App 也能预建播报群。 + app_codes: ["lalu", "fami", "huwaa"] super_gift_min_value: 100000 worker_poll_interval: "1s" worker_batch_size: 100 @@ -70,6 +79,11 @@ rocketmq: topic: "hyapp_room_outbox" consumer_group: "hyapp-activity-room-outbox" consumer_max_reconsume_times: 16 + lucky_gift_outbox: + enabled: true + topic: "hyapp_lucky_gift_outbox" + consumer_group: "hyapp-activity-lucky-gift-outbox" + consumer_max_reconsume_times: 16 wallet_outbox: enabled: true topic: "hyapp_wallet_outbox" @@ -78,6 +92,7 @@ rocketmq: cumulative_recharge_consumer_group: "hyapp-activity-cumulative-recharge-wallet-outbox" invite_activity_consumer_group: "hyapp-activity-invite-wallet-outbox" red_packet_broadcast_consumer_group: "hyapp-activity-red-packet-wallet-outbox" + vip_rebate_notice_consumer_group: "hyapp-activity-vip-rebate-notice-wallet-outbox" task_consumer_group: "hyapp-activity-task-wallet-outbox" user_leaderboard_consumer_group: "hyapp-activity-user-leaderboard-wallet-outbox" consumer_max_reconsume_times: 16 diff --git a/services/activity-service/configs/config.yaml b/services/activity-service/configs/config.yaml index 908f1060..546d6ef3 100644 --- a/services/activity-service/configs/config.yaml +++ b/services/activity-service/configs/config.yaml @@ -30,6 +30,13 @@ user_leaderboard_worker: redis_password: "" redis_db: 0 key_prefix: "activity:user_leaderboard" +wheel_reward_worker: + enabled: true + app_codes: ["lalu", "fami", "huwaa"] + poll_interval: "1s" + batch_size: 50 + lock_ttl: "30s" + max_retry: 16 message_action_confirm_worker: enabled: true outbox_poll_interval: "1s" @@ -50,6 +57,8 @@ tencent_im: request_timeout: "5s" broadcast: enabled: true + # 同一套腾讯 IM 云应用承载多个业务 App;群 ID 和 outbox 仍按 app_code 隔离。 + app_codes: ["lalu", "fami", "huwaa"] super_gift_min_value: 100000 worker_poll_interval: "1s" worker_batch_size: 100 @@ -70,6 +79,11 @@ rocketmq: topic: "hyapp_room_outbox" consumer_group: "hyapp-activity-room-outbox" consumer_max_reconsume_times: 16 + lucky_gift_outbox: + enabled: true + topic: "hyapp_lucky_gift_outbox" + consumer_group: "hyapp-activity-lucky-gift-outbox" + consumer_max_reconsume_times: 16 wallet_outbox: enabled: true topic: "hyapp_wallet_outbox" @@ -78,6 +92,7 @@ rocketmq: cumulative_recharge_consumer_group: "hyapp-activity-cumulative-recharge-wallet-outbox" invite_activity_consumer_group: "hyapp-activity-invite-wallet-outbox" red_packet_broadcast_consumer_group: "hyapp-activity-red-packet-wallet-outbox" + vip_rebate_notice_consumer_group: "hyapp-activity-vip-rebate-notice-wallet-outbox" task_consumer_group: "hyapp-activity-task-wallet-outbox" user_leaderboard_consumer_group: "hyapp-activity-user-leaderboard-wallet-outbox" consumer_max_reconsume_times: 16 diff --git a/services/activity-service/deploy/mysql/initdb/001_activity_service.sql b/services/activity-service/deploy/mysql/initdb/001_activity_service.sql index 1a046bd5..6ef1df87 100644 --- a/services/activity-service/deploy/mysql/initdb/001_activity_service.sql +++ b/services/activity-service/deploy/mysql/initdb/001_activity_service.sql @@ -695,6 +695,13 @@ CREATE TABLE IF NOT EXISTS growth_level_reward_jobs ( resource_group_id BIGINT NOT NULL COMMENT '资源分组 ID', wallet_command_id VARCHAR(128) NOT NULL COMMENT '钱包命令 ID', wallet_grant_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '钱包资源发放 ID', + reward_origin VARCHAR(32) NOT NULL DEFAULT 'organic' COMMENT '奖励来源:organic/temporary', + temporary_level_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '临时等级记录 ID', + grant_generation INT NOT NULL DEFAULT 0 COMMENT '同一奖励来源的发放代次', + reward_level INT NOT NULL DEFAULT 0 COMMENT '取得该奖励所需等级', + is_permanent TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否已提升为永久奖励', + temporary_expires_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '临时奖励到期时间,UTC epoch ms', + revoke_status VARCHAR(32) NOT NULL DEFAULT 'none' COMMENT 'none/pending/running/revoked/failed/skipped', status VARCHAR(32) NOT NULL COMMENT '业务状态', attempt_count INT NOT NULL DEFAULT 0 COMMENT '尝试次数', next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '下一次重试时间,UTC epoch ms', @@ -704,11 +711,70 @@ CREATE TABLE IF NOT EXISTS growth_level_reward_jobs ( created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', PRIMARY KEY (app_code, reward_job_id), - UNIQUE KEY uk_growth_level_reward_once (app_code, user_id, track, reward_source_type, reward_source_id), + UNIQUE KEY uk_growth_level_reward_generation (app_code, user_id, track, reward_source_type, reward_source_id, reward_origin, grant_generation), KEY idx_growth_level_reward_pending (app_code, status, next_retry_at_ms, created_at_ms), KEY idx_growth_level_reward_user (app_code, user_id, track, created_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='成长等级奖励任务表'; +CREATE TABLE IF NOT EXISTS growth_level_reward_wallet_grants ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + reward_job_id VARCHAR(96) NOT NULL COMMENT '奖励任务 ID', + grant_kind VARCHAR(32) NOT NULL COMMENT 'resource_group/avatar_frame/short_badge', + wallet_grant_id VARCHAR(96) NOT NULL COMMENT '钱包发放 ID', + revoke_status VARCHAR(32) NOT NULL DEFAULT 'none' COMMENT 'none/pending/running/revoked/failed/skipped', + revoke_attempt_count INT NOT NULL DEFAULT 0 COMMENT '撤回尝试次数', + revoke_next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '撤回重试时间', + revoke_locked_by VARCHAR(128) NOT NULL DEFAULT '' COMMENT '撤回 worker', + revoke_locked_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT '撤回锁截止', + revoke_failure_reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '撤回失败原因', + revoked_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '撤回完成时间', + created_at_ms BIGINT NOT NULL COMMENT '创建时间', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间', + PRIMARY KEY (app_code, reward_job_id, wallet_grant_id), + KEY idx_growth_reward_grant_revoke (app_code, revoke_status, revoke_next_retry_at_ms, created_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='等级奖励钱包发放明细'; + +CREATE TABLE IF NOT EXISTS growth_temporary_level_commands ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + command_id VARCHAR(128) NOT NULL COMMENT '整批调整幂等键', + request_hash VARCHAR(64) NOT NULL COMMENT '规范化请求 SHA256', + user_id BIGINT NOT NULL COMMENT '目标用户', + operator_admin_id BIGINT NOT NULL COMMENT '操作管理员', + created_at_ms BIGINT NOT NULL COMMENT '创建时间', + PRIMARY KEY (app_code, command_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='临时等级批量命令幂等表'; + +CREATE TABLE IF NOT EXISTS growth_temporary_levels ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + temporary_level_id VARCHAR(96) NOT NULL COMMENT '临时等级 ID', + command_id VARCHAR(128) NOT NULL COMMENT '批量命令 ID', + user_id BIGINT NOT NULL COMMENT '用户 ID', + track VARCHAR(32) NOT NULL COMMENT 'wealth/charm', + target_level INT NOT NULL COMMENT '临时目标等级', + target_required_value BIGINT NOT NULL COMMENT '目标等级起始阈值快照', + baseline_total_value BIGINT NOT NULL COMMENT '调整时真实累计值快照', + started_at_ms BIGINT NOT NULL COMMENT '有效期开始,包含', + expires_at_ms BIGINT NOT NULL COMMENT '有效期结束,不包含', + status VARCHAR(32) NOT NULL COMMENT 'active/superseded/expired', + generation INT NOT NULL COMMENT '用户轨道临时发放代次', + operator_admin_id BIGINT NOT NULL COMMENT '操作管理员', + reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '操作原因', + final_level INT NOT NULL DEFAULT 0 COMMENT '按基线和有效期经验计算的恢复等级', + activation_notice_status VARCHAR(32) NOT NULL DEFAULT 'pending' COMMENT '下发通知状态', + expiry_notice_status VARCHAR(32) NOT NULL DEFAULT 'skipped' COMMENT '到期通知状态', + attempt_count INT NOT NULL DEFAULT 0 COMMENT '副作用尝试次数', + next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '副作用重试时间', + locked_by VARCHAR(128) NOT NULL DEFAULT '' COMMENT '当前 worker', + locked_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT '锁截止时间', + failure_reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '副作用失败原因', + created_at_ms BIGINT NOT NULL COMMENT '创建时间', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间', + PRIMARY KEY (app_code, temporary_level_id), + UNIQUE KEY uk_growth_temp_command_track (app_code, command_id, track), + KEY idx_growth_temp_user_track (app_code, user_id, track, status, expires_at_ms), + KEY idx_growth_temp_work (app_code, status, next_retry_at_ms, expires_at_ms, created_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户临时成长等级 overlay'; + CREATE TABLE IF NOT EXISTS user_level_display_profiles ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', user_id BIGINT NOT NULL COMMENT '用户 ID', diff --git a/services/activity-service/deploy/mysql/migrations/009_temporary_growth_levels.sql b/services/activity-service/deploy/mysql/migrations/009_temporary_growth_levels.sql new file mode 100644 index 00000000..befb7ed8 --- /dev/null +++ b/services/activity-service/deploy/mysql/migrations/009_temporary_growth_levels.sql @@ -0,0 +1,46 @@ +-- 临时财富/魅力等级只建立 overlay 和副作用事实,不修改 user_growth_level_accounts 的真实经验。 +ALTER TABLE growth_level_reward_jobs + DROP INDEX uk_growth_level_reward_once, + ADD COLUMN reward_origin VARCHAR(32) NOT NULL DEFAULT 'organic' COMMENT 'organic/temporary' AFTER wallet_grant_id, + ADD COLUMN temporary_level_id VARCHAR(96) NOT NULL DEFAULT '' AFTER reward_origin, + ADD COLUMN grant_generation INT NOT NULL DEFAULT 0 AFTER temporary_level_id, + ADD COLUMN reward_level INT NOT NULL DEFAULT 0 AFTER grant_generation, + ADD COLUMN is_permanent TINYINT(1) NOT NULL DEFAULT 1 AFTER reward_level, + ADD COLUMN temporary_expires_at_ms BIGINT NOT NULL DEFAULT 0 AFTER is_permanent, + ADD COLUMN revoke_status VARCHAR(32) NOT NULL DEFAULT 'none' AFTER temporary_expires_at_ms, + ADD UNIQUE KEY uk_growth_level_reward_generation + (app_code, user_id, track, reward_source_type, reward_source_id, reward_origin, grant_generation); + +CREATE TABLE IF NOT EXISTS growth_level_reward_wallet_grants ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu', reward_job_id VARCHAR(96) NOT NULL, + grant_kind VARCHAR(32) NOT NULL, wallet_grant_id VARCHAR(96) NOT NULL, + revoke_status VARCHAR(32) NOT NULL DEFAULT 'none', revoke_attempt_count INT NOT NULL DEFAULT 0, + revoke_next_retry_at_ms BIGINT NOT NULL DEFAULT 0, revoke_locked_by VARCHAR(128) NOT NULL DEFAULT '', + revoke_locked_until_ms BIGINT NOT NULL DEFAULT 0, revoke_failure_reason VARCHAR(255) NOT NULL DEFAULT '', + revoked_at_ms BIGINT NOT NULL DEFAULT 0, created_at_ms BIGINT NOT NULL, updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, reward_job_id, wallet_grant_id), + KEY idx_growth_reward_grant_revoke (app_code, revoke_status, revoke_next_retry_at_ms, created_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS growth_temporary_level_commands ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu', command_id VARCHAR(128) NOT NULL, + request_hash VARCHAR(64) NOT NULL, user_id BIGINT NOT NULL, operator_admin_id BIGINT NOT NULL, + created_at_ms BIGINT NOT NULL, PRIMARY KEY (app_code, command_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS growth_temporary_levels ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu', temporary_level_id VARCHAR(96) NOT NULL, + command_id VARCHAR(128) NOT NULL, user_id BIGINT NOT NULL, track VARCHAR(32) NOT NULL, + target_level INT NOT NULL, target_required_value BIGINT NOT NULL, baseline_total_value BIGINT NOT NULL, + started_at_ms BIGINT NOT NULL, expires_at_ms BIGINT NOT NULL, status VARCHAR(32) NOT NULL, + generation INT NOT NULL, operator_admin_id BIGINT NOT NULL, reason VARCHAR(255) NOT NULL DEFAULT '', + final_level INT NOT NULL DEFAULT 0, activation_notice_status VARCHAR(32) NOT NULL DEFAULT 'pending', + expiry_notice_status VARCHAR(32) NOT NULL DEFAULT 'skipped', attempt_count INT NOT NULL DEFAULT 0, + next_retry_at_ms BIGINT NOT NULL DEFAULT 0, locked_by VARCHAR(128) NOT NULL DEFAULT '', + locked_until_ms BIGINT NOT NULL DEFAULT 0, failure_reason VARCHAR(255) NOT NULL DEFAULT '', + created_at_ms BIGINT NOT NULL, updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, temporary_level_id), + UNIQUE KEY uk_growth_temp_command_track (app_code, command_id, track), + KEY idx_growth_temp_user_track (app_code, user_id, track, status, expires_at_ms), + KEY idx_growth_temp_work (app_code, status, next_retry_at_ms, expires_at_ms, created_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/services/activity-service/internal/app/app.go b/services/activity-service/internal/app/app.go index b3e862b9..f8ef22dc 100644 --- a/services/activity-service/internal/app/app.go +++ b/services/activity-service/internal/app/app.go @@ -21,6 +21,7 @@ import ( firstrechargeservice "hyapp/services/activity-service/internal/service/firstrecharge" inviteactivityservice "hyapp/services/activity-service/internal/service/inviteactivity" messageservice "hyapp/services/activity-service/internal/service/message" + wheelservice "hyapp/services/activity-service/internal/service/wheel" mysqlstorage "hyapp/services/activity-service/internal/storage/mysql" ) @@ -37,10 +38,13 @@ type App struct { cumulativeRecharge *cumulativerechargeservice.Service inviteActivityReward *inviteactivityservice.Service actionConfirm *messageservice.ActionConfirmService + wheel *wheelservice.Service broadcastWorkerEnabled bool messageActionWorkerEnabled bool + wheelRewardWorkerEnabled bool workerNodeID string messageActionWorkerOptions config.MessageActionConfirmWorkerConfig + wheelRewardWorkerOptions config.WheelRewardWorkerConfig messageActionOutboxTopic string userLeaderboardRedisClose func() error mqConsumers []*rocketmqx.Consumer @@ -133,10 +137,13 @@ func New(cfg config.Config) (*App, error) { cumulativeRecharge: services.cumulativeRecharge, inviteActivityReward: services.inviteActivityReward, actionConfirm: services.actionConfirm, + wheel: services.wheel, broadcastWorkerEnabled: cfg.Broadcast.Enabled, messageActionWorkerEnabled: cfg.MessageActionConfirmWorker.Enabled, + wheelRewardWorkerEnabled: cfg.WheelRewardWorker.Enabled, workerNodeID: cfg.NodeID, messageActionWorkerOptions: cfg.MessageActionConfirmWorker, + wheelRewardWorkerOptions: cfg.WheelRewardWorker, messageActionOutboxTopic: cfg.RocketMQ.MessageActionOutbox.Topic, userLeaderboardRedisClose: userLeaderboardRedisClose, mqConsumers: mqConsumers, @@ -166,6 +173,18 @@ func (a *App) Run() error { a.runMessageActionOutboxWorker(ctx) }) } + if a.wheelRewardWorkerEnabled && a.wheel != nil && a.workers != nil { + a.workers.Go(func(ctx context.Context) { + a.wheel.RunRewardWorker(ctx, wheelservice.RewardWorkerOptions{ + WorkerID: a.workerNodeID + ":wheel-reward", + AppCodes: a.wheelRewardWorkerOptions.AppCodes, + PollInterval: a.wheelRewardWorkerOptions.PollInterval, + BatchSize: a.wheelRewardWorkerOptions.BatchSize, + LockTTL: a.wheelRewardWorkerOptions.LockTTL, + MaxRetry: a.wheelRewardWorkerOptions.MaxRetry, + }) + }) + } err := a.server.Serve(a.listener) if a.workers != nil { a.workers.StopAndWait() diff --git a/services/activity-service/internal/app/mq.go b/services/activity-service/internal/app/mq.go index 044f36ac..9244712e 100644 --- a/services/activity-service/internal/app/mq.go +++ b/services/activity-service/internal/app/mq.go @@ -4,9 +4,11 @@ import ( "context" "encoding/json" "errors" + "fmt" "time" "hyapp/pkg/appcode" + "hyapp/pkg/luckygiftmq" "hyapp/pkg/rocketmqx" "hyapp/pkg/roommq" servicemq "hyapp/pkg/servicekit/mq" @@ -26,6 +28,14 @@ func buildMQConsumers(cfg config.Config, services *serviceBundle) ([]*rocketmqx. } mqConsumers = append(mqConsumers, consumer) } + if cfg.RocketMQ.LuckyGiftOutbox.Enabled { + consumer, err := newLuckyGiftOutboxConsumer(cfg, services) + if err != nil { + shutdownConsumers(mqConsumers) + return nil, err + } + mqConsumers = append(mqConsumers, consumer) + } if cfg.RocketMQ.UserOutbox.Enabled && !services.broadcastPublisherAvailable { if !cfg.TaskEventWorker.Enabled { shutdownConsumers(mqConsumers) @@ -96,6 +106,14 @@ func buildMQConsumers(cfg config.Config, services *serviceBundle) ([]*rocketmqx. } mqConsumers = append(mqConsumers, consumer) } + if cfg.RocketMQ.WalletOutbox.Enabled { + consumer, err := newVIPRebateNoticeWalletConsumer(cfg, services) + if err != nil { + shutdownConsumers(mqConsumers) + return nil, err + } + mqConsumers = append(mqConsumers, consumer) + } if cfg.UserLeaderboardWorker.Enabled && cfg.RocketMQ.WalletOutbox.Enabled { consumer, err := newUserLeaderboardWalletConsumer(cfg, services) if err != nil { @@ -120,7 +138,12 @@ func newRoomOutboxConsumer(cfg config.Config, services *serviceBundle) (*rocketm if err != nil { return nil, err } - if err := consumer.Subscribe(cfg.RocketMQ.RoomOutbox.Topic, roommq.TagRoomOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + roomTagExpression, err := roommq.LegacyCompatibleTagExpression(activityRoomEventTypes()...) + if err != nil { + _ = consumer.Shutdown() + return nil, err + } + if err := consumer.Subscribe(cfg.RocketMQ.RoomOutbox.Topic, roomTagExpression, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { envelope, _, err := roommq.DecodeRoomOutboxMessage(message.Body) if err != nil { return err @@ -129,22 +152,60 @@ func newRoomOutboxConsumer(cfg config.Config, services *serviceBundle) (*rocketm // 任何模块失败都返回错误给 MQ,让同一个 envelope 重试,避免“播报成功但活动漏计分”的半完成状态。 eventCtx := appcode.WithContext(ctx, envelope.GetAppCode()) if _, err := services.broadcast.HandleRoomEvent(eventCtx, envelope); err != nil { - return err + return fmt.Errorf("room outbox broadcast projection: %w", err) } if _, err := services.growth.HandleRoomEvent(eventCtx, envelope); err != nil { - return err + return fmt.Errorf("room outbox growth projection: %w", err) } if _, err := services.weeklyStar.HandleRoomEvent(eventCtx, envelope); err != nil { - return err + return fmt.Errorf("room outbox weekly star projection: %w", err) } if _, err := services.agencyOpening.HandleRoomEvent(eventCtx, envelope); err != nil { - return err + return fmt.Errorf("room outbox agency opening projection: %w", err) } if _, err := services.task.HandleRoomEvent(eventCtx, envelope); err != nil { + return fmt.Errorf("room outbox task projection: %w", err) + } + if _, err := services.roomTurnoverReward.HandleRoomEvent(eventCtx, envelope); err != nil { + return fmt.Errorf("room outbox turnover reward projection: %w", err) + } + return nil + }); err != nil { + _ = consumer.Shutdown() + return nil, err + } + return consumer, nil +} + +func activityRoomEventTypes() []string { + // 六个投影共用同一 consumer group,因此 selector 必须取其事件集合并集。 + // RoomGiftSent 驱动增长/任务/周星/流水,另外三个事件只驱动播报;其他房间 + // 事实由 Broker 过滤,避免一个送礼高峰放大成所有活动 handler 的无效调用。 + return []string{ + roommq.EventTypeRoomGiftSent, + roommq.EventTypeRoomPasswordChanged, + roommq.EventTypeRoomRocketIgnited, + roommq.EventTypeRoomRocketRewardGranted, + } +} + +func newLuckyGiftOutboxConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) { + consumer, err := rocketmqx.NewConsumer(luckyGiftOutboxConsumerConfig(cfg.RocketMQ)) + if err != nil { + return nil, err + } + // 新 owner topic 从首版就使用 exact typed Tag;不订阅宽泛 legacy Tag,避免后续审计事实进入区域 IM 投影。 + if err := consumer.Subscribe(cfg.RocketMQ.LuckyGiftOutbox.Topic, luckygiftmq.TagLuckyGiftDrawn, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + envelope, _, err := luckygiftmq.DecodeLuckyGiftOutboxMessage(message.Body) + if err != nil { return err } - _, err = services.roomTurnoverReward.HandleRoomEvent(eventCtx, envelope) - return err + eventCtx := appcode.WithContext(ctx, envelope.GetAppCode()) + if _, err := services.broadcast.HandleLuckyGiftEvent(eventCtx, envelope); err != nil { + // 返回错误给 MQ 保留至少一次语义;im_broadcast_outbox 的 event_id 主键负责重投幂等。 + return fmt.Errorf("lucky gift outbox broadcast projection: %w", err) + } + return nil }); err != nil { _ = consumer.Shutdown() return nil, err @@ -175,7 +236,7 @@ func newFirstRechargeWalletConsumer(cfg config.Config, services *serviceBundle) if err != nil { return nil, err } - if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + if err := subscribeWalletOutboxEvents(consumer, cfg.RocketMQ.WalletOutbox.Topic, []string{walletmq.EventTypeWalletRechargeRecorded}, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { event, ok, err := firstRechargeEventFromWalletMessage(message.Body) if err != nil || !ok { return err @@ -194,7 +255,7 @@ func newCumulativeRechargeWalletConsumer(cfg config.Config, services *serviceBun if err != nil { return nil, err } - if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + if err := subscribeWalletOutboxEvents(consumer, cfg.RocketMQ.WalletOutbox.Topic, []string{walletmq.EventTypeWalletRechargeRecorded}, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { event, ok, err := cumulativeRechargeEventFromWalletMessage(message.Body) if err != nil || !ok { return err @@ -213,7 +274,7 @@ func newInviteActivityWalletConsumer(cfg config.Config, services *serviceBundle) if err != nil { return nil, err } - if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + if err := subscribeWalletOutboxEvents(consumer, cfg.RocketMQ.WalletOutbox.Topic, []string{walletmq.EventTypeWalletRechargeRecorded}, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { event, ok, err := inviteActivityRechargeEventFromWalletMessage(message.Body) if err != nil || !ok { return err @@ -257,7 +318,11 @@ func newRedPacketWalletConsumer(cfg config.Config, services *serviceBundle) (*ro if err != nil { return nil, err } - if err := consumer.Subscribe(redPacketWalletOutboxTopic(cfg.RocketMQ.WalletOutbox), walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + if err := subscribeWalletOutboxEvents(consumer, redPacketWalletOutboxTopic(cfg.RocketMQ.WalletOutbox), []string{ + walletmq.EventTypeWalletRedPacketCreated, + walletmq.EventTypeWalletRedPacketClaimed, + walletmq.EventTypeWalletRedPacketRefunded, + }, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { event, ok, err := redPacketEventFromWalletMessage(message.Body) if err != nil || !ok { return err @@ -270,6 +335,26 @@ func newRedPacketWalletConsumer(cfg config.Config, services *serviceBundle) (*ro return consumer, nil } +func newVIPRebateNoticeWalletConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) { + // VIP 返现资格是 wallet 的逐用户事实,必须使用独立 consumer group 投影为系统通知; + // 不能复用首充/红包位点,也不能用全服 fanout,因为每个用户的 rebate_id、金额和到期时间都不同。 + consumer, err := rocketmqx.NewConsumer(walletOutboxVIPRebateNoticeConsumerConfig(cfg.RocketMQ)) + if err != nil { + return nil, err + } + if err := subscribeWalletOutboxEvents(consumer, cfg.RocketMQ.WalletOutbox.Topic, []string{walletmq.EventTypeVIPDailyCoinRebateAvailable}, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + event, ok, err := vipRebateAvailableEventFromWalletMessage(message.Body) + if err != nil || !ok { + return err + } + return createVIPRebateSystemNotice(appcode.WithContext(ctx, event.AppCode), services.message, event) + }); err != nil { + _ = consumer.Shutdown() + return nil, err + } + return consumer, nil +} + func newUserLeaderboardWalletConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) { if services == nil || services.userLeaderboard == nil { return nil, errors.New("user leaderboard worker requires redis store") @@ -279,7 +364,7 @@ func newUserLeaderboardWalletConsumer(cfg config.Config, services *serviceBundle if err != nil { return nil, err } - if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + if err := subscribeWalletOutboxEvents(consumer, cfg.RocketMQ.WalletOutbox.Topic, []string{walletmq.EventTypeWalletGiftDebited}, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { event, ok, err := userLeaderboardGiftEventFromWalletMessage(message.Body) if err != nil || !ok { return err @@ -299,7 +384,7 @@ func newTaskWalletConsumer(cfg config.Config, services *serviceBundle) (*rocketm if err != nil { return nil, err } - if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + if err := subscribeWalletOutboxEvents(consumer, cfg.RocketMQ.WalletOutbox.Topic, []string{walletmq.EventTypeWalletRechargeRecorded}, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { // 钱包任务消费者使用独立 consumer group,不能复用红包或其他钱包消费者进度;否则充值任务会被其他业务确认掉。 event, appCode, ok, err := taskEventFromWalletMessage(message.Body) if err != nil || !ok { @@ -315,6 +400,18 @@ func newTaskWalletConsumer(cfg config.Config, services *serviceBundle) (*rocketm return consumer, nil } +// subscribeWalletOutboxEvents is the only activity-service entry point for wallet +// subscriptions during the typed-tag compatibility phase. Keeping selector assembly +// centralized guarantees every replica in a consumer group registers the same sorted +// expression and that the legacy Tag remains present until the old backlog is drained. +func subscribeWalletOutboxEvents(consumer *rocketmqx.Consumer, topic string, eventTypes []string, handler rocketmqx.Handler) error { + tagExpression, err := walletmq.LegacyCompatibleTagExpression(eventTypes...) + if err != nil { + return err + } + return consumer.Subscribe(topic, tagExpression, handler) +} + func newTaskUserOutboxConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) { consumer, err := rocketmqx.NewConsumer(userOutboxTaskConsumerConfig(cfg.RocketMQ)) if err != nil { @@ -342,7 +439,18 @@ func newTaskUserOutboxConsumer(cfg config.Config, services *serviceBundle) (*roc } func roomOutboxConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig { - return rocketMQConsumerConfig(cfg, cfg.RoomOutbox.ConsumerGroup, cfg.RoomOutbox.ConsumerMaxReconsumeTimes) + consumerConfig := rocketMQConsumerConfig(cfg, cfg.RoomOutbox.ConsumerGroup, cfg.RoomOutbox.ConsumerMaxReconsumeTimes) + // 一条房间事实会串行投影到多组 MySQL 幂等表和聚合表。SDK 默认开 20 个消费协程, + // 积压恢复时同一用户的多条送礼事实会并发争抢任务进度和活动聚合锁,把可恢复积压放大成死锁重试风暴。 + // 单实例固定单协程;生产的同 consumer group 仍由多实例分担 queue,不改变消费位点和重试语义。 + consumerConfig.ConsumeGoroutines = 1 + return consumerConfig +} + +func luckyGiftOutboxConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig { + // 顶部飘屏是实时副作用,新建或误改 group 时不能从 topic 最早位点重放历史大奖; + // 首次上线必须先部署 consumer,再开启 lucky-gift producer,确保 latest offset 不丢上线窗口事实。 + return rocketMQConsumerConfig(cfg, cfg.LuckyGiftOutbox.ConsumerGroup, cfg.LuckyGiftOutbox.ConsumerMaxReconsumeTimes) } func userOutboxConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig { @@ -372,6 +480,12 @@ func walletOutboxUserLeaderboardConsumerConfig(cfg config.RocketMQConfig) rocket return consumerConfig } +func walletOutboxVIPRebateNoticeConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig { + // VIP 返币通知会写用户 inbox,属于不可批量重放的业务副作用。新消费组必须沿用 SDK 的 + // latest-offset 默认值;上线窗口内需要补偿的少量资格由明确的位点重置处理,不能扫描全部历史钱包事实。 + return walletOutboxConsumerConfig(cfg, cfg.WalletOutbox.VIPRebateNoticeConsumerGroup) +} + func messageActionProducerConfig(cfg config.RocketMQConfig) rocketmqx.ProducerConfig { return rocketmqx.ProducerConfig{ EndpointConfig: rocketmqx.EndpointConfig{ diff --git a/services/activity-service/internal/app/mq_test.go b/services/activity-service/internal/app/mq_test.go new file mode 100644 index 00000000..3aed24a7 --- /dev/null +++ b/services/activity-service/internal/app/mq_test.go @@ -0,0 +1,72 @@ +package app + +import ( + "strings" + "testing" + + "hyapp/pkg/roommq" + "hyapp/services/activity-service/internal/config" +) + +func TestRoomOutboxConsumerConfigSerializesCompositeProjection(t *testing.T) { + cfg := config.Default().RocketMQ + cfg.RoomOutbox.ConsumerGroup = "activity-room-outbox-test" + + consumerConfig := roomOutboxConsumerConfig(cfg) + if consumerConfig.ConsumeGoroutines != 1 { + t.Fatalf("room outbox consume goroutines = %d, want 1", consumerConfig.ConsumeGoroutines) + } + if consumerConfig.GroupName != cfg.RoomOutbox.ConsumerGroup { + t.Fatalf("room outbox group = %q, want %q", consumerConfig.GroupName, cfg.RoomOutbox.ConsumerGroup) + } +} + +func TestLuckyGiftOutboxConsumerUsesIndependentRealtimeGroup(t *testing.T) { + cfg := config.Default().RocketMQ + cfg.LuckyGiftOutbox.ConsumerGroup = "activity-lucky-gift-test" + + consumerConfig := luckyGiftOutboxConsumerConfig(cfg) + if consumerConfig.GroupName != cfg.LuckyGiftOutbox.ConsumerGroup { + t.Fatalf("lucky gift outbox group = %q, want %q", consumerConfig.GroupName, cfg.LuckyGiftOutbox.ConsumerGroup) + } + if consumerConfig.GroupName == cfg.RoomOutbox.ConsumerGroup || consumerConfig.GroupName == cfg.WalletOutbox.RedPacketBroadcastConsumerGroup { + t.Fatalf("lucky gift outbox must not share another projection offset: %+v", consumerConfig) + } + if consumerConfig.ConsumeFromFirst { + t.Fatal("lucky gift top-banner consumer must not replay historical wins for a new group") + } +} + +func TestActivityRoomSelectorKeepsLegacyAndOnlyRelevantTypedEvents(t *testing.T) { + expression, err := roommq.LegacyCompatibleTagExpression(activityRoomEventTypes()...) + if err != nil { + t.Fatalf("LegacyCompatibleTagExpression failed: %v", err) + } + for _, required := range []string{ + roommq.TagRoomOutboxEvent, + roommq.EventTypeRoomGiftSent, + roommq.EventTypeRoomPasswordChanged, + roommq.EventTypeRoomRocketIgnited, + roommq.EventTypeRoomRocketRewardGranted, + } { + if !strings.Contains(expression, required) { + t.Fatalf("activity room selector %q misses %q", expression, required) + } + } + if strings.Contains(expression, roommq.EventTypeRoomMicChanged) { + t.Fatalf("activity room selector must not receive mic events: %q", expression) + } +} + +func TestVIPRebateNoticeConsumerDoesNotReplayHistoricalWalletEvents(t *testing.T) { + cfg := config.Default().RocketMQ + cfg.WalletOutbox.VIPRebateNoticeConsumerGroup = "activity-vip-rebate-notice-test" + + consumerConfig := walletOutboxVIPRebateNoticeConsumerConfig(cfg) + if consumerConfig.ConsumeFromFirst { + t.Fatal("VIP rebate notice consumer must start at the latest offset for a new group") + } + if consumerConfig.GroupName != cfg.WalletOutbox.VIPRebateNoticeConsumerGroup { + t.Fatalf("VIP rebate notice group = %q, want %q", consumerConfig.GroupName, cfg.WalletOutbox.VIPRebateNoticeConsumerGroup) + } +} diff --git a/services/activity-service/internal/app/services.go b/services/activity-service/internal/app/services.go index 61513ba7..b9174e93 100644 --- a/services/activity-service/internal/app/services.go +++ b/services/activity-service/internal/app/services.go @@ -60,7 +60,7 @@ func buildServiceBundle(cfg config.Config, repository *mysqlstorage.Repository, taskSvc := taskservice.New(repository, walletClient) registrationRewardSvc := registrationrewardservice.New(repository, walletClient) sevenDayCheckInSvc := sevendaycheckinservice.New(repository, walletClient) - growthSvc := growthservice.New(repository, walletClient) + growthSvc := growthservice.New(repository, walletClient, growthservice.WithNoticeService(messageSvc)) achievementSvc := achievementservice.New(repository, walletClient) agencyOpeningSvc := agencyopeningservice.New(repository, walletClient, activityclient.NewGRPCAgencyOpeningSource(clients.userConn), roomClient) weeklyStarSvc := weeklystarservice.New(repository, walletClient) @@ -83,6 +83,7 @@ func buildServiceBundle(cfg config.Config, repository *mysqlstorage.Repository, broadcastSvc := broadcastservice.New(broadcastservice.Config{ NodeID: cfg.NodeID, + AppCodes: cfg.Broadcast.AppCodes, SuperGiftMinValue: cfg.Broadcast.SuperGiftMinValue, WorkerBatchSize: cfg.Broadcast.WorkerBatchSize, WorkerLockTTL: cfg.Broadcast.WorkerLockTTL, diff --git a/services/activity-service/internal/app/task_events.go b/services/activity-service/internal/app/task_events.go index bbf142e3..300020c5 100644 --- a/services/activity-service/internal/app/task_events.go +++ b/services/activity-service/internal/app/task_events.go @@ -13,7 +13,7 @@ import ( ) const ( - taskWalletRechargeRecorded = "WalletRechargeRecorded" + taskWalletRechargeRecorded = walletmq.EventTypeWalletRechargeRecorded taskCPRelationshipCreated = "UserCPRelationshipCreated" taskMicDailyStatsUpdated = "UserMicDailyStatsUpdated" taskMoodPublished = "UserMoodPublished" diff --git a/services/activity-service/internal/app/vip_rebate_notice.go b/services/activity-service/internal/app/vip_rebate_notice.go new file mode 100644 index 00000000..a478c2e6 --- /dev/null +++ b/services/activity-service/internal/app/vip_rebate_notice.go @@ -0,0 +1,133 @@ +package app + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/walletmq" + "hyapp/pkg/xerr" + messageservice "hyapp/services/activity-service/internal/service/message" +) + +const vipDailyCoinRebateAvailableEventType = walletmq.EventTypeVIPDailyCoinRebateAvailable + +// vipRebateAvailableEvent 是 wallet outbox 的逐用户返现资格快照。 +// activity-service 只把这个已提交事实投影为 inbox;资格、金额、过期和最终入账仍由 wallet owner 判定。 +type vipRebateAvailableEvent struct { + AppCode string + EventID string + RebateID string + UserID int64 + TaskDay string + VIPLevel int32 + VIPName string + CoinAmount int64 + AvailableAtMS int64 + ExpiresAtMS int64 + ConfigVersion int64 + CreatedAtMS int64 +} + +func vipRebateAvailableEventFromWalletMessage(body []byte) (vipRebateAvailableEvent, bool, error) { + message, err := walletmq.DecodeWalletOutboxMessage(body) + if err != nil { + return vipRebateAvailableEvent{}, false, err + } + if message.EventType != vipDailyCoinRebateAvailableEventType { + return vipRebateAvailableEvent{}, false, nil + } + var payload struct { + RebateID string `json:"rebate_id"` + UserID int64 `json:"user_id"` + TaskDay string `json:"task_day"` + VIPLevel int32 `json:"vip_level"` + VIPName string `json:"vip_name"` + CoinAmount int64 `json:"coin_amount"` + AvailableAtMS int64 `json:"available_at_ms"` + ExpiresAtMS int64 `json:"expires_at_ms"` + ConfigVersion int64 `json:"config_version"` + CreatedAtMS int64 `json:"created_at_ms"` + } + if err := json.Unmarshal([]byte(message.PayloadJSON), &payload); err != nil { + // 目标事件 payload 损坏必须让 MQ 重投并进入告警,不能确认后永久丢失用户领取入口。 + return vipRebateAvailableEvent{}, false, err + } + payload.RebateID = strings.TrimSpace(payload.RebateID) + payload.TaskDay = strings.TrimSpace(payload.TaskDay) + payload.VIPName = strings.TrimSpace(payload.VIPName) + if payload.CreatedAtMS <= 0 { + payload.CreatedAtMS = message.OccurredAtMS + } + if payload.RebateID == "" || payload.UserID <= 0 || payload.UserID != message.UserID || !validUTCTaskDay(payload.TaskDay) || payload.VIPLevel <= 0 || payload.CoinAmount <= 0 || payload.AvailableAtMS <= 0 || payload.ExpiresAtMS <= payload.AvailableAtMS || payload.ConfigVersion <= 0 { + return vipRebateAvailableEvent{}, false, xerr.New(xerr.InvalidArgument, "VIP rebate wallet event is invalid") + } + return vipRebateAvailableEvent{ + AppCode: appcode.Normalize(message.AppCode), + EventID: message.EventID, + RebateID: payload.RebateID, + UserID: payload.UserID, + TaskDay: payload.TaskDay, + VIPLevel: payload.VIPLevel, + VIPName: payload.VIPName, + CoinAmount: payload.CoinAmount, + AvailableAtMS: payload.AvailableAtMS, + ExpiresAtMS: payload.ExpiresAtMS, + ConfigVersion: payload.ConfigVersion, + CreatedAtMS: payload.CreatedAtMS, + }, true, nil +} + +func createVIPRebateSystemNotice(ctx context.Context, service *messageservice.Service, event vipRebateAvailableEvent) error { + if service == nil { + return xerr.New(xerr.Unavailable, "message service is not configured") + } + actionParam, err := json.Marshal(map[string]any{ + "rebate_id": event.RebateID, + "task_day": event.TaskDay, + "coin_amount": event.CoinAmount, + "expires_at_ms": event.ExpiresAtMS, + }) + if err != nil { + return err + } + summary := fmt.Sprintf("尊敬的VIP用户,平台今日返现%d金币,请及时领取。", event.CoinAmount) + _, err = service.CreateSystemNotice(ctx, messageservice.NoticeCommand{ + TargetUserID: event.UserID, + Producer: "wallet-service", + ProducerEventID: event.EventID, + ProducerEventType: vipDailyCoinRebateAvailableEventType, + AggregateType: "vip_daily_coin_rebate", + AggregateID: event.RebateID, + TemplateID: "vip_daily_coin_rebate_available", + TemplateVersion: "v1", + Title: "VIP金币返现", + Summary: summary, + Body: summary, + ActionType: "vip_coin_rebate_claim", + ActionParam: string(actionParam), + Priority: 50, + SentAtMS: event.AvailableAtMS, + ExpireAtMS: event.ExpiresAtMS, + Metadata: map[string]any{ + "rebate_id": event.RebateID, + "task_day": event.TaskDay, + "vip_level": event.VIPLevel, + "vip_name": event.VIPName, + "coin_amount": event.CoinAmount, + "available_at_ms": event.AvailableAtMS, + "expires_at_ms": event.ExpiresAtMS, + "config_version": event.ConfigVersion, + "claim_status": "claimable", + }, + }) + return err +} + +func validUTCTaskDay(value string) bool { + parsed, err := time.Parse("2006-01-02", value) + return err == nil && parsed.Location() == time.UTC && parsed.Format("2006-01-02") == value +} diff --git a/services/activity-service/internal/app/vip_rebate_notice_test.go b/services/activity-service/internal/app/vip_rebate_notice_test.go new file mode 100644 index 00000000..d2f2fad9 --- /dev/null +++ b/services/activity-service/internal/app/vip_rebate_notice_test.go @@ -0,0 +1,90 @@ +package app + +import ( + "context" + "testing" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/walletmq" + messageservice "hyapp/services/activity-service/internal/service/message" + "hyapp/services/activity-service/internal/testutil/mysqltest" +) + +func TestVIPRebateAvailableEventFromWalletMessage(t *testing.T) { + body := vipRebateWalletMessage(t, "VipDailyCoinRebateAvailable", 42, `{ + "rebate_id":"viprebate_fami_2027-01-15_42", + "user_id":42, + "task_day":"2027-01-15", + "vip_level":9, + "vip_name":"VIP9", + "coin_amount":100000, + "available_at_ms":1800000000000, + "expires_at_ms":1800086400000, + "config_version":7, + "created_at_ms":1800000000100 + }`) + event, ok, err := vipRebateAvailableEventFromWalletMessage(body) + if err != nil || !ok { + t.Fatalf("parse VIP rebate event failed: ok=%v event=%+v err=%v", ok, event, err) + } + if event.AppCode != "fami" || event.EventID != "wev-vip-rebate" || event.RebateID != "viprebate_fami_2027-01-15_42" || event.UserID != 42 || event.TaskDay != "2027-01-15" || event.VIPLevel != 9 || event.CoinAmount != 100000 || event.ExpiresAtMS != 1800086400000 || event.ConfigVersion != 7 { + t.Fatalf("VIP rebate event mismatch: %+v", event) + } +} + +func TestVIPRebateWalletEventSkipsOtherFactsAndRejectsCorruptTarget(t *testing.T) { + other := vipRebateWalletMessage(t, "WalletRechargeRecorded", 42, `{}`) + if event, ok, err := vipRebateAvailableEventFromWalletMessage(other); err != nil || ok || event.EventID != "" { + t.Fatalf("non-target event should be acknowledged: ok=%v event=%+v err=%v", ok, event, err) + } + corrupt := vipRebateWalletMessage(t, "VipDailyCoinRebateAvailable", 42, `{"rebate_id":"r1","user_id":99}`) + if _, ok, err := vipRebateAvailableEventFromWalletMessage(corrupt); err == nil || ok { + t.Fatalf("corrupt target event must fail for MQ retry: ok=%v err=%v", ok, err) + } +} + +func TestVIPRebateWalletEventCreatesOneIdempotentSystemNotice(t *testing.T) { + ctx := appcode.WithContext(context.Background(), "fami") + repository := mysqltest.NewRepository(t) + service := messageservice.New(messageservice.Config{NodeID: "activity-test"}, repository) + service.SetClock(func() time.Time { return time.UnixMilli(1800000000200) }) + event := vipRebateAvailableEvent{ + AppCode: "fami", EventID: "wev-vip-rebate", RebateID: "viprebate_fami_2027-01-15_42", + UserID: 42, TaskDay: "2027-01-15", VIPLevel: 9, VIPName: "VIP9", CoinAmount: 100000, + AvailableAtMS: 1800000000000, ExpiresAtMS: 1800086400000, ConfigVersion: 7, CreatedAtMS: 1800000000100, + } + if err := createVIPRebateSystemNotice(ctx, service, event); err != nil { + t.Fatalf("create system notice failed: %v", err) + } + if err := createVIPRebateSystemNotice(ctx, service, event); err != nil { + t.Fatalf("duplicate system notice should be idempotent: %v", err) + } + items, _, err := service.ListInboxMessages(ctx, 42, "system", 20, "") + if err != nil || len(items) != 1 { + t.Fatalf("system inbox mismatch: items=%+v err=%v", items, err) + } + item := items[0] + if item.Title != "VIP金币返现" || item.ActionType != "vip_coin_rebate_claim" || item.AggregateID != event.RebateID || item.ExpireAtMS != event.ExpiresAtMS || item.ActionParam == "" { + t.Fatalf("VIP rebate notice mismatch: %+v", item) + } +} + +func vipRebateWalletMessage(t *testing.T, eventType string, userID int64, payload string) []byte { + t.Helper() + body, err := walletmq.EncodeWalletOutboxMessage(walletmq.WalletOutboxMessage{ + AppCode: "fami", + EventID: "wev-vip-rebate", + EventType: eventType, + TransactionID: "viprebate_fami_2027-01-15_42", + CommandID: "vip-rebate-generate:fami:2027-01-15:42", + UserID: userID, + AssetType: "COIN", + PayloadJSON: payload, + OccurredAtMS: 1800000000100, + }) + if err != nil { + t.Fatalf("encode wallet outbox message: %v", err) + } + return body +} diff --git a/services/activity-service/internal/app/wallet_events.go b/services/activity-service/internal/app/wallet_events.go index 7781b3a4..1a9009b2 100644 --- a/services/activity-service/internal/app/wallet_events.go +++ b/services/activity-service/internal/app/wallet_events.go @@ -18,7 +18,7 @@ func firstRechargeEventFromWalletMessage(body []byte) (firstrechargedomain.Recha if err != nil { return firstrechargedomain.RechargeEvent{}, false, err } - if message.EventType != firstrechargedomain.RechargeEventWalletRecorded { + if message.EventType != walletmq.EventTypeWalletRechargeRecorded { return firstrechargedomain.RechargeEvent{}, false, nil } fields := firstRechargeFactFromWalletPayload(message.PayloadJSON) @@ -49,7 +49,7 @@ func cumulativeRechargeEventFromWalletMessage(body []byte) (cumulativerechargedo if err != nil { return cumulativerechargedomain.RechargeEvent{}, false, err } - if message.EventType != cumulativerechargedomain.RechargeEventWalletRecorded { + if message.EventType != walletmq.EventTypeWalletRechargeRecorded { return cumulativerechargedomain.RechargeEvent{}, false, nil } // WalletRechargeRecorded 是累充唯一事实源;payload 里没有可折算 USD 金额时直接跳过,避免把普通钱包流水计入活动。 @@ -78,7 +78,7 @@ func inviteActivityRechargeEventFromWalletMessage(body []byte) (inviteactivitydo if err != nil { return inviteactivitydomain.RechargeEvent{}, false, err } - if message.EventType != inviteactivitydomain.EventTypeWalletRechargeRecorded || message.AssetType != "COIN" || message.AvailableDelta <= 0 { + if message.EventType != walletmq.EventTypeWalletRechargeRecorded || message.AssetType != "COIN" || message.AvailableDelta <= 0 { return inviteactivitydomain.RechargeEvent{}, false, nil } fields := cumulativeRechargeFactFromWalletPayload(message.PayloadJSON, message.AvailableDelta) @@ -101,7 +101,9 @@ func redPacketEventFromWalletMessage(body []byte) (broadcastdomain.RedPacketWall if err != nil { return broadcastdomain.RedPacketWalletEvent{}, false, err } - if message.EventType != "WalletRedPacketCreated" && message.EventType != "WalletRedPacketClaimed" && message.EventType != "WalletRedPacketRefunded" { + if message.EventType != walletmq.EventTypeWalletRedPacketCreated && + message.EventType != walletmq.EventTypeWalletRedPacketClaimed && + message.EventType != walletmq.EventTypeWalletRedPacketRefunded { return broadcastdomain.RedPacketWalletEvent{}, false, nil } fields := redPacketFieldsFromWalletPayload(message.PayloadJSON, message.EventType) @@ -138,7 +140,7 @@ func userLeaderboardGiftEventFromWalletMessage(body []byte) (userleaderboard.Gif if err != nil { return userleaderboard.GiftEvent{}, false, err } - if message.EventType != "WalletGiftDebited" { + if message.EventType != walletmq.EventTypeWalletGiftDebited { return userleaderboard.GiftEvent{}, false, nil } var decoded map[string]any @@ -310,12 +312,12 @@ func redPacketFieldsFromWalletPayload(payload string, eventType string) redPacke status := stringFromDecoded(decoded, "status") openAtMS := int64FromDecoded(decoded, "open_at_ms") expiresAtMS := int64FromDecoded(decoded, "expires_at_ms") - if eventType == "WalletRedPacketClaimed" || eventType == "WalletRedPacketRefunded" { + if eventType == walletmq.EventTypeWalletRedPacketClaimed || eventType == walletmq.EventTypeWalletRedPacketRefunded { status = stringFromDecoded(decoded, "packet_status") } totalCount := int32FromDecoded(decoded, "packet_count") remainingCount := int32FromDecoded(decoded, "remaining_count") - if remainingCount == 0 && eventType == "WalletRedPacketCreated" { + if remainingCount == 0 && eventType == walletmq.EventTypeWalletRedPacketCreated { remainingCount = totalCount } return redPacketPayloadFields{ diff --git a/services/activity-service/internal/client/user_profile_source.go b/services/activity-service/internal/client/user_profile_source.go index a670a063..49be4e74 100644 --- a/services/activity-service/internal/client/user_profile_source.go +++ b/services/activity-service/internal/client/user_profile_source.go @@ -36,11 +36,12 @@ func (s *GRPCUserProfileSource) GetSenderProfile(ctx context.Context, userID int return broadcastservice.SenderProfile{UserID: userID}, nil } return broadcastservice.SenderProfile{ - UserID: user.GetUserId(), - Account: user.GetDisplayUserId(), - Nickname: user.GetUsername(), - Avatar: user.GetAvatar(), - AvatarFrameURL: "", - RegionCode: user.GetRegionCode(), + UserID: user.GetUserId(), + Account: user.GetDisplayUserId(), + PrettyDisplayUserID: user.GetPrettyDisplayUserId(), + Nickname: user.GetUsername(), + Avatar: user.GetAvatar(), + AvatarFrameURL: "", + RegionCode: user.GetRegionCode(), }, nil } diff --git a/services/activity-service/internal/config/config.go b/services/activity-service/internal/config/config.go index f2edd547..a8c598ee 100644 --- a/services/activity-service/internal/config/config.go +++ b/services/activity-service/internal/config/config.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "hyapp/pkg/appcode" "hyapp/pkg/configx" "hyapp/pkg/logx" "hyapp/pkg/tencentim" @@ -39,6 +40,8 @@ type Config struct { TaskEventWorker TaskEventWorkerConfig `yaml:"task_event_worker"` // UserLeaderboardWorker 控制用户送礼榜单对 wallet_outbox 送礼事实的 Redis 投影。 UserLeaderboardWorker UserLeaderboardWorkerConfig `yaml:"user_leaderboard_worker"` + // WheelRewardWorker 补偿抽奖事务已经确定、但同步钱包发奖未完成的 WheelRewardSettlement outbox。 + WheelRewardWorker WheelRewardWorkerConfig `yaml:"wheel_reward_worker"` // TencentIM 是 activity-service 发送全局/区域播报群消息的 REST 配置。 TencentIM TencentIMConfig `yaml:"tencent_im"` // Broadcast 控制 IM 播报群 reconciler、outbox worker 和贵重礼物阈值。 @@ -119,6 +122,17 @@ type UserLeaderboardWorkerConfig struct { KeyPrefix string `yaml:"key_prefix"` } +// WheelRewardWorkerConfig 控制转盘发奖 outbox 的租户分区、锁和重试边界。 +// AppCodes 必须显式覆盖线上业务 App;worker 每轮逐 App claim,禁止空 context 默认只处理 lalu。 +type WheelRewardWorkerConfig struct { + Enabled bool `yaml:"enabled"` + AppCodes []string `yaml:"app_codes"` + PollInterval time.Duration `yaml:"poll_interval"` + BatchSize int `yaml:"batch_size"` + LockTTL time.Duration `yaml:"lock_ttl"` + MaxRetry int `yaml:"max_retry"` +} + // TencentIMConfig 描述 activity-service 调用腾讯云 IM REST API 的配置。 type TencentIMConfig struct { Enabled bool `yaml:"enabled"` @@ -151,7 +165,10 @@ func (c TencentIMConfig) RESTConfig() tencentim.RESTConfig { // BroadcastConfig stores activity-service broadcast worker policy. type BroadcastConfig struct { - Enabled bool `yaml:"enabled"` + Enabled bool `yaml:"enabled"` + // AppCodes 是共享腾讯 IM 应用内需要主动维护播报群的业务租户清单。 + // outbox 发现只能覆盖已经产生过消息的 App;新 App 上线前必须显式列入,才能提前建好全局/区域群。 + AppCodes []string `yaml:"app_codes"` SuperGiftMinValue int64 `yaml:"super_gift_min_value"` WorkerPollInterval time.Duration `yaml:"worker_poll_interval"` WorkerBatchSize int `yaml:"worker_batch_size"` @@ -174,6 +191,7 @@ type RocketMQConfig struct { Namespace string `yaml:"namespace"` VIPChannel bool `yaml:"vip_channel"` RoomOutbox RoomOutboxMQConfig `yaml:"room_outbox"` + LuckyGiftOutbox LuckyGiftOutboxMQConfig `yaml:"lucky_gift_outbox"` WalletOutbox WalletOutboxMQConfig `yaml:"wallet_outbox"` UserOutbox UserOutboxMQConfig `yaml:"user_outbox"` MessageActionOutbox MessageActionOutboxMQConfig `yaml:"message_action_outbox"` @@ -187,6 +205,15 @@ type RoomOutboxMQConfig struct { ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"` } +// LuckyGiftOutboxMQConfig 控制 activity 对幸运礼物 owner fact 的独立消费位点。 +// 它不能复用 room/wallet consumer group,否则 RocketMQ 的负载均衡会让区域飘屏事实被其他业务确认掉。 +type LuckyGiftOutboxMQConfig struct { + Enabled bool `yaml:"enabled"` + Topic string `yaml:"topic"` + ConsumerGroup string `yaml:"consumer_group"` + ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"` +} + // WalletOutboxMQConfig 控制 activity 对 wallet_outbox topic 的独立消费组。 type WalletOutboxMQConfig struct { Enabled bool `yaml:"enabled"` @@ -196,6 +223,7 @@ type WalletOutboxMQConfig struct { CumulativeRechargeConsumerGroup string `yaml:"cumulative_recharge_consumer_group"` InviteActivityConsumerGroup string `yaml:"invite_activity_consumer_group"` RedPacketBroadcastConsumerGroup string `yaml:"red_packet_broadcast_consumer_group"` + VIPRebateNoticeConsumerGroup string `yaml:"vip_rebate_notice_consumer_group"` TaskConsumerGroup string `yaml:"task_consumer_group"` UserLeaderboardConsumerGroup string `yaml:"user_leaderboard_consumer_group"` ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"` @@ -251,6 +279,14 @@ func Default() Config { UserLeaderboardWorker: UserLeaderboardWorkerConfig{ Enabled: false, }, + WheelRewardWorker: WheelRewardWorkerConfig{ + Enabled: true, + AppCodes: []string{"lalu", "fami", "huwaa"}, + PollInterval: time.Second, + BatchSize: 50, + LockTTL: 30 * time.Second, + MaxRetry: 16, + }, MessageActionConfirmWorker: MessageActionConfirmWorkerConfig{ Enabled: true, OutboxPollInterval: time.Second, @@ -269,6 +305,7 @@ func Default() Config { }, Broadcast: BroadcastConfig{ Enabled: false, + AppCodes: []string{"lalu", "fami", "huwaa"}, SuperGiftMinValue: 100000, WorkerPollInterval: time.Second, WorkerBatchSize: 100, @@ -303,6 +340,12 @@ func defaultRocketMQConfig() RocketMQConfig { ConsumerGroup: "hyapp-activity-room-outbox", ConsumerMaxReconsumeTimes: 16, }, + LuckyGiftOutbox: LuckyGiftOutboxMQConfig{ + Enabled: false, + Topic: "hyapp_lucky_gift_outbox", + ConsumerGroup: "hyapp-activity-lucky-gift-outbox", + ConsumerMaxReconsumeTimes: 16, + }, WalletOutbox: WalletOutboxMQConfig{ Enabled: false, Topic: "hyapp_wallet_outbox", @@ -311,6 +354,7 @@ func defaultRocketMQConfig() RocketMQConfig { CumulativeRechargeConsumerGroup: "hyapp-activity-cumulative-recharge-wallet-outbox", InviteActivityConsumerGroup: "hyapp-activity-invite-wallet-outbox", RedPacketBroadcastConsumerGroup: "hyapp-activity-red-packet-wallet-outbox", + VIPRebateNoticeConsumerGroup: "hyapp-activity-vip-rebate-notice-wallet-outbox", TaskConsumerGroup: "hyapp-activity-task-wallet-outbox", UserLeaderboardConsumerGroup: "hyapp-activity-user-leaderboard-wallet-outbox", ConsumerMaxReconsumeTimes: 16, @@ -391,6 +435,10 @@ func Load(path string) (Config, error) { if cfg.Broadcast.SuperGiftMinValue <= 0 { cfg.Broadcast.SuperGiftMinValue = 100000 } + cfg.Broadcast.AppCodes = normalizeAppCodes(cfg.Broadcast.AppCodes) + if len(cfg.Broadcast.AppCodes) == 0 { + cfg.Broadcast.AppCodes = append([]string(nil), Default().Broadcast.AppCodes...) + } if cfg.Broadcast.WorkerPollInterval <= 0 { cfg.Broadcast.WorkerPollInterval = time.Second } @@ -420,11 +468,32 @@ func Load(path string) (Config, error) { } cfg.UserLeaderboardWorker.RedisAddr = strings.TrimSpace(cfg.UserLeaderboardWorker.RedisAddr) cfg.UserLeaderboardWorker.KeyPrefix = strings.TrimSpace(cfg.UserLeaderboardWorker.KeyPrefix) + cfg.WheelRewardWorker.AppCodes = normalizeAppCodes(cfg.WheelRewardWorker.AppCodes) + if len(cfg.WheelRewardWorker.AppCodes) == 0 { + cfg.WheelRewardWorker.AppCodes = append([]string(nil), Default().WheelRewardWorker.AppCodes...) + } + if cfg.WheelRewardWorker.PollInterval <= 0 { + cfg.WheelRewardWorker.PollInterval = time.Second + } + if cfg.WheelRewardWorker.BatchSize <= 0 { + cfg.WheelRewardWorker.BatchSize = 50 + } + if cfg.WheelRewardWorker.LockTTL <= 0 { + cfg.WheelRewardWorker.LockTTL = 30 * time.Second + } + if cfg.WheelRewardWorker.MaxRetry <= 0 { + cfg.WheelRewardWorker.MaxRetry = 16 + } rocketMQ, err := normalizeRocketMQConfig(cfg.RocketMQ) if err != nil { return Config{}, err } cfg.RocketMQ = rocketMQ + if cfg.RocketMQ.LuckyGiftOutbox.Enabled && (!cfg.Broadcast.Enabled || !cfg.TencentIM.Enabled) { + // 消费成功只代表区域 outbox 已持久化;没有 worker 或腾讯 IM publisher 时记录会永久堆积, + // 因此不能让 lucky consumer 以“已开启”配置启动成只进不出的半链路。 + return Config{}, errors.New("rocketmq.lucky_gift_outbox.enabled requires broadcast.enabled and tencent_im.enabled") + } if (cfg.FirstRechargeRewardWorker.Enabled || cfg.CumulativeRechargeRewardWorker.Enabled || cfg.InviteActivityRewardWorker.Enabled || cfg.RedPacketBroadcastWorker.Enabled || cfg.UserLeaderboardWorker.Enabled) && !cfg.RocketMQ.WalletOutbox.Enabled { return Config{}, errors.New("wallet outbox workers require rocketmq.wallet_outbox.enabled") } @@ -440,6 +509,23 @@ func Load(path string) (Config, error) { return cfg, nil } +func normalizeAppCodes(input []string) []string { + seen := make(map[string]struct{}, len(input)) + out := make([]string, 0, len(input)) + for _, value := range input { + if strings.TrimSpace(value) == "" { + continue + } + value = appcode.Normalize(value) + if _, exists := seen[value]; exists { + continue + } + seen[value] = struct{}{} + out = append(out, value) + } + return out +} + func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) { defaults := defaultRocketMQConfig() cfg.NameServers = normalizeStringSlice(cfg.NameServers) @@ -457,6 +543,15 @@ func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) { if cfg.RoomOutbox.ConsumerMaxReconsumeTimes <= 0 { cfg.RoomOutbox.ConsumerMaxReconsumeTimes = defaults.RoomOutbox.ConsumerMaxReconsumeTimes } + if cfg.LuckyGiftOutbox.Topic = strings.TrimSpace(cfg.LuckyGiftOutbox.Topic); cfg.LuckyGiftOutbox.Topic == "" { + cfg.LuckyGiftOutbox.Topic = defaults.LuckyGiftOutbox.Topic + } + if cfg.LuckyGiftOutbox.ConsumerGroup = strings.TrimSpace(cfg.LuckyGiftOutbox.ConsumerGroup); cfg.LuckyGiftOutbox.ConsumerGroup == "" { + cfg.LuckyGiftOutbox.ConsumerGroup = defaults.LuckyGiftOutbox.ConsumerGroup + } + if cfg.LuckyGiftOutbox.ConsumerMaxReconsumeTimes <= 0 { + cfg.LuckyGiftOutbox.ConsumerMaxReconsumeTimes = defaults.LuckyGiftOutbox.ConsumerMaxReconsumeTimes + } if cfg.WalletOutbox.Topic = strings.TrimSpace(cfg.WalletOutbox.Topic); cfg.WalletOutbox.Topic == "" { cfg.WalletOutbox.Topic = defaults.WalletOutbox.Topic } @@ -473,6 +568,9 @@ func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) { if cfg.WalletOutbox.RedPacketBroadcastConsumerGroup = strings.TrimSpace(cfg.WalletOutbox.RedPacketBroadcastConsumerGroup); cfg.WalletOutbox.RedPacketBroadcastConsumerGroup == "" { cfg.WalletOutbox.RedPacketBroadcastConsumerGroup = defaults.WalletOutbox.RedPacketBroadcastConsumerGroup } + if cfg.WalletOutbox.VIPRebateNoticeConsumerGroup = strings.TrimSpace(cfg.WalletOutbox.VIPRebateNoticeConsumerGroup); cfg.WalletOutbox.VIPRebateNoticeConsumerGroup == "" { + cfg.WalletOutbox.VIPRebateNoticeConsumerGroup = defaults.WalletOutbox.VIPRebateNoticeConsumerGroup + } if cfg.WalletOutbox.TaskConsumerGroup = strings.TrimSpace(cfg.WalletOutbox.TaskConsumerGroup); cfg.WalletOutbox.TaskConsumerGroup == "" { cfg.WalletOutbox.TaskConsumerGroup = defaults.WalletOutbox.TaskConsumerGroup } @@ -512,7 +610,7 @@ func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) { if cfg.MessageActionOutbox.Retry < 0 { cfg.MessageActionOutbox.Retry = defaults.MessageActionOutbox.Retry } - if cfg.RoomOutbox.Enabled || cfg.WalletOutbox.Enabled || cfg.UserOutbox.Enabled || cfg.MessageActionOutbox.Enabled { + if cfg.RoomOutbox.Enabled || cfg.LuckyGiftOutbox.Enabled || cfg.WalletOutbox.Enabled || cfg.UserOutbox.Enabled || cfg.MessageActionOutbox.Enabled { cfg.Enabled = true } if cfg.Enabled { diff --git a/services/activity-service/internal/config/config_test.go b/services/activity-service/internal/config/config_test.go index 515ab57c..202a11df 100644 --- a/services/activity-service/internal/config/config_test.go +++ b/services/activity-service/internal/config/config_test.go @@ -1,6 +1,10 @@ package config -import "testing" +import ( + "os" + "path/filepath" + "testing" +) func TestLoadLocalEnablesOutboxMQConsumers(t *testing.T) { cfg, err := Load("../../configs/config.yaml") @@ -8,16 +12,25 @@ func TestLoadLocalEnablesOutboxMQConsumers(t *testing.T) { t.Fatalf("Load local config failed: %v", err) } // activity 的充值、红包、礼物和区域播报都依赖 MQ fanout,本地默认必须和测试/线上一致。 - if !cfg.RocketMQ.Enabled || !cfg.RocketMQ.RoomOutbox.Enabled || !cfg.RocketMQ.WalletOutbox.Enabled || !cfg.RocketMQ.UserOutbox.Enabled { + if !cfg.RocketMQ.Enabled || !cfg.RocketMQ.RoomOutbox.Enabled || !cfg.RocketMQ.LuckyGiftOutbox.Enabled || !cfg.RocketMQ.WalletOutbox.Enabled || !cfg.RocketMQ.UserOutbox.Enabled { t.Fatalf("local config must enable all activity outbox MQ consumers: %+v", cfg.RocketMQ) } + if len(cfg.Broadcast.AppCodes) != 3 || cfg.Broadcast.AppCodes[1] != "fami" { + t.Fatalf("local broadcast app list must include fami before it has outbox data: %+v", cfg.Broadcast.AppCodes) + } + if cfg.RocketMQ.LuckyGiftOutbox.Topic != "hyapp_lucky_gift_outbox" || cfg.RocketMQ.LuckyGiftOutbox.ConsumerGroup != "hyapp-activity-lucky-gift-outbox" { + t.Fatalf("local lucky gift consumer config mismatch: %+v", cfg.RocketMQ.LuckyGiftOutbox) + } if !cfg.FirstRechargeRewardWorker.Enabled || !cfg.CumulativeRechargeRewardWorker.Enabled || !cfg.InviteActivityRewardWorker.Enabled || !cfg.RedPacketBroadcastWorker.Enabled || !cfg.UserLeaderboardWorker.Enabled { t.Fatalf("local config must enable wallet outbox workers: first=%+v cumulative=%+v invite=%+v red_packet=%+v user_leaderboard=%+v", cfg.FirstRechargeRewardWorker, cfg.CumulativeRechargeRewardWorker, cfg.InviteActivityRewardWorker, cfg.RedPacketBroadcastWorker, cfg.UserLeaderboardWorker) } if cfg.UserLeaderboardWorker.RedisAddr != "127.0.0.1:13379" || cfg.UserLeaderboardWorker.KeyPrefix != "activity:user_leaderboard" { t.Fatalf("local user leaderboard worker redis config mismatch: %+v", cfg.UserLeaderboardWorker) } - if cfg.RocketMQ.WalletOutbox.InviteActivityConsumerGroup == "" || cfg.RocketMQ.WalletOutbox.UserLeaderboardConsumerGroup == "" || cfg.RocketMQ.UserOutbox.InviteActivityConsumerGroup == "" { + if !cfg.WheelRewardWorker.Enabled || len(cfg.WheelRewardWorker.AppCodes) != 3 || cfg.WheelRewardWorker.AppCodes[1] != "fami" || cfg.WheelRewardWorker.BatchSize <= 0 || cfg.WheelRewardWorker.LockTTL <= 0 { + t.Fatalf("local wheel reward compensation must cover every app with bounded claim settings: %+v", cfg.WheelRewardWorker) + } + if cfg.RocketMQ.WalletOutbox.InviteActivityConsumerGroup == "" || cfg.RocketMQ.WalletOutbox.UserLeaderboardConsumerGroup == "" || cfg.RocketMQ.WalletOutbox.VIPRebateNoticeConsumerGroup == "" || cfg.RocketMQ.UserOutbox.InviteActivityConsumerGroup == "" { t.Fatalf("local config must configure invite activity consumer groups: wallet=%+v user=%+v", cfg.RocketMQ.WalletOutbox, cfg.RocketMQ.UserOutbox) } } @@ -30,6 +43,12 @@ func TestLoadTencentExampleEnablesRoomOutboxMQ(t *testing.T) { if !cfg.RocketMQ.Enabled || !cfg.RocketMQ.RoomOutbox.Enabled || cfg.RocketMQ.RoomOutbox.Topic == "" || cfg.RocketMQ.RoomOutbox.ConsumerGroup == "" { t.Fatalf("tencent example must configure room outbox MQ consumer: %+v", cfg.RocketMQ) } + if len(cfg.Broadcast.AppCodes) != 3 || cfg.Broadcast.AppCodes[1] != "fami" { + t.Fatalf("tencent broadcast app list must include fami: %+v", cfg.Broadcast.AppCodes) + } + if !cfg.RocketMQ.LuckyGiftOutbox.Enabled || cfg.RocketMQ.LuckyGiftOutbox.Topic != "hyapp_lucky_gift_outbox" || cfg.RocketMQ.LuckyGiftOutbox.ConsumerGroup == "" { + t.Fatalf("tencent example must configure lucky gift outbox MQ consumer: %+v", cfg.RocketMQ.LuckyGiftOutbox) + } if !cfg.RocketMQ.UserOutbox.Enabled || cfg.RocketMQ.UserOutbox.Topic != "hyapp_user_outbox" || cfg.RocketMQ.UserOutbox.ConsumerGroup == "" { t.Fatalf("tencent example must configure user outbox MQ consumer: %+v", cfg.RocketMQ.UserOutbox) } @@ -42,4 +61,26 @@ func TestLoadTencentExampleEnablesRoomOutboxMQ(t *testing.T) { if !cfg.UserLeaderboardWorker.Enabled || cfg.UserLeaderboardWorker.RedisAddr == "" || cfg.UserLeaderboardWorker.RedisPassword == "" || cfg.RocketMQ.WalletOutbox.UserLeaderboardConsumerGroup == "" { t.Fatalf("tencent example must configure user leaderboard worker: worker=%+v wallet=%+v", cfg.UserLeaderboardWorker, cfg.RocketMQ.WalletOutbox) } + if !cfg.WheelRewardWorker.Enabled || len(cfg.WheelRewardWorker.AppCodes) != 3 || cfg.WheelRewardWorker.AppCodes[2] != "huwaa" || cfg.WheelRewardWorker.MaxRetry != 16 { + t.Fatalf("tencent example must configure multi-app wheel reward compensation: %+v", cfg.WheelRewardWorker) + } +} + +func TestLoadRejectsLuckyGiftConsumerWithoutBroadcastDelivery(t *testing.T) { + path := filepath.Join(t.TempDir(), "config.yaml") + if err := os.WriteFile(path, []byte(` +tencent_im: + enabled: false +broadcast: + enabled: false +rocketmq: + name_servers: ["127.0.0.1:9876"] + lucky_gift_outbox: + enabled: true +`), 0o600); err != nil { + t.Fatalf("write config: %v", err) + } + if _, err := Load(path); err == nil { + t.Fatal("lucky gift consumer without broadcast delivery must be rejected") + } } diff --git a/services/activity-service/internal/domain/broadcast/broadcast.go b/services/activity-service/internal/domain/broadcast/broadcast.go index 502a043e..95a400c6 100644 --- a/services/activity-service/internal/domain/broadcast/broadcast.go +++ b/services/activity-service/internal/domain/broadcast/broadcast.go @@ -22,6 +22,9 @@ const ( TypeRoomPasswordChanged = "room_password_changed" // TypeLuckyGiftBigWin 是幸运礼物中奖区域飘屏;当前只让 10 倍及以上大奖进入播报 outbox。 TypeLuckyGiftBigWin = "lucky_gift_big_win" + // TypeVIPOnlineNotice 是 VIP 用户进入 App 时的全局飘屏。是否允许发送必须由 gateway + // 先向 wallet-service 校验 effective benefit,activity-service 只负责持久化和 IM 投递。 + TypeVIPOnlineNotice = "vip_online_notice" // StatusPending 表示消息已持久化,尚未被 worker claim。 StatusPending = "pending" @@ -80,6 +83,15 @@ type ConsumeRoomEventResult struct { BroadcastCreated bool } +// ConsumeLuckyGiftEventResult 描述 lucky-gift owner fact 是否生成区域播报; +// EventID 保留来源事实,BroadcastEventID 是 activity-service 自己的幂等投影键。 +type ConsumeLuckyGiftEventResult struct { + EventID string + Status string + BroadcastEventID string + BroadcastCreated bool +} + // RedPacketWalletEvent 是 wallet-service 已提交的红包资金事实。 type RedPacketWalletEvent struct { AppCode string diff --git a/services/activity-service/internal/domain/growth/growth.go b/services/activity-service/internal/domain/growth/growth.go index 891784ae..6e9b77b2 100644 --- a/services/activity-service/internal/domain/growth/growth.go +++ b/services/activity-service/internal/domain/growth/growth.go @@ -8,10 +8,30 @@ const ( StatusActive = "active" StatusDisabled = "disabled" - RewardStatusPending = "pending" - RewardStatusRunning = "running" - RewardStatusGranted = "granted" - RewardStatusFailed = "failed" + RewardStatusPending = "pending" + RewardStatusRunning = "running" + RewardStatusGranted = "granted" + RewardStatusFailed = "failed" + RewardStatusCanceled = "canceled" + + RewardOriginOrganic = "organic" + RewardOriginTemporary = "temporary" + + RevokeStatusNone = "none" + RevokeStatusPending = "pending" + RevokeStatusRunning = "running" + RevokeStatusRevoked = "revoked" + RevokeStatusFailed = "failed" + RevokeStatusSkipped = "skipped" + + TemporaryLevelStatusActive = "active" + TemporaryLevelStatusSuperseded = "superseded" + TemporaryLevelStatusExpired = "expired" + + NoticeStatusPending = "pending" + NoticeStatusSent = "sent" + NoticeStatusSkipped = "skipped" + NoticeStatusFailed = "failed" RewardSourceLevel = "level" RewardSourceTier = "tier" @@ -218,22 +238,121 @@ type SetUserLevelResult struct { ServerTimeMS int64 } -// RewardJob 是等级奖励资源组的异步发放任务。 -type RewardJob struct { - AppCode string +// TemporaryLevel 是真实成长账户之上的限时展示层。BaselineTotalValue 只做审计, +// 有效期经验始终按 value event 的 occurred_at_ms 落在 [StartedAtMS, ExpiresAtMS) 内聚合。 +type TemporaryLevel struct { + AppCode string + TemporaryLevelID string + CommandID string + UserID int64 + Track string + TargetLevel int32 + TargetRequiredValue int64 + BaselineTotalValue int64 + StartedAtMS int64 + ExpiresAtMS int64 + Status string + Generation int32 + OperatorAdminID int64 + Reason string + FinalLevel int32 + ActivationNoticeStatus string + ExpiryNoticeStatus string + AttemptCount int32 + FailureReason string + CreatedAtMS int64 + UpdatedAtMS int64 +} + +// TemporaryLevelAdjustment 是一次后台临时等级调整。wealth/charm 可在同一命令中各出现一次。 +type TemporaryLevelAdjustment struct { + Track string + Level int32 + DurationDays int32 +} + +// AdjustTemporaryLevelsCommand 以 CommandID 为整批幂等键;RequestHash 用于拒绝同键不同载荷。 +type AdjustTemporaryLevelsCommand struct { + CommandID string + RequestHash string + UserID int64 + Adjustments []TemporaryLevelAdjustment + OperatorAdminID int64 + Reason string +} + +// AdminTrackProfile 同时返回真实账户和当前展示层,后台无需从临时目标反推真实经验。 +type AdminTrackProfile struct { + Track string + RealLevel int32 + RealTotalValue int64 + DisplayLevel int32 + DisplayValue int64 + TemporaryLevelID string + TemporaryTargetLevel int32 + StartedAtMS int64 + ExpiresAtMS int64 +} + +type AdminUserLevelProfile struct { + UserID int64 + Tracks []AdminTrackProfile + ServerTimeMS int64 +} + +type AdminLevelProfiles struct { + Profiles []AdminUserLevelProfile + ServerTimeMS int64 +} + +// TemporaryLevelWork 是 cron 认领后的副作用快照。过期状态、奖励永久化/撤回准备均已在认领事务内提交。 +type TemporaryLevelWork struct { + TemporaryLevel + SendActivationNotice bool + SendExpiryNotice bool +} + +// RewardWalletGrant 保存一个奖励任务下的每次钱包发放。资源组、头像框和短徽章必须分别留痕。 +type RewardWalletGrant struct { + RewardJobID string + GrantKind string + WalletGrantID string + CreatedAtMS int64 +} + +// RewardRevocation 是一条可独立幂等重试的钱包 grant 撤回任务。 +type RewardRevocation struct { RewardJobID string + WalletGrantID string + TemporaryLevelID string UserID int64 Track string - RewardSourceType string - RewardSourceID string - ResourceGroupID int64 - WalletCommandID string - WalletGrantID string - Status string AttemptCount int32 - FailureReason string - CreatedAtMS int64 - UpdatedAtMS int64 +} + +// RewardJob 是等级奖励资源组的异步发放任务。 +type RewardJob struct { + AppCode string + RewardJobID string + UserID int64 + Track string + RewardSourceType string + RewardSourceID string + ResourceGroupID int64 + WalletCommandID string + WalletGrantID string + RewardOrigin string + TemporaryLevelID string + GrantGeneration int32 + RewardLevel int32 + Permanent bool + TemporaryExpiresAtMS int64 + RevokeStatus string + Status string + AttemptCount int32 + FailureReason string + CreatedAtMS int64 + UpdatedAtMS int64 } // RewardQuery 是用户奖励记录分页筛选。 diff --git a/services/activity-service/internal/domain/wheel/wheel.go b/services/activity-service/internal/domain/wheel/wheel.go index eec9c6f7..4753e83a 100644 --- a/services/activity-service/internal/domain/wheel/wheel.go +++ b/services/activity-service/internal/domain/wheel/wheel.go @@ -13,6 +13,12 @@ const ( RewardTypeDress = "dress" EventTypeWheelRewardSettlement = "WheelRewardSettlement" + + OutboxStatusPending = "pending" + OutboxStatusDelivering = "delivering" + OutboxStatusRetryable = "retryable" + OutboxStatusDelivered = "delivered" + OutboxStatusFailed = "failed" ) // RuleConfig 是转盘配置的不可变版本快照。 @@ -113,3 +119,19 @@ type DrawQuery struct { Page int32 PageSize int32 } + +// RewardOutboxRecord 是转盘发奖补偿 worker 的持久化工作项。 +// Payload 始终保留抽奖事务写入时的不可变奖品快照;补偿时不能回读当前奖档配置,否则运营改配置后会改变历史应发权益。 +type RewardOutboxRecord struct { + AppCode string + OutboxID string + Payload []byte + Status string + RetryCount int + NextRetryAtMS int64 + LockedBy string + LockUntilMS int64 + LastError string + CreatedAtMS int64 + UpdatedAtMS int64 +} diff --git a/services/activity-service/internal/service/agencyopening/service.go b/services/activity-service/internal/service/agencyopening/service.go index 9d27130e..34ce249e 100644 --- a/services/activity-service/internal/service/agencyopening/service.go +++ b/services/activity-service/internal/service/agencyopening/service.go @@ -221,6 +221,11 @@ func (s *Service) HandleRoomEvent(ctx context.Context, envelope *roomeventsv1.Ev } eventCtx := appcode.WithContext(ctx, envelope.GetAppCode()) ownerUserID, err := s.fetchRoomOwner(eventCtx, roomID) + if xerr.IsCode(err, xerr.NotFound) { + // RoomGiftSent 已经是 room-service 提交过的历史事实;若消费时房间已被删除,当前 owner 已不可恢复, + // 该事件对“开业房主流水”只能收敛为 skipped。这里只吞精确 NotFound,网络、超时和内部错误仍返回 MQ 重试,避免把临时依赖故障误判成业务终态。 + return 0, nil + } if err != nil || ownerUserID <= 0 { return 0, err } diff --git a/services/activity-service/internal/service/agencyopening/service_test.go b/services/activity-service/internal/service/agencyopening/service_test.go index ce87761b..92f46f50 100644 --- a/services/activity-service/internal/service/agencyopening/service_test.go +++ b/services/activity-service/internal/service/agencyopening/service_test.go @@ -6,6 +6,8 @@ import ( "time" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" roomeventsv1 "hyapp.local/api/proto/events/room/v1" roomv1 "hyapp.local/api/proto/room/v1" @@ -109,6 +111,46 @@ func TestHandleRoomEventScoresApplicantRoomOwner(t *testing.T) { } } +func TestHandleRoomEventTreatsDeletedRoomAsTerminalSkip(t *testing.T) { + repo := &agencyOpeningFakeRepo{consumeStatus: domain.EventStatusConsumed} + room := &agencyOpeningFakeRoomClient{err: status.Error(codes.NotFound, "room not found")} + svc := New(repo, nil, nil, room) + body, err := proto.Marshal(&roomeventsv1.RoomGiftSent{SenderUserId: 9, TargetUserId: 88, GiftValue: 900}) + if err != nil { + t.Fatalf("marshal gift: %v", err) + } + + count, err := svc.HandleRoomEvent(context.Background(), &roomeventsv1.EventEnvelope{ + EventId: "deleted-room-gift", RoomId: "deleted-room", EventType: "RoomGiftSent", AppCode: "lalu", Body: body, + }) + if err != nil || count != 0 { + t.Fatalf("deleted room must be terminal skipped, count=%d err=%v", count, err) + } + if repo.consumed.EventID != "" { + t.Fatalf("deleted room must not create agency turnover: %+v", repo.consumed) + } +} + +func TestHandleRoomEventKeepsTransientRoomFailureRetryable(t *testing.T) { + repo := &agencyOpeningFakeRepo{consumeStatus: domain.EventStatusConsumed} + room := &agencyOpeningFakeRoomClient{err: status.Error(codes.Unavailable, "room service unavailable")} + svc := New(repo, nil, nil, room) + body, err := proto.Marshal(&roomeventsv1.RoomGiftSent{SenderUserId: 9, TargetUserId: 88, GiftValue: 900}) + if err != nil { + t.Fatalf("marshal gift: %v", err) + } + + count, err := svc.HandleRoomEvent(context.Background(), &roomeventsv1.EventEnvelope{ + EventId: "transient-room-gift", RoomId: "room-1", EventType: "RoomGiftSent", AppCode: "lalu", Body: body, + }) + if err == nil || status.Code(err) != codes.Unavailable || count != 0 { + t.Fatalf("transient room failure must remain retryable, count=%d err=%v", count, err) + } + if repo.consumed.EventID != "" { + t.Fatalf("transient room failure must not create partial projection: %+v", repo.consumed) + } +} + type agencyOpeningFakeRepo struct { currentCycle domain.Cycle hasCurrent bool @@ -179,10 +221,14 @@ func (s agencyOpeningFakeAgencySource) ResolveOwnerAgency(context.Context, int64 type agencyOpeningFakeRoomClient struct { ownerUserID int64 roomID string + err error } func (c *agencyOpeningFakeRoomClient) AdminGetRoom(_ context.Context, req *roomv1.AdminGetRoomRequest, _ ...grpc.CallOption) (*roomv1.AdminGetRoomResponse, error) { c.roomID = req.GetRoomId() + if c.err != nil { + return nil, c.err + } return &roomv1.AdminGetRoomResponse{ Room: &roomv1.AdminRoomListItem{OwnerUserId: c.ownerUserID}, }, nil diff --git a/services/activity-service/internal/service/broadcast/lucky_gift.go b/services/activity-service/internal/service/broadcast/lucky_gift.go new file mode 100644 index 00000000..c2245faa --- /dev/null +++ b/services/activity-service/internal/service/broadcast/lucky_gift.go @@ -0,0 +1,195 @@ +package broadcast + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "google.golang.org/protobuf/proto" + luckygifteventsv1 "hyapp.local/api/proto/events/luckygift/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/luckygiftmq" + "hyapp/pkg/xerr" + broadcastdomain "hyapp/services/activity-service/internal/domain/broadcast" +) + +// HandleLuckyGiftEvent 把 lucky-gift-service 在钱包返奖成功后发布的 owner fact 投影成区域 IM outbox。 +// MQ 事实只负责描述已发生的中奖;activity-service 独立拥有区域展示策略和腾讯 IM 投递状态,不能反写 owner outbox。 +func (s *Service) HandleLuckyGiftEvent(ctx context.Context, envelope *luckygifteventsv1.EventEnvelope) (broadcastdomain.ConsumeLuckyGiftEventResult, error) { + if envelope == nil { + return broadcastdomain.ConsumeLuckyGiftEventResult{}, xerr.New(xerr.InvalidArgument, "lucky gift event envelope is required") + } + result := broadcastdomain.ConsumeLuckyGiftEventResult{ + EventID: envelope.GetEventId(), + Status: broadcastdomain.StatusSkipped, + } + if envelope.GetEventType() != luckygiftmq.EventTypeLuckyGiftDrawn { + // 独立 topic 未来可能增加审计类事实;未知类型必须确认跳过,不能误生成客户端无法识别的播报。 + return result, nil + } + + var event luckygifteventsv1.LuckyGiftDrawn + if err := proto.Unmarshal(envelope.GetBody(), &event); err != nil { + return broadcastdomain.ConsumeLuckyGiftEventResult{}, fmt.Errorf("decode lucky gift drawn body: %w", err) + } + if err := validateLuckyGiftDrawnEnvelope(envelope, &event); err != nil { + return broadcastdomain.ConsumeLuckyGiftEventResult{}, err + } + // 区域顶部飘屏只接受已经实际到账的 10 倍及以上奖励。pending/failed 事实、0 奖励和无区域事实 + // 都属于合法跳过,避免展示先于钱包到账或把区域未知事件扩大成全局消息。 + if !shouldPublishLuckyGiftBigWin(&event) { + return result, nil + } + + eventCtx := appcode.WithContext(ctx, envelope.GetAppCode()) + sender, err := s.luckyGiftSenderProfile(eventCtx, &event) + if err != nil { + // 资料查询是完整 IM 快照的一部分;暂时失败必须交还 MQ 重试,不能先确认后永久退化成错误昵称。 + return broadcastdomain.ConsumeLuckyGiftEventResult{}, fmt.Errorf("resolve lucky gift sender profile: %w", err) + } + broadcastEventID := "lucky_gift_big_win:" + event.GetDrawId() + payloadJSON, err := luckyGiftBigWinPayload(envelope, &event, broadcastEventID, sender, s.now().UTC().UnixMilli()) + if err != nil { + return broadcastdomain.ConsumeLuckyGiftEventResult{}, err + } + published, err := s.PublishRegionBroadcast(eventCtx, PublishInput{ + EventID: broadcastEventID, + BroadcastType: broadcastdomain.TypeLuckyGiftBigWin, + RegionID: event.GetVisibleRegionId(), + PayloadJSON: payloadJSON, + }) + if err != nil { + return broadcastdomain.ConsumeLuckyGiftEventResult{}, err + } + // im_broadcast_outbox 的 (app_code,event_id) 是本投影的幂等事实;MQ 重投只会得到 Created=false, + // 不会生成第二条区域消息,也不需要读取 lucky-gift-service 的 owner outbox 状态。 + return broadcastdomain.ConsumeLuckyGiftEventResult{ + EventID: envelope.GetEventId(), + Status: published.Status, + BroadcastEventID: published.EventID, + BroadcastCreated: published.Created, + }, nil +} + +func validateLuckyGiftDrawnEnvelope(envelope *luckygifteventsv1.EventEnvelope, event *luckygifteventsv1.LuckyGiftDrawn) error { + if strings.TrimSpace(envelope.GetAppCode()) == "" || strings.TrimSpace(envelope.GetEventId()) == "" { + return xerr.New(xerr.InvalidArgument, "lucky gift event identity is incomplete") + } + drawID := strings.TrimSpace(event.GetDrawId()) + if drawID == "" || strings.TrimSpace(event.GetRoomId()) == "" || event.GetSenderUserId() <= 0 { + return xerr.New(xerr.InvalidArgument, "lucky gift drawn fact is incomplete") + } + if envelopeDrawID := strings.TrimSpace(envelope.GetDrawId()); envelopeDrawID != "" && envelopeDrawID != drawID { + return xerr.New(xerr.InvalidArgument, "lucky gift envelope draw_id does not match body") + } + return nil +} + +func shouldPublishLuckyGiftBigWin(event *luckygifteventsv1.LuckyGiftDrawn) bool { + if event == nil || !strings.EqualFold(strings.TrimSpace(event.GetRewardStatus()), "granted") { + return false + } + return event.GetEffectiveRewardCoins() > 0 && + event.GetVisibleRegionId() > 0 && + event.GetMultiplierPpm() >= luckygiftmq.LuckyGiftDisplayMinMultiplierPPM +} + +func (s *Service) luckyGiftSenderProfile(ctx context.Context, event *luckygifteventsv1.LuckyGiftDrawn) (SenderProfile, error) { + profile := SenderProfile{ + UserID: event.GetSenderUserId(), + Account: strings.TrimSpace(event.GetSenderDisplayUserId()), + PrettyDisplayUserID: strings.TrimSpace(event.GetSenderPrettyDisplayUserId()), + Nickname: strings.TrimSpace(event.GetSenderName()), + Avatar: strings.TrimSpace(event.GetSenderAvatar()), + } + // 送礼主链路已经把展示快照写进 owner fact;字段齐全时禁止回查当前资料,避免用户改名后历史事实漂移。 + if profile.Nickname != "" && profile.Account != "" { + return profile, nil + } + resolved, err := s.broadcastUserProfile(ctx, profile.UserID) + if err != nil { + return SenderProfile{}, err + } + if profile.Account == "" { + profile.Account = strings.TrimSpace(resolved.Account) + } + if profile.PrettyDisplayUserID == "" { + profile.PrettyDisplayUserID = strings.TrimSpace(resolved.PrettyDisplayUserID) + } + if profile.Nickname == "" { + profile.Nickname = firstLuckyGiftDisplayValue(resolved.Nickname, profile.PrettyDisplayUserID, profile.Account) + } + if profile.Avatar == "" { + profile.Avatar = strings.TrimSpace(resolved.Avatar) + } + return profile, nil +} + +func firstLuckyGiftDisplayValue(values ...string) string { + for _, value := range values { + if normalized := strings.TrimSpace(value); normalized != "" { + return normalized + } + } + return "" +} + +func luckyGiftBigWinPayload(envelope *luckygifteventsv1.EventEnvelope, event *luckygifteventsv1.LuckyGiftDrawn, eventID string, sender SenderProfile, sentAtMS int64) (string, error) { + createdAtMS := event.GetDrawCreatedAtMs() + if createdAtMS <= 0 { + createdAtMS = envelope.GetOccurredAtMs() + } + // 字段保持拆服前 Flutter 已消费的扁平协议,并补充 granted/pretty ID 快照;不携带 gift_name, + // 客户端继续用 gift_id 命中本地多语言礼物配置,避免服务端文案覆盖客户端翻译。 + payload := map[string]any{ + "event_type": "lucky_gift_drawn", + "event_id": eventID, + "broadcast_type": broadcastdomain.TypeLuckyGiftBigWin, + "scope": broadcastdomain.ScopeRegion, + "app_code": envelope.GetAppCode(), + "region_id": event.GetVisibleRegionId(), + "visible_region_id": event.GetVisibleRegionId(), + "draw_id": event.GetDrawId(), + "command_id": event.GetCommandId(), + "pool_id": event.GetPoolId(), + "room_id": event.GetRoomId(), + "gift_id": event.GetGiftId(), + "gift_count": event.GetGiftCount(), + "user_id": event.GetSenderUserId(), + "sender_user_id": event.GetSenderUserId(), + "target_user_id": event.GetTargetUserId(), + "sender_name": sender.Nickname, + "sender_avatar": sender.Avatar, + "sender_display_user_id": sender.Account, + "sender_pretty_display_user_id": sender.PrettyDisplayUserID, + "coin_spent": event.GetCoinSpent(), + "rule_version": event.GetRuleVersion(), + "experience_pool": event.GetExperiencePool(), + "selected_tier_id": event.GetSelectedTierId(), + "multiplier_ppm": event.GetMultiplierPpm(), + "base_reward_coins": event.GetBaseRewardCoins(), + "effective_reward_coins": event.GetEffectiveRewardCoins(), + "reward_status": "granted", + "wallet_transaction_id": event.GetWalletTransactionId(), + "stage_feedback": event.GetStageFeedback(), + "high_multiplier": event.GetHighMultiplier(), + "created_at_ms": createdAtMS, + "draw_created_at_ms": createdAtMS, + "reward_granted_at_ms": event.GetRewardGrantedAtMs(), + "sent_at_ms": sentAtMS, + "action": map[string]any{ + "type": "enter_room", + "room_id": event.GetRoomId(), + }, + "sender": map[string]any{ + "user_id": sender.UserID, + "display_user_id": sender.Account, + "pretty_display_user_id": sender.PrettyDisplayUserID, + "nickname": sender.Nickname, + "avatar": sender.Avatar, + }, + } + encoded, err := json.Marshal(payload) + return string(encoded), err +} diff --git a/services/activity-service/internal/service/broadcast/service.go b/services/activity-service/internal/service/broadcast/service.go index 91ef5ef0..c04a9b41 100644 --- a/services/activity-service/internal/service/broadcast/service.go +++ b/services/activity-service/internal/service/broadcast/service.go @@ -8,7 +8,9 @@ import ( "errors" "fmt" "log/slog" + "sort" "strings" + "sync/atomic" "time" "google.golang.org/protobuf/proto" @@ -58,18 +60,21 @@ type SenderProfileSource interface { // SenderProfile 是播报 IM payload 的发送者展示字段。 // activity-service 不持久化该资料,只在消费事实时组装一次播报消息。 type SenderProfile struct { - UserID int64 - Account string - Nickname string - Avatar string - AvatarFrameURL string - RegionCode string + UserID int64 + Account string + PrettyDisplayUserID string + Nickname string + Avatar string + AvatarFrameURL string + RegionCode string } // Config 保存播报策略和 worker 参数。 // SuperGiftMinValue 是产品策略阈值;Worker* 是 outbox 投递吞吐和故障恢复参数。 type Config struct { - NodeID string + NodeID string + // AppCodes 是共享腾讯 IM 应用内需要维护的业务租户;群名和 outbox 仍按 app_code 隔离。 + AppCodes []string SuperGiftMinValue int64 WorkerBatchSize int WorkerLockTTL time.Duration @@ -101,6 +106,8 @@ type Service struct { regionSource RegionSource senderProfileSource SenderProfileSource now func() time.Time + // outboxClaimCursor 轮转跨 App claim 起点,避免默认 lalu 的持续积压饿死其他租户播报。 + outboxClaimCursor atomic.Uint64 } // New 创建播报服务。 @@ -222,6 +229,13 @@ func (s *Service) RemoveRegionBroadcastMember(ctx context.Context, userID int64, return "", false, xerr.New(xerr.InvalidArgument, err.Error()) } if err := s.publisher.DeleteGroupMember(ctx, groupID, userID); err != nil { + var restErr tencentim.RESTError + if errors.As(err, &restErr) && restErr.Code == 10007 && strings.Contains(strings.ToLower(restErr.Info), "disable kick member") { + // 区域播报群是 AVChatRoom,腾讯 IM 明确不允许服务端踢成员。用户换区依靠客户端 + // 离开旧群并加入新群;这个永久能力拒绝不能重试,否则每条 UserRegionChanged + // 都会重投到 DLQ。removed=false 保留真实结果,nil 让 MQ 确认该读模型操作已降级完成。 + return groupID, false, nil + } return groupID, false, err } // 过渡期老客户端可能仍在 v1 旧区域群里,切区时一并尽力移除; @@ -287,14 +301,15 @@ func (s *Service) ProcessPendingBroadcasts(ctx context.Context, options WorkerOp } options = normalizeWorkerOptions(options, s.cfg) nowMS := s.now().UTC().UnixMilli() - records, err := s.repository.ClaimPendingBroadcastOutbox(ctx, options.WorkerID, nowMS, nowMS+options.LockTTL.Milliseconds(), options.BatchSize, options.MaxRetry) + records, err := s.claimPendingBroadcasts(ctx, options, nowMS) if err != nil { return broadcastdomain.ProcessResult{}, err } result := broadcastdomain.ProcessResult{ClaimedCount: len(records), HasMore: len(records) >= options.BatchSize} for _, record := range records { + recordCtx := appcode.WithContext(ctx, record.AppCode) // 腾讯 REST 只接受已序列化 JSON;event_id/desc/ext 让客户端和日志都能快速识别消息类型。 - err := s.publisher.PublishGroupCustomMessage(ctx, tencentim.CustomGroupMessage{ + err := s.publisher.PublishGroupCustomMessage(recordCtx, tencentim.CustomGroupMessage{ GroupID: record.GroupID, EventID: record.EventID, Desc: record.BroadcastType, @@ -305,7 +320,7 @@ func (s *Service) ProcessPendingBroadcasts(ctx context.Context, options WorkerOp result.FailureCount++ // 单条投递失败必须落日志:区域群缺失/满员这类腾讯侧错误此前只写 last_error 列, // 重试烧完后记录无声沉底为 failed,线上排障只能翻表。attempt 达到上限时标记 final 便于告警。 - logx.Error(ctx, "broadcast_outbox_deliver_failed", err, + logx.Error(recordCtx, "broadcast_outbox_deliver_failed", err, slog.String("event_id", record.EventID), slog.String("group_id", record.GroupID), slog.String("scope", record.Scope), @@ -314,18 +329,84 @@ func (s *Service) ProcessPendingBroadcasts(ctx context.Context, options WorkerOp slog.Int("max_retry", options.MaxRetry), slog.Bool("final", record.AttemptCount+1 >= options.MaxRetry)) // Mark 失败只记录为 best-effort:当前记录锁会过期,下一轮 worker 仍可重新接管。 - _ = s.repository.MarkBroadcastOutboxFailed(ctx, record.EventID, options.WorkerID, s.now().UTC().UnixMilli(), s.now().UTC().Add(options.PollInterval).UnixMilli(), options.MaxRetry, trimError(err.Error())) + _ = s.repository.MarkBroadcastOutboxFailed(recordCtx, record.EventID, options.WorkerID, s.now().UTC().UnixMilli(), s.now().UTC().Add(options.PollInterval).UnixMilli(), options.MaxRetry, trimError(err.Error())) continue } - if err := s.repository.MarkBroadcastOutboxDelivered(ctx, record.EventID, s.now().UTC().UnixMilli()); err != nil { + if err := s.repository.MarkBroadcastOutboxDelivered(recordCtx, record.EventID, s.now().UTC().UnixMilli()); err != nil { return result, err } result.SuccessCount++ - s.dualSendLegacyBroadcast(ctx, record) + s.dualSendLegacyBroadcast(recordCtx, record) } return result, nil } +type broadcastOutboxAppCodeLister interface { + ListBroadcastOutboxAppCodes(ctx context.Context) ([]string, error) +} + +func (s *Service) claimPendingBroadcasts(ctx context.Context, options WorkerOptions, nowMS int64) ([]broadcastdomain.OutboxRecord, error) { + appCodes, err := s.broadcastOutboxAppCodes(ctx) + if err != nil || len(appCodes) == 0 { + return nil, err + } + start := int(s.outboxClaimCursor.Add(1)-1) % len(appCodes) + ordered := make([]string, 0, len(appCodes)) + for offset := 0; offset < len(appCodes); offset++ { + ordered = append(ordered, appCodes[(start+offset)%len(appCodes)]) + } + + // 第一轮给每个 App 公平配额;若有 App 无积压,第二轮再把剩余额度让给有积压的 App,兼顾隔离和吞吐。 + records := make([]broadcastdomain.OutboxRecord, 0, options.BatchSize) + quota := (options.BatchSize + len(ordered) - 1) / len(ordered) + for pass := 0; pass < 2 && len(records) < options.BatchSize; pass++ { + for _, appCode := range ordered { + remaining := options.BatchSize - len(records) + if remaining <= 0 { + break + } + limit := remaining + if pass == 0 && limit > quota { + limit = quota + } + claimed, err := s.repository.ClaimPendingBroadcastOutbox(appcode.WithContext(ctx, appCode), options.WorkerID, nowMS, nowMS+options.LockTTL.Milliseconds(), limit, options.MaxRetry) + if err != nil { + return nil, err + } + records = append(records, claimed...) + } + } + return records, nil +} + +func (s *Service) broadcastOutboxAppCodes(ctx context.Context) ([]string, error) { + // 配置清单保证零 outbox 的新 App 也会建群;数据库发现继续兜底历史或灰度 App,避免漏投已有记录。 + appCodes := append([]string(nil), s.cfg.AppCodes...) + appCodes = append(appCodes, appcode.FromContext(ctx)) + if lister, ok := s.repository.(broadcastOutboxAppCodeLister); ok { + discovered, err := lister.ListBroadcastOutboxAppCodes(ctx) + if err != nil { + return nil, err + } + appCodes = append(appCodes, discovered...) + } + seen := make(map[string]struct{}, len(appCodes)) + normalized := make([]string, 0, len(appCodes)) + for _, appCode := range appCodes { + if strings.TrimSpace(appCode) == "" { + continue + } + appCode = appcode.Normalize(appCode) + if _, exists := seen[appCode]; exists { + continue + } + seen[appCode] = struct{}{} + normalized = append(normalized, appCode) + } + sort.Strings(normalized) + return normalized, nil +} + // dualSendLegacyBroadcast 在播报群从 v1(ChatRoom) 切到 v2(AVChatRoom) 的过渡期内, // 把已成功投递到 v2 群的全局/区域播报补发一份到 v1 旧群:老客户端在重新拉取 join 列表前 // 仍停留在旧群里。补发尽力而为,失败不影响主投递的成功状态。 @@ -616,6 +697,20 @@ func normalizeConfig(cfg Config) Config { if cfg.WorkerPollInterval <= 0 { cfg.WorkerPollInterval = time.Second } + seenApps := make(map[string]struct{}, len(cfg.AppCodes)) + normalizedApps := make([]string, 0, len(cfg.AppCodes)) + for _, value := range cfg.AppCodes { + if strings.TrimSpace(value) == "" { + continue + } + value = appcode.Normalize(value) + if _, exists := seenApps[value]; exists { + continue + } + seenApps[value] = struct{}{} + normalizedApps = append(normalizedApps, value) + } + cfg.AppCodes = normalizedApps return cfg } @@ -674,7 +769,9 @@ func normalizedPayloadJSON(input string, forced map[string]any) (string, error) } for key, value := range forced { if key == "region_id" && value.(int64) <= 0 { - // 全局播报不写 region_id,避免客户端误把 0 当作一个真实区域。 + // 全局播报必须删除调用方可能夹带的 region_id,避免客户端把伪造区域或 0 + // 当成真实路由维度;只跳过 forced 写入会保留原 payload 中的脏字段。 + delete(payload, "region_id") continue } // forced 字段覆盖调用方 payload,保证 event_id/scope/app_code/sent_at_ms 不被外部请求伪造。 diff --git a/services/activity-service/internal/service/broadcast/service_test.go b/services/activity-service/internal/service/broadcast/service_test.go index 9f8b78c9..26bb06bc 100644 --- a/services/activity-service/internal/service/broadcast/service_test.go +++ b/services/activity-service/internal/service/broadcast/service_test.go @@ -8,12 +8,77 @@ import ( "time" "google.golang.org/protobuf/proto" + luckygifteventsv1 "hyapp.local/api/proto/events/luckygift/v1" roomeventsv1 "hyapp.local/api/proto/events/room/v1" "hyapp/pkg/appcode" + "hyapp/pkg/imgroup" + "hyapp/pkg/luckygiftmq" "hyapp/pkg/tencentim" broadcastdomain "hyapp/services/activity-service/internal/domain/broadcast" ) +type crossAppBroadcastRepository struct { + Repository + claimed map[string]bool + deliveredApps []string +} + +func (r *crossAppBroadcastRepository) ListBroadcastOutboxAppCodes(context.Context) ([]string, error) { + return []string{"huwaa", "fami"}, nil +} + +func (r *crossAppBroadcastRepository) ClaimPendingBroadcastOutbox(ctx context.Context, _ string, _ int64, _ int64, _ int, _ int) ([]broadcastdomain.OutboxRecord, error) { + appCode := appcode.FromContext(ctx) + if r.claimed == nil { + r.claimed = map[string]bool{} + } + if r.claimed[appCode] { + return nil, nil + } + r.claimed[appCode] = true + return []broadcastdomain.OutboxRecord{{ + AppCode: appCode, + EventID: "cross-app:" + appCode, + Scope: broadcastdomain.ScopeRoom, + GroupID: "room-" + appCode, + BroadcastType: broadcastdomain.TypeLuckyGiftBigWin, + PayloadJSON: `{}`, + }}, nil +} + +func (r *crossAppBroadcastRepository) MarkBroadcastOutboxDelivered(ctx context.Context, _ string, _ int64) error { + r.deliveredApps = append(r.deliveredApps, appcode.FromContext(ctx)) + return nil +} + +func (r *crossAppBroadcastRepository) MarkBroadcastOutboxFailed(context.Context, string, string, int64, int64, int, string) error { + return nil +} + +func TestProcessPendingBroadcastsUsesEachRecordAppContext(t *testing.T) { + repository := &crossAppBroadcastRepository{} + service := New(Config{NodeID: "cross-app-broadcast"}, repository, &fakePublisher{}, nil) + service.SetClock(func() time.Time { return time.UnixMilli(1_700_000_000_000).UTC() }) + + result, err := service.ProcessPendingBroadcasts(context.Background(), WorkerOptions{WorkerID: "worker", BatchSize: 2, LockTTL: time.Second, MaxRetry: 8}) + if err != nil { + t.Fatalf("process cross-app broadcasts: %v", err) + } + if result.SuccessCount != 2 || len(repository.deliveredApps) != 2 { + t.Fatalf("cross-app broadcasts were not delivered: result=%+v apps=%v", result, repository.deliveredApps) + } + want := map[string]bool{"fami": true, "huwaa": true} + for _, appCode := range repository.deliveredApps { + if !want[appCode] { + t.Fatalf("unexpected delivered app context %q", appCode) + } + delete(want, appCode) + } + if len(want) != 0 { + t.Fatalf("missing delivered app contexts: %v", want) + } +} + func TestHandleRoomGiftSentCreatesRegionBroadcast(t *testing.T) { repository := newFakeRepository() service := New(Config{NodeID: "node-a", SuperGiftMinValue: 100}, repository, nil, nil) @@ -68,6 +133,129 @@ func TestHandleRoomGiftSentCreatesRegionBroadcast(t *testing.T) { } } +func TestHandleLuckyGiftEventCreatesTenTimesRegionBroadcastIdempotently(t *testing.T) { + repository := newFakeRepository() + service := New(Config{NodeID: "node-a"}, repository, nil, nil) + service.SetClock(func() time.Time { return time.UnixMilli(1_700_000_001_000) }) + // owner fact 已带送礼时展示快照;即使当前 user-service 不可用,也不应回查并阻断这条事实。 + service.SetSenderProfileSource(fakeSenderProfileSource{err: errors.New("user service unavailable")}) + envelope := mustLuckyGiftEnvelope(t, &luckygifteventsv1.LuckyGiftDrawn{ + DrawId: "lucky_draw_10x", + CommandId: "cmd-lucky-10x", + RoomId: "room-210", + PoolId: "super_lucky", + GiftId: "43", + GiftCount: 1, + SenderUserId: 42, + TargetUserId: 99, + SenderName: "Real Sender", + SenderAvatar: "https://cdn.example/sender.png", + SenderDisplayUserId: "160042", + SenderPrettyDisplayUserId: "888042", + VisibleRegionId: 210, + CountryId: 15, + CoinSpent: 100, + RuleVersion: 7, + ExperiencePool: "advanced", + SelectedTierId: "10x", + MultiplierPpm: luckygiftmq.LuckyGiftDisplayMinMultiplierPPM, + BaseRewardCoins: 1000, + EffectiveRewardCoins: 1000, + HighMultiplier: true, + RewardStatus: "granted", + WalletTransactionId: "wallet-tx-10x", + DrawCreatedAtMs: 1_700_000_000_000, + RewardGrantedAtMs: 1_700_000_000_500, + }) + + result, err := service.HandleLuckyGiftEvent(context.Background(), envelope) + if err != nil { + t.Fatalf("HandleLuckyGiftEvent failed: %v", err) + } + if result.Status != broadcastdomain.StatusPending || !result.BroadcastCreated || result.BroadcastEventID != "lucky_gift_big_win:lucky_draw_10x" { + t.Fatalf("unexpected lucky gift consume result: %+v", result) + } + record := repository.records[result.BroadcastEventID] + if record.GroupID != "hy_lalu_bc_r_v2_210" || record.BroadcastType != broadcastdomain.TypeLuckyGiftBigWin { + t.Fatalf("unexpected lucky gift broadcast record: %+v", record) + } + var payload map[string]any + if err := json.Unmarshal([]byte(record.PayloadJSON), &payload); err != nil { + t.Fatalf("lucky gift payload is not json: %v", err) + } + if payload["event_type"] != "lucky_gift_drawn" || payload["reward_status"] != "granted" || payload["sender_name"] != "Real Sender" || payload["sender_pretty_display_user_id"] != "888042" { + t.Fatalf("lucky gift payload identity/profile mismatch: %+v", payload) + } + if payload["room_id"] != "room-210" || payload["draw_id"] != "lucky_draw_10x" || payload["multiplier_ppm"] != float64(luckygiftmq.LuckyGiftDisplayMinMultiplierPPM) || payload["effective_reward_coins"] != float64(1000) || payload["sent_at_ms"] != float64(1_700_000_001_000) { + t.Fatalf("lucky gift payload reward mismatch: %+v", payload) + } + if _, exists := payload["gift_name"]; exists { + t.Fatalf("region payload must let Flutter resolve localized gift name: %+v", payload) + } + action, _ := payload["action"].(map[string]any) + if action["type"] != "enter_room" || action["room_id"] != "room-210" { + t.Fatalf("lucky gift action mismatch: %+v", action) + } + sender, _ := payload["sender"].(map[string]any) + if sender["nickname"] != "Real Sender" || sender["display_user_id"] != "160042" || sender["pretty_display_user_id"] != "888042" { + t.Fatalf("nested lucky gift sender mismatch: %+v", sender) + } + + again, err := service.HandleLuckyGiftEvent(context.Background(), envelope) + if err != nil { + t.Fatalf("duplicate HandleLuckyGiftEvent failed: %v", err) + } + if again.BroadcastCreated || len(repository.records) != 1 { + t.Fatalf("duplicate lucky owner fact must reuse one outbox: result=%+v records=%d", again, len(repository.records)) + } +} + +func TestHandleLuckyGiftEventSkipsNonBigWinFacts(t *testing.T) { + tests := []struct { + name string + mutate func(*luckygifteventsv1.LuckyGiftDrawn) + }{ + {name: "below ten times", mutate: func(event *luckygifteventsv1.LuckyGiftDrawn) { + event.MultiplierPpm = luckygiftmq.LuckyGiftDisplayMinMultiplierPPM - 1 + }}, + {name: "pending reward", mutate: func(event *luckygifteventsv1.LuckyGiftDrawn) { event.RewardStatus = "pending" }}, + {name: "zero reward", mutate: func(event *luckygifteventsv1.LuckyGiftDrawn) { event.EffectiveRewardCoins = 0 }}, + {name: "unknown region", mutate: func(event *luckygifteventsv1.LuckyGiftDrawn) { event.VisibleRegionId = 0 }}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + repository := newFakeRepository() + service := New(Config{NodeID: "node-a"}, repository, nil, nil) + event := validLuckyGiftDrawn() + test.mutate(event) + result, err := service.HandleLuckyGiftEvent(context.Background(), mustLuckyGiftEnvelope(t, event)) + if err != nil { + t.Fatalf("HandleLuckyGiftEvent failed: %v", err) + } + if result.Status != broadcastdomain.StatusSkipped || result.BroadcastEventID != "" || len(repository.records) != 0 { + t.Fatalf("non-big-win fact must be skipped: result=%+v records=%+v", result, repository.records) + } + }) + } +} + +func TestHandleLuckyGiftEventReturnsProfileFailureForMQRetry(t *testing.T) { + repository := newFakeRepository() + service := New(Config{NodeID: "node-a"}, repository, nil, nil) + service.SetSenderProfileSource(fakeSenderProfileSource{err: errors.New("user service timeout")}) + event := validLuckyGiftDrawn() + // 旧 pending outbox 可能没有展示快照;activity 必须回查资料,失败时不能确认 MQ 或写不完整播报。 + event.SenderName = "" + event.SenderDisplayUserId = "" + + if _, err := service.HandleLuckyGiftEvent(context.Background(), mustLuckyGiftEnvelope(t, event)); err == nil { + t.Fatal("missing snapshot with profile failure should return an error") + } + if len(repository.records) != 0 { + t.Fatalf("profile failure must not persist incomplete broadcast: %+v", repository.records) + } +} + func TestHandleRoomGiftSentCreatesRegionBroadcastForTaggedGiftBelowThreshold(t *testing.T) { repository := newFakeRepository() service := New(Config{NodeID: "node-a", SuperGiftMinValue: 1000}, repository, nil, nil) @@ -178,6 +366,54 @@ func TestProcessPendingBroadcastsPublishesCustomMessage(t *testing.T) { } } +func TestVIPOnlineNoticeUsesGlobalOutboxAndStableEnvelope(t *testing.T) { + repository := newFakeRepository() + publisher := &fakePublisher{} + service := New(Config{NodeID: "node-a"}, repository, publisher, nil) + service.SetClock(func() time.Time { return time.UnixMilli(1_800_000_000_000) }) + ctx := appcode.WithContext(context.Background(), "fami") + input := PublishInput{ + EventID: "vip_online_notice:42:digest", + BroadcastType: broadcastdomain.TypeVIPOnlineNotice, + PayloadJSON: `{"event_id":"forged","broadcast_type":"forged","scope":"region","app_code":"lalu","region_id":999,"user_id":42,"vip_level":9}`, + } + result, err := service.PublishGlobalBroadcast(ctx, input) + if err != nil { + t.Fatalf("PublishGlobalBroadcast failed: %v", err) + } + if !result.Created || result.GroupID != "hy_fami_bc_g_v2" || result.Status != broadcastdomain.StatusPending { + t.Fatalf("global publish result mismatch: %+v", result) + } + record := repository.records[input.EventID] + if record.Scope != broadcastdomain.ScopeGlobal || record.GroupID != "hy_fami_bc_g_v2" || record.BroadcastType != broadcastdomain.TypeVIPOnlineNotice { + t.Fatalf("global outbox mismatch: %+v", record) + } + var payload map[string]any + if err := json.Unmarshal([]byte(record.PayloadJSON), &payload); err != nil { + t.Fatalf("decode global payload: %v", err) + } + if payload["event_id"] != input.EventID || payload["broadcast_type"] != broadcastdomain.TypeVIPOnlineNotice || payload["scope"] != broadcastdomain.ScopeGlobal || payload["app_code"] != "fami" || payload["sent_at_ms"] != float64(1_800_000_000_000) { + t.Fatalf("forced global envelope mismatch: %+v", payload) + } + if _, exists := payload["region_id"]; exists { + t.Fatalf("global payload must not carry region_id: %+v", payload) + } + + // 同一个 event_id 的 HTTP 重试只返回已有 outbox,不能覆盖首个用户资料快照或重复发送。 + retry, err := service.PublishGlobalBroadcast(ctx, PublishInput{EventID: input.EventID, BroadcastType: broadcastdomain.TypeVIPOnlineNotice, PayloadJSON: `{"user_id":99}`}) + if err != nil || retry.Created || repository.records[input.EventID].PayloadJSON != record.PayloadJSON { + t.Fatalf("global retry must be idempotent: retry=%+v record=%+v err=%v", retry, repository.records[input.EventID], err) + } + processed, err := service.ProcessPendingBroadcasts(ctx, WorkerOptions{WorkerID: "worker-vip", BatchSize: 10}) + if err != nil || processed.SuccessCount != 1 || len(publisher.messages) != 2 { + // 全局 v2 成功后会向 v1 旧群补发一份,两个 TIMCustomElem 共享同一业务 payload。 + t.Fatalf("VIP online IM publish mismatch: result=%+v messages=%+v err=%v", processed, publisher.messages, err) + } + if publisher.messages[0].Desc != broadcastdomain.TypeVIPOnlineNotice || publisher.messages[0].Ext != "im_broadcast" || publisher.messages[0].EventID != input.EventID { + t.Fatalf("VIP online TIMCustomElem mismatch: %+v", publisher.messages[0]) + } +} + func TestConsumeRedPacketCreatedBuildsFullIMPayload(t *testing.T) { repository := newFakeRepository() service := New(Config{NodeID: "node-a"}, repository, nil, nil) @@ -333,6 +569,28 @@ func TestRemoveRegionBroadcastMemberDeletesOnlyUserMembership(t *testing.T) { } } +func TestRemoveRegionBroadcastMemberAcknowledgesAVChatKickRestriction(t *testing.T) { + publisher := &fakePublisher{deleteError: tencentim.RESTError{Code: 10007, Info: "this group disable kick member"}} + service := New(Config{NodeID: "node-a"}, newFakeRepository(), publisher, nil) + + groupID, removed, err := service.RemoveRegionBroadcastMember(appcode.WithContext(context.Background(), "lalu"), 42, 1001) + if err != nil { + t.Fatalf("AVChat kick restriction must be acknowledged without retry: %v", err) + } + if removed || groupID != "hy_lalu_bc_r_v2_1001" { + t.Fatalf("AVChat restriction result mismatch: group_id=%s removed=%t", groupID, removed) + } + if len(publisher.deletedGroupIDs) != 1 { + t.Fatalf("unsupported v2 removal must not retry the legacy AVChat group: %v", publisher.deletedGroupIDs) + } + + // 10007 还可能表示其他群业务错误;只能降级腾讯明确返回的 disable-kick 语义。 + publisher.deleteError = tencentim.RESTError{Code: 10007, Info: "another group error"} + if _, _, err := service.RemoveRegionBroadcastMember(appcode.WithContext(context.Background(), "lalu"), 42, 1001); err == nil { + t.Fatal("unrelated Tencent IM 10007 error must remain retryable") + } +} + func TestRegionBroadcastUsesConfiguredGroupPrefix(t *testing.T) { repository := newFakeRepository() publisher := &fakePublisher{} @@ -389,6 +647,33 @@ func TestEnsureBroadcastGroupsUsesAVChatRoomAndSurvivesGlobalFailure(t *testing. } } +func TestReconcileBroadcastGroupsIncludesConfiguredAppWithoutOutbox(t *testing.T) { + publisher := &fakePublisher{} + service := New(Config{NodeID: "multi-app-reconcile", AppCodes: []string{"lalu", "fami"}}, newFakeRepository(), publisher, fakeRegionSource{regionIDs: []int64{1001}}) + + service.reconcileBroadcastGroups(appcode.WithContext(context.Background(), "lalu"), WorkerOptions{WorkerID: "worker"}) + + wantGroups := map[string]bool{ + "hy_lalu_bc_g_v2": true, + "hy_lalu_bc_r_v2_1001": true, + "hy_fami_bc_g_v2": true, + "hy_fami_bc_r_v2_1001": true, + } + for index, spec := range publisher.ensuredGroups { + if !wantGroups[spec.GroupID] { + t.Fatalf("unexpected configured app group: %+v", spec) + } + delete(wantGroups, spec.GroupID) + parsed := imgroup.Parse(spec.GroupID) + if parsed.AppCode != publisher.ensuredAppCodes[index] { + t.Fatalf("group %s reconciled with wrong app context %s", spec.GroupID, publisher.ensuredAppCodes[index]) + } + } + if len(wantGroups) != 0 { + t.Fatalf("configured app groups were not reconciled: %+v", wantGroups) + } +} + func mustGiftEnvelope(t *testing.T, gift *roomeventsv1.RoomGiftSent) *roomeventsv1.EventEnvelope { t.Helper() body, err := proto.Marshal(gift) @@ -439,6 +724,47 @@ func redPacketPayloadData(t *testing.T, payloadJSON string) map[string]any { return payload.Data } +func validLuckyGiftDrawn() *luckygifteventsv1.LuckyGiftDrawn { + return &luckygifteventsv1.LuckyGiftDrawn{ + DrawId: "lucky_draw_valid", + CommandId: "cmd-lucky-valid", + RoomId: "room-210", + PoolId: "super_lucky", + GiftId: "43", + GiftCount: 1, + SenderUserId: 42, + TargetUserId: 99, + SenderName: "Real Sender", + SenderAvatar: "https://cdn.example/sender.png", + SenderDisplayUserId: "160042", + VisibleRegionId: 210, + CoinSpent: 100, + MultiplierPpm: luckygiftmq.LuckyGiftDisplayMinMultiplierPPM, + BaseRewardCoins: 1000, + EffectiveRewardCoins: 1000, + RewardStatus: "granted", + WalletTransactionId: "wallet-tx-valid", + DrawCreatedAtMs: 1_700_000_000_000, + RewardGrantedAtMs: 1_700_000_000_500, + } +} + +func mustLuckyGiftEnvelope(t *testing.T, event *luckygifteventsv1.LuckyGiftDrawn) *luckygifteventsv1.EventEnvelope { + t.Helper() + body, err := proto.Marshal(event) + if err != nil { + t.Fatalf("marshal lucky gift drawn: %v", err) + } + return &luckygifteventsv1.EventEnvelope{ + EventId: "lucky_gift_drawn:" + event.GetDrawId(), + EventType: luckygiftmq.EventTypeLuckyGiftDrawn, + AppCode: "lalu", + DrawId: event.GetDrawId(), + OccurredAtMs: event.GetDrawCreatedAtMs(), + Body: body, + } +} + func containsCustomMessageEvent(messages []tencentim.CustomGroupMessage, eventID string) bool { return findCustomMessageEvent(messages, eventID) != nil } @@ -517,13 +843,16 @@ func (r *fakeRepository) MarkBroadcastOutboxFailed(_ context.Context, eventID st type fakePublisher struct { messages []tencentim.CustomGroupMessage ensuredGroups []tencentim.GroupSpec + ensuredAppCodes []string ensureErrors map[string]error deletedGroupIDs []string deletedUserID int64 + deleteError error } -func (p *fakePublisher) EnsureGroup(_ context.Context, spec tencentim.GroupSpec) error { +func (p *fakePublisher) EnsureGroup(ctx context.Context, spec tencentim.GroupSpec) error { p.ensuredGroups = append(p.ensuredGroups, spec) + p.ensuredAppCodes = append(p.ensuredAppCodes, appcode.FromContext(ctx)) return p.ensureErrors[spec.GroupID] } @@ -535,7 +864,7 @@ func (p *fakePublisher) PublishGroupCustomMessage(_ context.Context, message ten func (p *fakePublisher) DeleteGroupMember(_ context.Context, groupID string, userID int64) error { p.deletedGroupIDs = append(p.deletedGroupIDs, groupID) p.deletedUserID = userID - return nil + return p.deleteError } type fakeRegionSource struct { diff --git a/services/activity-service/internal/service/broadcast/worker.go b/services/activity-service/internal/service/broadcast/worker.go index d9213912..a9751c81 100644 --- a/services/activity-service/internal/service/broadcast/worker.go +++ b/services/activity-service/internal/service/broadcast/worker.go @@ -5,6 +5,7 @@ import ( "log/slog" "time" + "hyapp/pkg/appcode" "hyapp/pkg/logx" ) @@ -40,7 +41,29 @@ func (s *Service) RunWorker(ctx context.Context, options WorkerOptions) { } func (s *Service) reconcileBroadcastGroups(ctx context.Context, options WorkerOptions) { - if _, err := s.EnsureBroadcastGroups(ctx); err != nil && ctx.Err() == nil { - logx.Error(ctx, "broadcast_group_reconcile_failed", err, slog.String("worker_id", options.WorkerID)) + appCodes, err := s.broadcastOutboxAppCodes(ctx) + if err != nil { + if ctx.Err() == nil { + logx.Error(ctx, "broadcast_group_reconcile_apps_failed", err, slog.String("worker_id", options.WorkerID)) + } + return + } + // 没有历史 outbox 的当前 App 仍需要全局/区域群;其他 App 从实际 outbox 租户键发现,避免后台 worker 永远只维护默认 lalu。 + currentApp := appcode.FromContext(ctx) + foundCurrent := false + for _, appCode := range appCodes { + if appCode == currentApp { + foundCurrent = true + break + } + } + if !foundCurrent { + appCodes = append(appCodes, currentApp) + } + for _, appCode := range appCodes { + appCtx := appcode.WithContext(ctx, appCode) + if _, err := s.EnsureBroadcastGroups(appCtx); err != nil && ctx.Err() == nil { + logx.Error(appCtx, "broadcast_group_reconcile_failed", err, slog.String("worker_id", options.WorkerID), slog.String("app_code", appCode)) + } } } diff --git a/services/activity-service/internal/service/growth/service.go b/services/activity-service/internal/service/growth/service.go index 79e02f26..54e668f2 100644 --- a/services/activity-service/internal/service/growth/service.go +++ b/services/activity-service/internal/service/growth/service.go @@ -2,8 +2,10 @@ package growth import ( "context" + "crypto/sha256" "encoding/json" "fmt" + "sort" "strconv" "strings" "time" @@ -15,6 +17,7 @@ import ( "hyapp/pkg/appcode" "hyapp/pkg/xerr" domain "hyapp/services/activity-service/internal/domain/growth" + messageservice "hyapp/services/activity-service/internal/service/message" ) const levelRewardReason = "growth_level_reward" @@ -38,29 +41,53 @@ type Repository interface { ListLevelRewards(ctx context.Context, query domain.RewardQuery) ([]domain.RewardJob, int64, error) ConsumeLevelEvent(ctx context.Context, event domain.ValueEvent, nowMS int64) (domain.EventResult, error) SetUserLevel(ctx context.Context, command domain.SetUserLevelCommand, nowMS int64) (domain.SetUserLevelResult, error) + BatchGetUserLevelAdminProfiles(ctx context.Context, userIDs []int64, nowMS int64) (domain.AdminLevelProfiles, error) + AdjustTemporaryUserLevels(ctx context.Context, command domain.AdjustTemporaryLevelsCommand, nowMS int64) (domain.AdminUserLevelProfile, error) UpsertLevelTrack(ctx context.Context, command domain.TrackCommand, nowMS int64) (domain.Track, bool, error) UpsertLevelRule(ctx context.Context, command domain.RuleCommand, nowMS int64) (domain.Rule, bool, error) UpsertLevelTier(ctx context.Context, command domain.TierCommand, nowMS int64) (domain.Tier, bool, error) ClaimPendingLevelRewardJobs(ctx context.Context, workerID string, nowMS int64, lockTTL time.Duration, batchSize int) ([]domain.RewardJob, error) - MarkLevelRewardGranted(ctx context.Context, rewardJobID string, walletGrantID string, nowMS int64) error - MarkLevelRewardFailed(ctx context.Context, rewardJobID string, failureReason string, nextRetryAtMS int64, nowMS int64) error + MarkLevelRewardGranted(ctx context.Context, rewardJobID string, grants []domain.RewardWalletGrant, nowMS int64) error + MarkLevelRewardFailed(ctx context.Context, rewardJobID string, grants []domain.RewardWalletGrant, failureReason string, nextRetryAtMS int64, nowMS int64) error + ClaimTemporaryLevelWork(ctx context.Context, workerID string, nowMS int64, lockTTL time.Duration, batchSize int) ([]domain.TemporaryLevelWork, error) + MarkTemporaryLevelNotices(ctx context.Context, temporaryLevelID string, activationStatus string, expiryStatus string, failureReason string, nextRetryAtMS int64, nowMS int64) error + ClaimLevelRewardRevocations(ctx context.Context, workerID string, nowMS int64, lockTTL time.Duration, batchSize int) ([]domain.RewardRevocation, error) + MarkLevelRewardRevoked(ctx context.Context, rewardJobID string, walletGrantID string, nowMS int64) error + MarkLevelRewardRevokeFailed(ctx context.Context, rewardJobID string, walletGrantID string, failureReason string, nextRetryAtMS int64, nowMS int64) error } // WalletClient 是等级奖励发放的唯一外部副作用。 type WalletClient interface { GrantResource(ctx context.Context, req *walletv1.GrantResourceRequest, opts ...grpc.CallOption) (*walletv1.ResourceGrantResponse, error) GrantResourceGroup(ctx context.Context, req *walletv1.GrantResourceGroupRequest, opts ...grpc.CallOption) (*walletv1.ResourceGrantResponse, error) + RevokeResourceGrant(ctx context.Context, req *walletv1.RevokeResourceGrantRequest, opts ...grpc.CallOption) (*walletv1.ResourceGrantResponse, error) +} + +// NoticeService 只调用 message service 的公开 helper;growth 不直接写 inbox 表。 +type NoticeService interface { + CreateSystemNotice(ctx context.Context, cmd messageservice.NoticeCommand) (messageservice.NoticeResult, error) +} + +type Option func(*Service) + +func WithNoticeService(notices NoticeService) Option { + return func(service *Service) { service.notices = notices } } // Service 承载 App 查询、事件消费、后台配置和等级奖励补偿。 type Service struct { repository Repository wallet WalletClient + notices NoticeService now func() time.Time } -func New(repository Repository, wallet WalletClient) *Service { - return &Service{repository: repository, wallet: wallet, now: time.Now} +func New(repository Repository, wallet WalletClient, options ...Option) *Service { + service := &Service{repository: repository, wallet: wallet, now: time.Now} + for _, option := range options { + option(service) + } + return service } func (s *Service) SetClock(now func() time.Time) { @@ -112,6 +139,65 @@ func (s *Service) BatchGetUserLevelDisplayProfiles(ctx context.Context, userIDs return s.repository.BatchGetUserLevelDisplayProfiles(ctx, normalized, s.now().UnixMilli()) } +// BatchGetUserLevelAdminProfiles 返回真实账户和当前有效 overlay;到期毫秒由 repository 读取条件立即排除, +// 所以后台展示不会依赖 cron 是否已经把记录推进为 expired。 +func (s *Service) BatchGetUserLevelAdminProfiles(ctx context.Context, userIDs []int64) (domain.AdminLevelProfiles, error) { + if err := s.requireRepository(); err != nil { + return domain.AdminLevelProfiles{}, err + } + normalized := uniquePositiveUserIDs(userIDs) + if len(normalized) == 0 { + return domain.AdminLevelProfiles{Profiles: []domain.AdminUserLevelProfile{}, ServerTimeMS: s.now().UnixMilli()}, nil + } + if len(normalized) > 500 { + return domain.AdminLevelProfiles{}, xerr.New(xerr.InvalidArgument, "too many user ids; maximum is 500") + } + return s.repository.BatchGetUserLevelAdminProfiles(ctx, normalized, s.now().UnixMilli()) +} + +// AdjustTemporaryUserLevels 校验整批命令后只调用一次 repository,保证 wealth/charm 要么一起生效、要么一起回滚。 +func (s *Service) AdjustTemporaryUserLevels(ctx context.Context, command domain.AdjustTemporaryLevelsCommand) (domain.AdminUserLevelProfile, error) { + if err := s.requireRepository(); err != nil { + return domain.AdminUserLevelProfile{}, err + } + command.CommandID = strings.TrimSpace(command.CommandID) + command.Reason = strings.TrimSpace(command.Reason) + if command.CommandID == "" || command.UserID <= 0 || command.OperatorAdminID <= 0 || len(command.Adjustments) == 0 || len(command.Adjustments) > 2 { + return domain.AdminUserLevelProfile{}, xerr.New(xerr.InvalidArgument, "temporary level command is incomplete") + } + seen := make(map[string]struct{}, len(command.Adjustments)) + for index := range command.Adjustments { + item := &command.Adjustments[index] + item.Track = normalizeTrack(item.Track) + if item.Track != domain.TrackWealth && item.Track != domain.TrackCharm { + return domain.AdminUserLevelProfile{}, xerr.New(xerr.InvalidArgument, "temporary level track must be wealth or charm") + } + if item.Level < 1 || item.Level > 50 || item.DurationDays <= 0 { + return domain.AdminUserLevelProfile{}, xerr.New(xerr.InvalidArgument, "temporary level must be 1..50 and duration_days must be positive") + } + if _, exists := seen[item.Track]; exists { + return domain.AdminUserLevelProfile{}, xerr.New(xerr.InvalidArgument, "temporary level track is duplicated") + } + seen[item.Track] = struct{}{} + } + // 排序让同一业务载荷生成稳定 hash,也让 repository 以固定轨道顺序加锁,降低双轨并发死锁概率。 + sort.Slice(command.Adjustments, func(i, j int) bool { return command.Adjustments[i].Track < command.Adjustments[j].Track }) + if command.Reason == "" { + command.Reason = "admin_temporary_level" + } + hashPayload, _ := json.Marshal(struct { + UserID int64 `json:"user_id"` + Adjustments []domain.TemporaryLevelAdjustment `json:"adjustments"` + OperatorAdminID int64 `json:"operator_admin_id"` + Reason string `json:"reason"` + }{ + UserID: command.UserID, Adjustments: command.Adjustments, + OperatorAdminID: command.OperatorAdminID, Reason: command.Reason, + }) + command.RequestHash = fmt.Sprintf("%x", sha256.Sum256(hashPayload)) + return s.repository.AdjustTemporaryUserLevels(ctx, command, s.now().UTC().UnixMilli()) +} + func (s *Service) IssueRegistrationLevelBadges(ctx context.Context, userID int64, commandID string) (domain.RegistrationLevelBadgeGrants, error) { if err := s.requireRepository(); err != nil { return domain.RegistrationLevelBadgeGrants{}, err @@ -386,14 +472,16 @@ func (s *Service) ProcessLevelRewardBatch(ctx context.Context, runID string, wor } for _, job := range jobs { processed++ - walletGrantID, grantErr := s.grantLevelRewardJob(ctx, job) + walletGrants, grantErr := s.grantLevelRewardJob(ctx, job) if grantErr != nil { failure++ nextRetry := s.now().Add(time.Minute).UnixMilli() - _ = s.repository.MarkLevelRewardFailed(ctx, job.RewardJobID, xerr.MessageOf(grantErr), nextRetry, s.now().UnixMilli()) + // One reward job may span several idempotent wallet commands. Persist every successful prefix even when a + // later command fails, otherwise an overlay expiry cannot discover and revoke the already-issued asset. + _ = s.repository.MarkLevelRewardFailed(ctx, job.RewardJobID, walletGrants, xerr.MessageOf(grantErr), nextRetry, s.now().UnixMilli()) continue } - if markErr := s.repository.MarkLevelRewardGranted(ctx, job.RewardJobID, walletGrantID, s.now().UnixMilli()); markErr != nil { + if markErr := s.repository.MarkLevelRewardGranted(ctx, job.RewardJobID, walletGrants, s.now().UnixMilli()); markErr != nil { failure++ continue } @@ -402,8 +490,9 @@ func (s *Service) ProcessLevelRewardBatch(ctx context.Context, runID string, wor return int32(len(jobs)), processed, success, failure, len(jobs) == batchSize, nil } -func (s *Service) grantLevelRewardJob(ctx context.Context, job domain.RewardJob) (string, error) { - walletGrantID := "" +func (s *Service) grantLevelRewardJob(ctx context.Context, job domain.RewardJob) ([]domain.RewardWalletGrant, error) { + grants := make([]domain.RewardWalletGrant, 0, 3) + nowMS := s.now().UnixMilli() if job.ResourceGroupID > 0 { // 资源组奖励沿用原有配置快照;command_id 后缀把组奖励和单资源等级物料奖励拆成多个钱包幂等动作。 resp, err := s.wallet.GrantResourceGroup(ctx, &walletv1.GrantResourceGroupRequest{ @@ -416,16 +505,19 @@ func (s *Service) grantLevelRewardJob(ctx context.Context, job domain.RewardJob) GrantSource: domain.GrantSourceGrowthLevel, }) if err != nil { - return "", err + return grants, err } if resp.GetGrant() != nil { - walletGrantID = resp.GetGrant().GetGrantId() + grants = append(grants, domain.RewardWalletGrant{RewardJobID: job.RewardJobID, GrantKind: "resource_group", WalletGrantID: resp.GetGrant().GetGrantId(), CreatedAtMS: nowMS}) } } levelResources, err := s.levelResourceIDsForRewardJob(ctx, job) if err != nil { - return "", err + return grants, err } + durationMS := levelAvatarFrameRewardDurationMS + // 临时直发物料也按自然奖励的长有效期授予,并由记录到子表的 grant 在 overlay 到期时显式撤回。 + // 这样用户在有效期内真实达到该等级后只需把任务提升为永久并跳过撤回,钱包权益不会仍按临时截止时间消失。 if levelResources.avatarFrameResourceID > 0 { // 等级头像框是每级固定物料,不依赖运营额外创建资源组;按产品要求直接发 9999 天权益。 resp, err := s.wallet.GrantResource(ctx, &walletv1.GrantResourceRequest{ @@ -434,16 +526,16 @@ func (s *Service) grantLevelRewardJob(ctx context.Context, job domain.RewardJob) TargetUserId: job.UserID, ResourceId: levelResources.avatarFrameResourceID, Quantity: 1, - DurationMs: levelAvatarFrameRewardDurationMS, + DurationMs: durationMS, Reason: levelRewardReason, OperatorUserId: job.UserID, GrantSource: domain.GrantSourceGrowthLevel, }) if err != nil { - return "", err + return grants, err } - if walletGrantID == "" && resp.GetGrant() != nil { - walletGrantID = resp.GetGrant().GetGrantId() + if resp.GetGrant() != nil { + grants = append(grants, domain.RewardWalletGrant{RewardJobID: job.RewardJobID, GrantKind: "avatar_frame", WalletGrantID: resp.GetGrant().GetGrantId(), CreatedAtMS: nowMS}) } } if levelResources.shortBadgeResourceID > 0 { @@ -454,19 +546,140 @@ func (s *Service) grantLevelRewardJob(ctx context.Context, job domain.RewardJob) TargetUserId: job.UserID, ResourceId: levelResources.shortBadgeResourceID, Quantity: 1, - DurationMs: levelAvatarFrameRewardDurationMS, + DurationMs: durationMS, Reason: levelRewardReason, OperatorUserId: job.UserID, GrantSource: domain.GrantSourceGrowthLevel, }) if err != nil { - return "", err + return grants, err } - if walletGrantID == "" && resp.GetGrant() != nil { - walletGrantID = resp.GetGrant().GetGrantId() + if resp.GetGrant() != nil { + grants = append(grants, domain.RewardWalletGrant{RewardJobID: job.RewardJobID, GrantKind: "short_badge", WalletGrantID: resp.GetGrant().GetGrantId(), CreatedAtMS: nowMS}) } } - return walletGrantID, nil + return grants, nil +} + +// ProcessTemporaryLevelBatch 推进过期状态、发送幂等系统消息并撤回不再应得的临时奖励。 +// 状态准备由 repository 事务完成,外部副作用失败只回写 retry_at,不会重新污染真实成长账户。 +func (s *Service) ProcessTemporaryLevelBatch(ctx context.Context, workerID string, batchSize int, lockTTL time.Duration) (claimed int32, processed int32, success int32, failure int32, hasMore bool, err error) { + if err := s.requireRepository(); err != nil { + return 0, 0, 0, 0, false, err + } + if s.wallet == nil || s.notices == nil { + return 0, 0, 0, 0, false, xerr.New(xerr.Unavailable, "temporary level side effects are not configured") + } + if batchSize <= 0 { + batchSize = 100 + } + if lockTTL <= 0 { + lockTTL = 30 * time.Second + } + if strings.TrimSpace(workerID) == "" { + workerID = "activity-temporary-level" + } + nowMS := s.now().UTC().UnixMilli() + works, err := s.repository.ClaimTemporaryLevelWork(ctx, workerID, nowMS, lockTTL, batchSize) + if err != nil { + return 0, 0, 0, 0, false, err + } + for _, work := range works { + processed++ + activationStatus, expiryStatus := "", "" + var workErr error + if work.SendActivationNotice { + workErr = s.sendTemporaryLevelNotice(ctx, work, false, nowMS) + if workErr == nil { + activationStatus = domain.NoticeStatusSent + } else { + activationStatus = domain.NoticeStatusFailed + } + } + if workErr == nil && work.SendExpiryNotice { + workErr = s.sendTemporaryLevelNotice(ctx, work, true, nowMS) + if workErr == nil { + expiryStatus = domain.NoticeStatusSent + } else { + expiryStatus = domain.NoticeStatusFailed + } + } + failureReason := "" + nextRetryAtMS := int64(0) + if workErr != nil { + failure++ + failureReason = xerr.MessageOf(workErr) + nextRetryAtMS = s.now().Add(time.Minute).UnixMilli() + } else { + success++ + } + if markErr := s.repository.MarkTemporaryLevelNotices(ctx, work.TemporaryLevelID, activationStatus, expiryStatus, failureReason, nextRetryAtMS, s.now().UnixMilli()); markErr != nil { + failure++ + if workErr == nil && success > 0 { + success-- + } + } + } + remaining := batchSize - len(works) + revocations := []domain.RewardRevocation{} + if remaining > 0 { + revocations, err = s.repository.ClaimLevelRewardRevocations(ctx, workerID, s.now().UnixMilli(), lockTTL, remaining) + if err != nil { + return int32(len(works)), processed, success, failure, false, err + } + } + for _, revoke := range revocations { + processed++ + _, revokeErr := s.wallet.RevokeResourceGrant(ctx, &walletv1.RevokeResourceGrantRequest{ + RequestId: fmt.Sprintf("temporary-level-revoke:%s:%s", revoke.RewardJobID, revoke.WalletGrantID), + AppCode: appcode.FromContext(ctx), + GrantId: revoke.WalletGrantID, + Reason: "temporary_level_expired", + OperatorUserId: revoke.UserID, + }) + if revokeErr != nil { + failure++ + _ = s.repository.MarkLevelRewardRevokeFailed(ctx, revoke.RewardJobID, revoke.WalletGrantID, xerr.MessageOf(revokeErr), s.now().Add(time.Minute).UnixMilli(), s.now().UnixMilli()) + continue + } + if markErr := s.repository.MarkLevelRewardRevoked(ctx, revoke.RewardJobID, revoke.WalletGrantID, s.now().UnixMilli()); markErr != nil { + failure++ + continue + } + success++ + } + claimed = int32(len(works) + len(revocations)) + hasMore = len(works) == batchSize || (remaining > 0 && len(revocations) == remaining) + return claimed, processed, success, failure, hasMore, nil +} + +func (s *Service) sendTemporaryLevelNotice(ctx context.Context, work domain.TemporaryLevelWork, expired bool, nowMS int64) error { + trackName := map[string]string{domain.TrackWealth: "富豪", domain.TrackCharm: "魅力"}[work.Track] + body := fmt.Sprintf("尊敬的用户,官方已将您%s等级临时升级为Lv%d,有效期%d天,请在有效期内继续升级哦", trackName, work.TargetLevel, (work.ExpiresAtMS-work.StartedAtMS)/(24*60*60*1000)) + eventSuffix := "activated" + eventType := "temporary_level_activated" + if expired { + body = fmt.Sprintf("您的临时%s等级Lv%d已到期,根据您在有效期内的经验值,现恢复为Lv%d", trackName, work.TargetLevel, work.FinalLevel) + eventSuffix = "expired" + eventType = "temporary_level_expired" + } + _, err := s.notices.CreateSystemNotice(ctx, messageservice.NoticeCommand{ + TargetUserID: work.UserID, + Producer: "growth-level", + ProducerEventID: fmt.Sprintf("temporary-level:%s:%s", work.TemporaryLevelID, eventSuffix), + ProducerEventType: eventType, + AggregateType: "temporary_growth_level", + AggregateID: work.TemporaryLevelID, + Title: "系统通知", + Summary: body, + Body: body, + SentAtMS: nowMS, + Metadata: map[string]any{ + "track": work.Track, "target_level": work.TargetLevel, "final_level": work.FinalLevel, + "started_at_ms": work.StartedAtMS, "expires_at_ms": work.ExpiresAtMS, + }, + }) + return err } type levelRewardResourceIDs struct { @@ -642,3 +855,19 @@ func validMetricForTrack(track string, metric string) bool { return false } } + +func uniquePositiveUserIDs(values []int64) []int64 { + result := make([]int64, 0, len(values)) + seen := make(map[int64]struct{}, len(values)) + for _, value := range values { + if value <= 0 { + continue + } + if _, exists := seen[value]; exists { + continue + } + seen[value] = struct{}{} + result = append(result, value) + } + return result +} diff --git a/services/activity-service/internal/service/growth/service_test.go b/services/activity-service/internal/service/growth/service_test.go index 7fc0e132..ff9a93d9 100644 --- a/services/activity-service/internal/service/growth/service_test.go +++ b/services/activity-service/internal/service/growth/service_test.go @@ -13,6 +13,7 @@ import ( "hyapp/pkg/appcode" domain "hyapp/services/activity-service/internal/domain/growth" growthservice "hyapp/services/activity-service/internal/service/growth" + messageservice "hyapp/services/activity-service/internal/service/message" "hyapp/services/activity-service/internal/testutil/mysqltest" ) @@ -88,6 +89,595 @@ func TestGrowthLevelEventRewardFlow(t *testing.T) { } } +func TestTemporaryGrowthLevelOverlayExpiresAndRevokesAllWalletGrants(t *testing.T) { + repository := mysqltest.NewRepository(t) + wallet := &fakeGrowthWallet{} + notices := &fakeGrowthNoticeService{} + now := fixedGrowthNow() + svc := growthservice.New(repository, wallet, growthservice.WithNoticeService(notices)) + svc.SetClock(func() time.Time { return now }) + ctx := appcode.WithContext(context.Background(), "lalu") + seedGrowthRules(t, ctx, svc, domain.TrackWealth) + if _, created, err := svc.UpsertLevelRule(ctx, domain.RuleCommand{ + Track: domain.TrackWealth, Level: 2, RequiredValue: 200, Name: "wealth 2", Status: domain.StatusActive, + RewardResourceGroupID: 9002, SortOrder: 2, + DisplayConfigJSON: `{"long_badge_resource_id":702,"avatar_frame_resource_id":802,"short_badge_resource_id":902}`, + OperatorAdminID: 90001, + }); err != nil || created { + t.Fatalf("update temporary reward resources failed: created=%v err=%v", created, err) + } + + profile, err := svc.AdjustTemporaryUserLevels(ctx, domain.AdjustTemporaryLevelsCommand{ + CommandID: "temporary-level-command-1", UserID: 51001, OperatorAdminID: 90001, + Adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackWealth, Level: 2, DurationDays: 7}}, + }) + if err != nil { + t.Fatalf("AdjustTemporaryUserLevels failed: %v", err) + } + wealth := findAdminTrack(profile.Tracks, domain.TrackWealth) + if wealth.RealLevel != 0 || wealth.DisplayLevel != 2 || wealth.DisplayValue != 200 || wealth.TemporaryLevelID == "" { + t.Fatalf("temporary profile mismatch: %+v", wealth) + } + // Same command and payload is a read-only idempotent replay; it must not create a second generation. + replayed, err := svc.AdjustTemporaryUserLevels(ctx, domain.AdjustTemporaryLevelsCommand{ + CommandID: "temporary-level-command-1", UserID: 51001, OperatorAdminID: 90001, + Adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackWealth, Level: 2, DurationDays: 7}}, + }) + if err != nil || findAdminTrack(replayed.Tracks, domain.TrackWealth).TemporaryLevelID != wealth.TemporaryLevelID { + t.Fatalf("temporary command replay mismatch: profile=%+v err=%v", replayed, err) + } + if _, err := svc.AdjustTemporaryUserLevels(ctx, domain.AdjustTemporaryLevelsCommand{ + CommandID: "temporary-level-command-1", UserID: 51001, OperatorAdminID: 90001, Reason: "changed-payload", + Adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackWealth, Level: 2, DurationDays: 7}}, + }); err == nil { + t.Fatal("same command id with a changed reason must be rejected as an idempotency conflict") + } + + if _, _, success, failure, _, err := svc.ProcessTemporaryLevelBatch(ctx, "worker-notice", 10, time.Second); err != nil || success != 1 || failure != 0 || len(notices.commands) != 1 { + t.Fatalf("activation notice batch mismatch: success=%d failure=%d notices=%d err=%v", success, failure, len(notices.commands), err) + } + if claimed, _, success, failure, _, err := svc.ProcessLevelRewardBatch(ctx, "run-temp-reward", "worker-reward", 10, time.Second); err != nil || claimed != 3 || success != 3 || failure != 0 { + t.Fatalf("temporary reward batch mismatch: claimed=%d success=%d failure=%d err=%v", claimed, success, failure, err) + } + for _, grant := range wallet.resourceGrants { + if grant.GetDurationMs() != 9999*24*time.Hour.Milliseconds() { + t.Fatalf("temporary direct resource must rely on explicit revoke instead of wallet expiry: %+v", grant) + } + } + + // The end instant is exclusive: before cron runs, every read must already ignore the overlay. + now = now.Add(7 * 24 * time.Hour) + profiles, err := svc.BatchGetUserLevelAdminProfiles(ctx, []int64{51001}) + if err != nil { + t.Fatalf("BatchGetUserLevelAdminProfiles at expiry failed: %v", err) + } + atExpiry := findAdminTrack(profiles.Profiles[0].Tracks, domain.TrackWealth) + if atExpiry.DisplayLevel != 0 || atExpiry.TemporaryLevelID != "" { + t.Fatalf("expired overlay must be ignored immediately: %+v", atExpiry) + } + claimed, processed, success, failure, _, err := svc.ProcessTemporaryLevelBatch(ctx, "worker-expiry", 20, time.Second) + if err != nil || claimed != 6 || processed != 6 || success != 6 || failure != 0 { + t.Fatalf("expiry batch mismatch: claimed=%d processed=%d success=%d failure=%d err=%v", claimed, processed, success, failure, err) + } + if len(wallet.revokes) != 5 || len(notices.commands) != 2 { + t.Fatalf("expiry side effects mismatch: revokes=%d notices=%d", len(wallet.revokes), len(notices.commands)) + } +} + +func TestTemporaryGrowthLevelExpiredGenerationIsMaterializedBeforeNewAdjustment(t *testing.T) { + repository := mysqltest.NewRepository(t) + wallet := &fakeGrowthWallet{} + notices := &fakeGrowthNoticeService{} + now := fixedGrowthNow() + svc := growthservice.New(repository, wallet, growthservice.WithNoticeService(notices)) + svc.SetClock(func() time.Time { return now }) + ctx := appcode.WithContext(context.Background(), "lalu") + seedGrowthRules(t, ctx, svc, domain.TrackWealth) + if _, created, err := svc.UpsertLevelRule(ctx, domain.RuleCommand{ + Track: domain.TrackWealth, Level: 3, RequiredValue: 300, Name: "wealth 3", Status: domain.StatusActive, + SortOrder: 3, DisplayConfigJSON: `{}`, OperatorAdminID: 90001, + }); err != nil || !created { + t.Fatalf("seed level 3 failed: created=%v err=%v", created, err) + } + first, err := svc.AdjustTemporaryUserLevels(ctx, domain.AdjustTemporaryLevelsCommand{ + CommandID: "temporary-expired-before-replace-1", UserID: 51007, OperatorAdminID: 90001, + Adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackWealth, Level: 2, DurationDays: 1}}, + }) + if err != nil { + t.Fatalf("create first temporary generation failed: %v", err) + } + firstID := findAdminTrack(first.Tracks, domain.TrackWealth).TemporaryLevelID + now = now.Add(24 * time.Hour) + second, err := svc.AdjustTemporaryUserLevels(ctx, domain.AdjustTemporaryLevelsCommand{ + CommandID: "temporary-expired-before-replace-2", UserID: 51007, OperatorAdminID: 90001, + Adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackWealth, Level: 3, DurationDays: 1}}, + }) + if err != nil { + t.Fatalf("create second temporary generation failed: %v", err) + } + current := findAdminTrack(second.Tracks, domain.TrackWealth) + if current.TemporaryLevelID == "" || current.TemporaryLevelID == firstID || current.DisplayLevel != 3 { + t.Fatalf("new generation must replace only after old expiry is materialized: first=%s current=%+v", firstID, current) + } + claimed, processed, success, failure, _, err := svc.ProcessTemporaryLevelBatch(ctx, "worker-expired-replace", 10, time.Second) + if err != nil || claimed != 2 || processed != 2 || success != 2 || failure != 0 || len(notices.commands) != 2 { + t.Fatalf("old expiry and new activation must both remain retryable: claimed=%d processed=%d success=%d failure=%d notices=%d err=%v", claimed, processed, success, failure, len(notices.commands), err) + } +} + +func TestTemporaryGrowthWorkersReclaimExpiredRunningLocks(t *testing.T) { + repository := mysqltest.NewRepository(t) + wallet := &fakeGrowthWallet{} + now := fixedGrowthNow() + svc := growthservice.New(repository, wallet, growthservice.WithNoticeService(&fakeGrowthNoticeService{})) + svc.SetClock(func() time.Time { return now }) + ctx := appcode.WithContext(context.Background(), "lalu") + seedGrowthRules(t, ctx, svc, domain.TrackWealth) + if _, err := svc.AdjustTemporaryUserLevels(ctx, domain.AdjustTemporaryLevelsCommand{ + CommandID: "temporary-reward-reclaim", UserID: 51008, OperatorAdminID: 90001, + Adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackWealth, Level: 2, DurationDays: 1}}, + }); err != nil { + t.Fatalf("seed reward reclaim generation failed: %v", err) + } + firstJobs, err := repository.ClaimPendingLevelRewardJobs(ctx, "crashed-reward-worker", now.UnixMilli(), time.Second, 20) + if err != nil || len(firstJobs) == 0 { + t.Fatalf("first reward claim failed: jobs=%d err=%v", len(firstJobs), err) + } + reclaimedJobs, err := repository.ClaimPendingLevelRewardJobs(ctx, "replacement-reward-worker", now.Add(time.Second).UnixMilli(), time.Second, 20) + if err != nil || len(reclaimedJobs) != len(firstJobs) { + t.Fatalf("expired running reward locks must be reclaimed: first=%d reclaimed=%d err=%v", len(firstJobs), len(reclaimedJobs), err) + } + + // A separate generation is fully granted so revocation child rows can exercise the same crash-recovery boundary. + if _, err := svc.AdjustTemporaryUserLevels(ctx, domain.AdjustTemporaryLevelsCommand{ + CommandID: "temporary-revoke-reclaim", UserID: 51009, OperatorAdminID: 90001, + Adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackWealth, Level: 2, DurationDays: 1}}, + }); err != nil { + t.Fatalf("seed revoke reclaim generation failed: %v", err) + } + if claimed, _, success, failure, _, err := svc.ProcessLevelRewardBatch(ctx, "revoke-reclaim-grant-run", "reward-worker", 20, time.Second); err != nil || claimed == 0 || success != claimed || failure != 0 { + t.Fatalf("grant revocation seed jobs failed: claimed=%d success=%d failure=%d err=%v", claimed, success, failure, err) + } + expiresAt := now.Add(24 * time.Hour) + if _, err := repository.ClaimTemporaryLevelWork(ctx, "expiry-materializer", expiresAt.UnixMilli(), time.Second, 20); err != nil { + t.Fatalf("materialize temporary expiry failed: %v", err) + } + firstRevokes, err := repository.ClaimLevelRewardRevocations(ctx, "crashed-revoke-worker", expiresAt.UnixMilli(), time.Second, 100) + if err != nil || len(firstRevokes) == 0 { + t.Fatalf("first revoke claim failed: revokes=%d err=%v", len(firstRevokes), err) + } + reclaimedRevokes, err := repository.ClaimLevelRewardRevocations(ctx, "replacement-revoke-worker", expiresAt.Add(time.Second).UnixMilli(), time.Second, 100) + if err != nil || len(reclaimedRevokes) != len(firstRevokes) { + t.Fatalf("expired running revoke locks must be reclaimed: first=%d reclaimed=%d err=%v", len(firstRevokes), len(reclaimedRevokes), err) + } +} + +func TestTemporaryRewardsBecomePermanentWhenRealLevelCatchesUp(t *testing.T) { + repository := mysqltest.NewRepository(t) + wallet := &fakeGrowthWallet{} + now := fixedGrowthNow() + svc := growthservice.New(repository, wallet, growthservice.WithNoticeService(&fakeGrowthNoticeService{})) + svc.SetClock(func() time.Time { return now }) + ctx := appcode.WithContext(context.Background(), "lalu") + seedGrowthRules(t, ctx, svc, domain.TrackWealth) + if _, created, err := svc.UpsertLevelRule(ctx, domain.RuleCommand{ + Track: domain.TrackWealth, Level: 2, RequiredValue: 200, Name: "wealth 2", Status: domain.StatusActive, + RewardResourceGroupID: 9002, SortOrder: 2, + DisplayConfigJSON: `{"avatar_frame_resource_id":802,"short_badge_resource_id":902}`, + OperatorAdminID: 90001, + }); err != nil || created { + t.Fatalf("update level 2 direct rewards failed: created=%v err=%v", created, err) + } + if _, err := svc.AdjustTemporaryUserLevels(ctx, domain.AdjustTemporaryLevelsCommand{ + CommandID: "temporary-promote-permanent", UserID: 51010, OperatorAdminID: 90001, + Adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackWealth, Level: 2, DurationDays: 1}}, + }); err != nil { + t.Fatalf("create temporary promotion generation failed: %v", err) + } + if claimed, _, success, _, _, err := svc.ProcessLevelRewardBatch(ctx, "temporary-promotion-grants", "reward-worker", 20, time.Second); err != nil || claimed == 0 || success != claimed { + t.Fatalf("grant temporary promotion rewards failed: claimed=%d success=%d err=%v", claimed, success, err) + } + now = now.Add(time.Millisecond) + if _, err := svc.ConsumeLevelEvent(ctx, domain.ValueEvent{ + EventID: "temporary-promotion-organic", SourceEventID: "temporary-promotion-organic", SourceService: "room-service", + SourceEventType: "RoomGiftSent", UserID: 51010, Track: domain.TrackWealth, + MetricType: domain.MetricGiftSpendCoin, ValueDelta: 200, OccurredAtMS: now.UnixMilli(), DimensionsJSON: `{}`, + }); err != nil { + t.Fatalf("organic catch-up event failed: %v", err) + } + now = fixedGrowthNow().Add(24 * time.Hour) + if _, _, _, _, _, err := svc.ProcessTemporaryLevelBatch(ctx, "promotion-expiry-worker", 20, time.Second); err != nil { + t.Fatalf("process promoted generation expiry failed: %v", err) + } + if len(wallet.revokes) != 0 { + t.Fatalf("rewards earned by real level must remain permanent, revokes=%d", len(wallet.revokes)) + } + for _, grant := range wallet.resourceGrants { + if grant.GetDurationMs() != 9999*24*time.Hour.Milliseconds() { + t.Fatalf("promoted direct resource would still expire in wallet: %+v", grant) + } + } +} + +func TestTemporaryRewardGenerationReplacementCarriesLiveGrantsWithoutDuplicateIssue(t *testing.T) { + repository := mysqltest.NewRepository(t) + wallet := &fakeGrowthWallet{} + now := fixedGrowthNow() + svc := growthservice.New(repository, wallet, growthservice.WithNoticeService(&fakeGrowthNoticeService{})) + svc.SetClock(func() time.Time { return now }) + ctx := appcode.WithContext(context.Background(), "lalu") + seedGrowthRules(t, ctx, svc, domain.TrackWealth) + if _, created, err := svc.UpsertLevelRule(ctx, domain.RuleCommand{ + Track: domain.TrackWealth, Level: 2, RequiredValue: 200, Name: "wealth 2", Status: domain.StatusActive, + RewardResourceGroupID: 9002, SortOrder: 2, + DisplayConfigJSON: `{"avatar_frame_resource_id":802,"short_badge_resource_id":902}`, + OperatorAdminID: 90001, + }); err != nil || created { + t.Fatalf("update level 2 resources failed: created=%v err=%v", created, err) + } + first, err := svc.AdjustTemporaryUserLevels(ctx, domain.AdjustTemporaryLevelsCommand{ + CommandID: "temporary-generation-carry-1", UserID: 51011, OperatorAdminID: 90001, + Adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackWealth, Level: 2, DurationDays: 1}}, + }) + if err != nil { + t.Fatalf("create first temporary generation failed: %v", err) + } + firstID := findAdminTrack(first.Tracks, domain.TrackWealth).TemporaryLevelID + if claimed, _, success, failure, _, err := svc.ProcessLevelRewardBatch(ctx, "temporary-generation-carry-grant", "reward-worker", 20, time.Second); err != nil || claimed == 0 || success != claimed || failure != 0 { + t.Fatalf("grant first generation failed: claimed=%d success=%d failure=%d err=%v", claimed, success, failure, err) + } + groupGrants, directGrants := len(wallet.grants), len(wallet.resourceGrants) + + // 覆盖调整建立新计时基线,但目标范围内已发放的 grant 必须迁移到新代,不能撤回再发造成重复权益。 + now = now.Add(time.Hour) + second, err := svc.AdjustTemporaryUserLevels(ctx, domain.AdjustTemporaryLevelsCommand{ + CommandID: "temporary-generation-carry-2", UserID: 51011, OperatorAdminID: 90001, + Adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackWealth, Level: 2, DurationDays: 1}}, + }) + if err != nil { + t.Fatalf("replace temporary generation failed: %v", err) + } + secondTrack := findAdminTrack(second.Tracks, domain.TrackWealth) + if secondTrack.TemporaryLevelID == "" || secondTrack.TemporaryLevelID == firstID { + t.Fatalf("replacement must create a distinct generation: first=%s second=%+v", firstID, secondTrack) + } + if claimed, _, success, failure, _, err := svc.ProcessLevelRewardBatch(ctx, "temporary-generation-carry-no-duplicate", "reward-worker", 20, time.Second); err != nil || claimed != 0 || success != 0 || failure != 0 { + t.Fatalf("replacement must not enqueue duplicate grants: claimed=%d success=%d failure=%d err=%v", claimed, success, failure, err) + } + if len(wallet.grants) != groupGrants || len(wallet.resourceGrants) != directGrants { + t.Fatalf("replacement duplicated wallet grants: groups=%d/%d direct=%d/%d", len(wallet.grants), groupGrants, len(wallet.resourceGrants), directGrants) + } + + // 自然成长只提升当前代中迁移过来的奖励;到期后这些 grant 不得再进入撤回队列。 + now = now.Add(time.Millisecond) + if _, err := svc.ConsumeLevelEvent(ctx, domain.ValueEvent{ + EventID: "temporary-generation-carry-organic", SourceEventID: "temporary-generation-carry-organic", SourceService: "room-service", + SourceEventType: "RoomGiftSent", UserID: 51011, Track: domain.TrackWealth, + MetricType: domain.MetricGiftSpendCoin, ValueDelta: 200, OccurredAtMS: now.UnixMilli(), DimensionsJSON: `{}`, + }); err != nil { + t.Fatalf("organic catch-up after replacement failed: %v", err) + } + now = time.UnixMilli(secondTrack.ExpiresAtMS).UTC() + if _, _, _, _, _, err := svc.ProcessTemporaryLevelBatch(ctx, "temporary-generation-carry-expiry", 100, time.Second); err != nil { + t.Fatalf("process replacement expiry failed: %v", err) + } + if len(wallet.revokes) != 0 { + t.Fatalf("current-generation grants promoted by real growth must not be revoked: revokes=%d", len(wallet.revokes)) + } +} + +func TestSupersededRewardWithFailedRevokeIsNotPromotedByNaturalGrowth(t *testing.T) { + repository := mysqltest.NewRepository(t) + wallet := &fakeGrowthWallet{} + now := fixedGrowthNow() + svc := growthservice.New(repository, wallet, growthservice.WithNoticeService(&fakeGrowthNoticeService{})) + svc.SetClock(func() time.Time { return now }) + ctx := appcode.WithContext(context.Background(), "lalu") + seedGrowthRules(t, ctx, svc, domain.TrackWealth) + if _, created, err := svc.UpsertLevelRule(ctx, domain.RuleCommand{ + Track: domain.TrackWealth, Level: 3, RequiredValue: 300, Name: "wealth 3", Status: domain.StatusActive, + RewardResourceGroupID: 9004, SortOrder: 3, + DisplayConfigJSON: `{"avatar_frame_resource_id":803,"short_badge_resource_id":903}`, + OperatorAdminID: 90001, + }); err != nil || !created { + t.Fatalf("seed level 3 resources failed: created=%v err=%v", created, err) + } + if _, err := svc.AdjustTemporaryUserLevels(ctx, domain.AdjustTemporaryLevelsCommand{ + CommandID: "temporary-failed-revoke-1", UserID: 51012, OperatorAdminID: 90001, + Adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackWealth, Level: 3, DurationDays: 1}}, + }); err != nil { + t.Fatalf("create high temporary generation failed: %v", err) + } + if claimed, _, success, failure, _, err := svc.ProcessLevelRewardBatch(ctx, "temporary-failed-revoke-grant", "reward-worker", 50, time.Second); err != nil || claimed == 0 || success != claimed || failure != 0 { + t.Fatalf("grant high temporary generation failed: claimed=%d success=%d failure=%d err=%v", claimed, success, failure, err) + } + + // 降低覆盖目标时仅 Lv3 留在旧代撤回;模拟钱包撤回失败,验证后续自然 Lv3 不会把旧代事实误标永久。 + now = now.Add(time.Hour) + if _, err := svc.AdjustTemporaryUserLevels(ctx, domain.AdjustTemporaryLevelsCommand{ + CommandID: "temporary-failed-revoke-2", UserID: 51012, OperatorAdminID: 90001, + Adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackWealth, Level: 2, DurationDays: 1}}, + }); err != nil { + t.Fatalf("replace with lower temporary target failed: %v", err) + } + revocations, err := repository.ClaimLevelRewardRevocations(ctx, "failed-revoke-worker", now.UnixMilli(), time.Second, 100) + if err != nil || len(revocations) == 0 { + t.Fatalf("claim superseded level 3 revocations failed: revocations=%d err=%v", len(revocations), err) + } + for _, revoke := range revocations { + if err := repository.MarkLevelRewardRevokeFailed(ctx, revoke.RewardJobID, revoke.WalletGrantID, "wallet timeout", now.Add(time.Minute).UnixMilli(), now.UnixMilli()); err != nil { + t.Fatalf("mark superseded revoke failed: %v", err) + } + } + + now = now.Add(time.Millisecond) + if _, err := svc.ConsumeLevelEvent(ctx, domain.ValueEvent{ + EventID: "temporary-failed-revoke-organic", SourceEventID: "temporary-failed-revoke-organic", SourceService: "room-service", + SourceEventType: "RoomGiftSent", UserID: 51012, Track: domain.TrackWealth, + MetricType: domain.MetricGiftSpendCoin, ValueDelta: 300, OccurredAtMS: now.UnixMilli(), DimensionsJSON: `{}`, + }); err != nil { + t.Fatalf("natural level 3 event failed: %v", err) + } + rewards, _, err := svc.ListLevelRewards(ctx, domain.RewardQuery{UserID: 51012, Track: domain.TrackWealth, PageSize: 100}) + if err != nil { + t.Fatalf("list rewards after natural level 3 failed: %v", err) + } + var oldTemporary, freshOrganic int + for _, reward := range rewards { + if reward.RewardSourceType != domain.RewardSourceLevel || reward.RewardSourceID != "level:3" { + continue + } + if reward.RewardOrigin == domain.RewardOriginTemporary && !reward.Permanent && reward.RevokeStatus == domain.RevokeStatusFailed { + oldTemporary++ + } + if reward.RewardOrigin == domain.RewardOriginOrganic && reward.Permanent { + freshOrganic++ + } + } + if oldTemporary != 1 || freshOrganic != 1 { + t.Fatalf("natural growth must preserve failed old revoke and create one new permanent job: old=%d organic=%d rewards=%+v", oldTemporary, freshOrganic, rewards) + } + groupGrants, directGrants := len(wallet.grants), len(wallet.resourceGrants) + if claimed, _, success, failure, _, err := svc.ProcessLevelRewardBatch(ctx, "temporary-failed-revoke-organic-grant", "reward-worker", 20, time.Second); err != nil || claimed != 1 || success != 1 || failure != 0 { + t.Fatalf("fresh organic level 3 grant failed: claimed=%d success=%d failure=%d err=%v", claimed, success, failure, err) + } + if len(wallet.grants) != groupGrants+1 || len(wallet.resourceGrants) != directGrants+2 { + t.Fatalf("fresh organic job must issue independent replacement grants: groups=%d/%d direct=%d/%d", len(wallet.grants), groupGrants, len(wallet.resourceGrants), directGrants) + } +} + +func TestTemporaryRewardPartialWalletSuccessIsDurableAndRevokedAtExpiry(t *testing.T) { + repository := mysqltest.NewRepository(t) + wallet := &fakePartialGrowthWallet{failNextResource: true} + now := fixedGrowthNow() + svc := growthservice.New(repository, wallet, growthservice.WithNoticeService(&fakeGrowthNoticeService{})) + svc.SetClock(func() time.Time { return now }) + ctx := appcode.WithContext(context.Background(), "lalu") + for _, rule := range []domain.RuleCommand{ + {Track: domain.TrackWealth, Level: 1, RequiredValue: 100, Name: "wealth 1", Status: domain.StatusActive, SortOrder: 1, DisplayConfigJSON: `{}`, OperatorAdminID: 90001}, + {Track: domain.TrackWealth, Level: 2, RequiredValue: 200, Name: "wealth 2", Status: domain.StatusActive, RewardResourceGroupID: 9002, SortOrder: 2, DisplayConfigJSON: `{"avatar_frame_resource_id":802}`, OperatorAdminID: 90001}, + } { + if _, created, err := svc.UpsertLevelRule(ctx, rule); err != nil || !created { + t.Fatalf("seed partial-grant rule level %d failed: created=%v err=%v", rule.Level, created, err) + } + } + profile, err := svc.AdjustTemporaryUserLevels(ctx, domain.AdjustTemporaryLevelsCommand{ + CommandID: "temporary-partial-wallet-grant", UserID: 51013, OperatorAdminID: 90001, + Adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackWealth, Level: 2, DurationDays: 1}}, + }) + if err != nil { + t.Fatalf("create partial-grant temporary level failed: %v", err) + } + track := findAdminTrack(profile.Tracks, domain.TrackWealth) + if claimed, _, success, failure, _, err := svc.ProcessLevelRewardBatch(ctx, "temporary-partial-wallet-grant-run", "reward-worker", 20, time.Second); err != nil || claimed != 1 || success != 0 || failure != 1 { + t.Fatalf("partial wallet grant batch mismatch: claimed=%d success=%d failure=%d err=%v", claimed, success, failure, err) + } + if len(wallet.grants) != 1 || len(wallet.resourceGrants) != 0 { + t.Fatalf("test precondition requires group success followed by direct-resource failure: groups=%d resources=%d", len(wallet.grants), len(wallet.resourceGrants)) + } + + // 到期事务必须能看见失败任务中已成功的资源组 grant,并把它交给撤回 worker。 + now = time.UnixMilli(track.ExpiresAtMS).UTC() + if _, _, _, failure, _, err := svc.ProcessTemporaryLevelBatch(ctx, "temporary-partial-wallet-expiry", 20, time.Second); err != nil || failure != 0 { + t.Fatalf("process partial-grant expiry failed: failure=%d err=%v", failure, err) + } + if len(wallet.revokes) != 1 || wallet.revokes[0].GetGrantId() != "grant-1" { + t.Fatalf("successful wallet prefix must be explicitly revoked: revokes=%+v", wallet.revokes) + } + rewards, _, err := svc.ListLevelRewards(ctx, domain.RewardQuery{UserID: 51013, Track: domain.TrackWealth, PageSize: 20}) + if err != nil || len(rewards) != 1 || rewards[0].Status != domain.RewardStatusCanceled || rewards[0].RevokeStatus != domain.RevokeStatusRevoked { + t.Fatalf("partial reward terminal state mismatch: rewards=%+v err=%v", rewards, err) + } +} + +func TestTemporaryGrowthLevelDualTrackValidationRollsBackWholeCommand(t *testing.T) { + svc, _ := newGrowthService(t) + ctx := appcode.WithContext(context.Background(), "lalu") + seedGrowthRules(t, ctx, svc, domain.TrackWealth) + + _, err := svc.AdjustTemporaryUserLevels(ctx, domain.AdjustTemporaryLevelsCommand{ + CommandID: "temporary-level-command-atomic", UserID: 51002, OperatorAdminID: 90001, + Adjustments: []domain.TemporaryLevelAdjustment{ + {Track: domain.TrackWealth, Level: 2, DurationDays: 7}, + // charm has no active level-2 rule in this isolated schema, so the whole transaction must fail. + {Track: domain.TrackCharm, Level: 2, DurationDays: 7}, + }, + }) + if err == nil { + t.Fatal("dual-track command should fail when one active rule is missing") + } + profiles, queryErr := svc.BatchGetUserLevelAdminProfiles(ctx, []int64{51002}) + if queryErr != nil { + t.Fatalf("BatchGetUserLevelAdminProfiles failed: %v", queryErr) + } + if wealth := findAdminTrack(profiles.Profiles[0].Tracks, domain.TrackWealth); wealth.TemporaryLevelID != "" || wealth.DisplayLevel != wealth.RealLevel { + t.Fatalf("wealth adjustment leaked from rolled-back command: %+v", wealth) + } +} + +func TestTemporaryGrowthLevelAddsWindowExperienceWithoutMutatingRealTotal(t *testing.T) { + repository := mysqltest.NewRepository(t) + wallet := &fakeGrowthWallet{} + now := fixedGrowthNow() + svc := growthservice.New(repository, wallet) + svc.SetClock(func() time.Time { return now }) + ctx := appcode.WithContext(context.Background(), "lalu") + seedGrowthRules(t, ctx, svc, domain.TrackWealth) + if _, created, err := svc.UpsertLevelRule(ctx, domain.RuleCommand{ + Track: domain.TrackWealth, Level: 3, RequiredValue: 300, Name: "wealth 3", Status: domain.StatusActive, + SortOrder: 3, DisplayConfigJSON: `{}`, OperatorAdminID: 90001, + }); err != nil || !created { + t.Fatalf("seed level 3 failed: created=%v err=%v", created, err) + } + if _, err := svc.AdjustTemporaryUserLevels(ctx, domain.AdjustTemporaryLevelsCommand{ + CommandID: "temporary-level-window", UserID: 51003, OperatorAdminID: 90001, + Adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackWealth, Level: 2, DurationDays: 1}}, + }); err != nil { + t.Fatalf("AdjustTemporaryUserLevels failed: %v", err) + } + if _, err := svc.ConsumeLevelEvent(ctx, domain.ValueEvent{ + EventID: "temporary-window-event", SourceEventID: "temporary-window-event", SourceService: "room-service", + SourceEventType: "RoomGiftSent", UserID: 51003, Track: domain.TrackWealth, + MetricType: domain.MetricGiftSpendCoin, ValueDelta: 100, OccurredAtMS: now.UnixMilli(), DimensionsJSON: `{}`, + }); err != nil { + t.Fatalf("ConsumeLevelEvent failed: %v", err) + } + profiles, err := svc.BatchGetUserLevelAdminProfiles(ctx, []int64{51003}) + if err != nil { + t.Fatalf("BatchGetUserLevelAdminProfiles failed: %v", err) + } + wealth := findAdminTrack(profiles.Profiles[0].Tracks, domain.TrackWealth) + if wealth.RealTotalValue != 100 || wealth.RealLevel != 1 || wealth.DisplayValue != 300 || wealth.DisplayLevel != 3 { + t.Fatalf("real and overlay progress must remain separate: %+v", wealth) + } + now = now.Add(24 * time.Hour) + profiles, err = svc.BatchGetUserLevelAdminProfiles(ctx, []int64{51003}) + if err != nil { + t.Fatalf("expiry profile failed: %v", err) + } + wealth = findAdminTrack(profiles.Profiles[0].Tracks, domain.TrackWealth) + if wealth.RealTotalValue != 100 || wealth.DisplayValue != 100 || wealth.DisplayLevel != 1 || wealth.TemporaryLevelID != "" { + t.Fatalf("expiry must reveal untouched real account: %+v", wealth) + } +} + +func TestTemporaryGrowthLevelRejectsInvalidTracksBoundsAndDuration(t *testing.T) { + svc, _ := newGrowthService(t) + ctx := appcode.WithContext(context.Background(), "lalu") + tests := []struct { + name string + adjustments []domain.TemporaryLevelAdjustment + }{ + {name: "game is read only", adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackGame, Level: 2, DurationDays: 7}}}, + {name: "level zero", adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackWealth, Level: 0, DurationDays: 7}}}, + {name: "above level fifty", adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackCharm, Level: 51, DurationDays: 7}}}, + {name: "zero duration", adjustments: []domain.TemporaryLevelAdjustment{{Track: domain.TrackWealth, Level: 2, DurationDays: 0}}}, + {name: "duplicate track", adjustments: []domain.TemporaryLevelAdjustment{ + {Track: domain.TrackWealth, Level: 2, DurationDays: 7}, + {Track: domain.TrackWealth, Level: 3, DurationDays: 7}, + }}, + } + for index, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := svc.AdjustTemporaryUserLevels(ctx, domain.AdjustTemporaryLevelsCommand{ + CommandID: fmt.Sprintf("temporary-invalid-%d", index), UserID: 51004, OperatorAdminID: 90001, + Adjustments: test.adjustments, + }) + if err == nil { + t.Fatalf("invalid adjustment was accepted: %+v", test.adjustments) + } + }) + } +} + +func TestGrowthLevelEventConcurrentSameUserTrack(t *testing.T) { + svc, _ := newGrowthService(t) + ctx := appcode.WithContext(context.Background(), "lalu") + seedGrowthRules(t, ctx, svc, domain.TrackWealth) + + // 先创建账户,确保并发波次命中生产故障里的“已有热点账户”路径,而不是只覆盖首次插入。 + if _, err := svc.ConsumeLevelEvent(ctx, growthValueEvent("growth-hot-account-seed", 41001, 1)); err != nil { + t.Fatalf("seed hot growth account: %v", err) + } + + const writers = 40 + start := make(chan struct{}) + errs := make(chan error, writers) + for index := 0; index < writers; index++ { + go func(index int) { + <-start + _, err := svc.ConsumeLevelEvent(ctx, growthValueEvent(fmt.Sprintf("growth-hot-account-%d", index), 41001, 1)) + errs <- err + }(index) + } + close(start) + for index := 0; index < writers; index++ { + if err := <-errs; err != nil { + t.Fatalf("concurrent growth event %d failed: %v", index, err) + } + } + + // 同一幂等键并发只能有一个事务增加累计值,其余事务必须在首个提交后稳定返回 duplicate。 + const duplicateWriters = 10 + duplicateStart := make(chan struct{}) + type consumeResult struct { + status string + err error + } + duplicateResults := make(chan consumeResult, duplicateWriters) + for index := 0; index < duplicateWriters; index++ { + go func() { + <-duplicateStart + result, err := svc.ConsumeLevelEvent(ctx, growthValueEvent("growth-hot-account-duplicate", 41001, 1)) + duplicateResults <- consumeResult{status: result.Status, err: err} + }() + } + close(duplicateStart) + consumed := 0 + duplicates := 0 + for index := 0; index < duplicateWriters; index++ { + result := <-duplicateResults + if result.err != nil { + t.Fatalf("duplicate concurrent growth event %d failed: %v", index, result.err) + } + switch result.status { + case domain.EventStatusConsumed: + consumed++ + case domain.EventStatusDuplicate: + duplicates++ + default: + t.Fatalf("unexpected duplicate concurrent status %q", result.status) + } + } + if consumed != 1 || duplicates != duplicateWriters-1 { + t.Fatalf("duplicate concurrency mismatch: consumed=%d duplicates=%d", consumed, duplicates) + } + + overview, err := svc.GetMyLevelOverview(ctx, 41001) + if err != nil { + t.Fatalf("get concurrent growth overview: %v", err) + } + wealth := findTrackOverview(overview.Tracks, domain.TrackWealth) + if want := int64(1 + writers + 1); wealth.TotalValue != want { + t.Fatalf("concurrent total value=%d want=%d", wealth.TotalValue, want) + } +} + +func growthValueEvent(eventID string, userID int64, delta int64) domain.ValueEvent { + return domain.ValueEvent{ + EventID: eventID, + SourceEventID: eventID, + SourceService: "room-service", + SourceEventType: "RoomGiftSent", + UserID: userID, + Track: domain.TrackWealth, + MetricType: domain.MetricGiftSpendCoin, + ValueDelta: delta, + OccurredAtMS: fixedGrowthNow().UnixMilli(), + DimensionsJSON: `{"room_id":"room-concurrency"}`, + } +} + func TestGrowthLevelRewardGrantsLevelResources(t *testing.T) { svc, wallet := newGrowthService(t) ctx := appcode.WithContext(context.Background(), "lalu") @@ -400,9 +990,48 @@ func findTrackOverview(items []domain.TrackOverview, track string) domain.TrackO return domain.TrackOverview{} } +func findAdminTrack(items []domain.AdminTrackProfile, track string) domain.AdminTrackProfile { + for _, item := range items { + if item.Track == track { + return item + } + } + return domain.AdminTrackProfile{} +} + type fakeGrowthWallet struct { grants []*walletv1.GrantResourceGroupRequest resourceGrants []*walletv1.GrantResourceRequest + revokes []*walletv1.RevokeResourceGrantRequest +} + +// fakePartialGrowthWallet reproduces a multi-action reward where the resource group commits but a later direct +// resource RPC fails. Real wallet command IDs are idempotent; the test focuses on activity's durable grant ledger. +type fakePartialGrowthWallet struct { + fakeGrowthWallet + failNextResource bool +} + +func (f *fakePartialGrowthWallet) GrantResource(ctx context.Context, req *walletv1.GrantResourceRequest, opts ...grpc.CallOption) (*walletv1.ResourceGrantResponse, error) { + if f.failNextResource { + f.failNextResource = false + return nil, fmt.Errorf("injected direct resource failure") + } + return f.fakeGrowthWallet.GrantResource(ctx, req, opts...) +} + +func (f *fakeGrowthWallet) RevokeResourceGrant(_ context.Context, req *walletv1.RevokeResourceGrantRequest, _ ...grpc.CallOption) (*walletv1.ResourceGrantResponse, error) { + f.revokes = append(f.revokes, req) + return &walletv1.ResourceGrantResponse{Grant: &walletv1.ResourceGrant{GrantId: req.GetGrantId(), Status: "revoked"}}, nil +} + +type fakeGrowthNoticeService struct { + commands []messageservice.NoticeCommand +} + +func (f *fakeGrowthNoticeService) CreateSystemNotice(_ context.Context, cmd messageservice.NoticeCommand) (messageservice.NoticeResult, error) { + f.commands = append(f.commands, cmd) + return messageservice.NoticeResult{Created: true}, nil } func (f *fakeGrowthWallet) GrantResource(_ context.Context, req *walletv1.GrantResourceRequest, _ ...grpc.CallOption) (*walletv1.ResourceGrantResponse, error) { diff --git a/services/activity-service/internal/service/message/action_type_test.go b/services/activity-service/internal/service/message/action_type_test.go new file mode 100644 index 00000000..81139378 --- /dev/null +++ b/services/activity-service/internal/service/message/action_type_test.go @@ -0,0 +1,14 @@ +package message + +import "testing" + +func TestAllowedActionTypeIncludesVIPCoinRebateClaim(t *testing.T) { + // 返现通知由 wallet 事实触发,如果该动作从白名单回归,消息会在进入 MySQL 前被拒绝, + // Flutter 也就永远拿不到 rebate_id。这个纯单测不依赖可选的 MySQL 测试环境。 + if !allowedActionType("vip_coin_rebate_claim") { + t.Fatal("vip_coin_rebate_claim must remain an allowed system inbox action") + } + if allowedActionType("vip_coin_rebate_claim_javascript") { + t.Fatal("action whitelist must still reject unregistered variants") + } +} diff --git a/services/activity-service/internal/service/message/service.go b/services/activity-service/internal/service/message/service.go index 2bf9bc02..78317591 100644 --- a/services/activity-service/internal/service/message/service.go +++ b/services/activity-service/internal/service/message/service.go @@ -467,6 +467,10 @@ func allowedActionType(actionType string) bool { switch actionType { case "", "wallet_withdraw_detail", "host_application_detail", "activity_detail", "room_detail", "user_profile", "app_h5", "coin_seller_transfer_detail": return true + case "vip_coin_rebate_claim": + // VIP 返现通知只携带 wallet 生成的 rebate_id JSON,客户端仍需调用受鉴权的 + // claim 接口才能入账;它不是任意外链或脚本动作,可以进入 system inbox。 + return true default: return false } diff --git a/services/activity-service/internal/service/task/service.go b/services/activity-service/internal/service/task/service.go index f9cbf217..958a2a54 100644 --- a/services/activity-service/internal/service/task/service.go +++ b/services/activity-service/internal/service/task/service.go @@ -217,7 +217,7 @@ func (s *Service) HandleRoomEvent(ctx context.Context, envelope *roomeventsv1.Ev } // coin_spent 是钱包真实扣费金额;旧 outbox 可能只有礼物面值,兜底用 gift_value 保持历史事件可重放。 coinSpent := gift.GetCoinSpent() - if coinSpent <= 0 { + if coinSpent == 0 { coinSpent = gift.GetGiftValue() } dimensions, err := roomGiftTaskDimensions(envelope, &gift) @@ -225,9 +225,12 @@ func (s *Service) HandleRoomEvent(ctx context.Context, envelope *roomeventsv1.Ev return 0, err } eventCtx := appcode.WithContext(ctx, envelope.GetAppCode()) - // 一次送礼同时满足“消耗金币”和“赠送次数”两个指标;用不同 event_id 后缀隔离幂等键,避免两个指标互相覆盖。 - events := []taskdomain.Event{ - { + // 零价礼物是已提交的合法房间事实:它不能制造 Value=0 的金币任务事件,但仍应累计 + // 送礼次数。因此转换层只过滤恰好为 0 的 spend;负值仍交给 ConsumeTaskEvent 拒绝, + // 避免这个兼容修复顺带吞掉上游坏数据。 + events := make([]taskdomain.Event, 0, 4) + if coinSpent != 0 { + events = append(events, taskdomain.Event{ EventID: envelope.GetEventId() + ":task:gift_spend", EventType: envelope.GetEventType(), SourceService: "room-service", @@ -236,22 +239,23 @@ func (s *Service) HandleRoomEvent(ctx context.Context, envelope *roomeventsv1.Ev Value: coinSpent, OccurredAtMS: envelope.GetOccurredAtMs(), DimensionsJSON: dimensions, - }, - { - EventID: envelope.GetEventId() + ":task:gift_count", - EventType: envelope.GetEventType(), - SourceService: "room-service", - UserID: gift.GetSenderUserId(), - MetricType: taskdomain.MetricGiftSendCount, - Value: int64(gift.GetGiftCount()), - OccurredAtMS: envelope.GetOccurredAtMs(), - DimensionsJSON: dimensions, - }, + }) } + // 次数指标来自已校验为正数的 gift_count,与礼物是否免费无关;独立 event_id 保证重放幂等。 + events = append(events, taskdomain.Event{ + EventID: envelope.GetEventId() + ":task:gift_count", + EventType: envelope.GetEventType(), + SourceService: "room-service", + UserID: gift.GetSenderUserId(), + MetricType: taskdomain.MetricGiftSendCount, + Value: int64(gift.GetGiftCount()), + OccurredAtMS: envelope.GetOccurredAtMs(), + DimensionsJSON: dimensions, + }) // 幸运礼物是普通送礼的子集;普通礼物指标照常累计,额外再写幸运礼物专属指标,方便后台用不同任务口径配置。 if isLuckyGiftTaskEvent(&gift) { - events = append(events, - taskdomain.Event{ + if coinSpent != 0 { + events = append(events, taskdomain.Event{ EventID: envelope.GetEventId() + ":task:lucky_gift_spend", EventType: envelope.GetEventType(), SourceService: "room-service", @@ -260,18 +264,18 @@ func (s *Service) HandleRoomEvent(ctx context.Context, envelope *roomeventsv1.Ev Value: coinSpent, OccurredAtMS: envelope.GetOccurredAtMs(), DimensionsJSON: dimensions, - }, - taskdomain.Event{ - EventID: envelope.GetEventId() + ":task:lucky_gift_count", - EventType: envelope.GetEventType(), - SourceService: "room-service", - UserID: gift.GetSenderUserId(), - MetricType: taskdomain.MetricLuckyGiftSendCount, - Value: int64(gift.GetGiftCount()), - OccurredAtMS: envelope.GetOccurredAtMs(), - DimensionsJSON: dimensions, - }, - ) + }) + } + events = append(events, taskdomain.Event{ + EventID: envelope.GetEventId() + ":task:lucky_gift_count", + EventType: envelope.GetEventType(), + SourceService: "room-service", + UserID: gift.GetSenderUserId(), + MetricType: taskdomain.MetricLuckyGiftSendCount, + Value: int64(gift.GetGiftCount()), + OccurredAtMS: envelope.GetOccurredAtMs(), + DimensionsJSON: dimensions, + }) } consumed := int32(0) for _, event := range events { diff --git a/services/activity-service/internal/service/task/service_test.go b/services/activity-service/internal/service/task/service_test.go index 5e55a89b..898fe891 100644 --- a/services/activity-service/internal/service/task/service_test.go +++ b/services/activity-service/internal/service/task/service_test.go @@ -6,13 +6,168 @@ import ( "time" "google.golang.org/grpc" + "google.golang.org/protobuf/proto" + roomeventsv1 "hyapp.local/api/proto/events/room/v1" walletv1 "hyapp.local/api/proto/wallet/v1" "hyapp/pkg/appcode" + "hyapp/pkg/xerr" taskdomain "hyapp/services/activity-service/internal/domain/task" taskservice "hyapp/services/activity-service/internal/service/task" "hyapp/services/activity-service/internal/testutil/mysqltest" ) +// TestHandleRoomEventKeepsCountForProductionZeroValueGift 回归生产 gift_id=459 的零价礼物。 +// 这类事实必须确认并累计送礼次数,不能生成会被领域层拒绝的零增量 spend 事件。 +func TestHandleRoomEventKeepsCountForProductionZeroValueGift(t *testing.T) { + repository := newRecordingTaskRepository() + svc := taskservice.New(repository, &fakeWalletClient{}) + envelope := roomGiftEnvelope(t, "evt-production-free-gift-459", &roomeventsv1.RoomGiftSent{ + SenderUserId: 10001, + TargetUserId: 20002, + GiftId: "459", + GiftCount: 1, + GiftValue: 0, + CoinSpent: 0, + GiftTypeCode: "normal", + }) + + consumed, err := svc.HandleRoomEvent(context.Background(), envelope) + if err != nil { + t.Fatalf("HandleRoomEvent zero-value gift failed: %v", err) + } + if consumed != 1 { + t.Fatalf("zero-value gift consumed=%d, want one count projection", consumed) + } + assertRecordedTaskEvents(t, repository.events, []expectedTaskEvent{{ + EventID: "evt-production-free-gift-459:task:gift_count", + MetricType: taskdomain.MetricGiftSendCount, + Value: 1, + }}) + + // RocketMQ 重投会用相同 envelope event_id 再次进入;稳定后缀让仓储幂等确认,不重复累加。 + if _, err := svc.HandleRoomEvent(context.Background(), envelope); err != nil { + t.Fatalf("HandleRoomEvent duplicate zero-value gift failed: %v", err) + } + if len(repository.events) != 1 || repository.calls != 2 { + t.Fatalf("duplicate projection must keep one unique event: calls=%d events=%+v", repository.calls, repository.events) + } +} + +func TestHandleRoomEventUsesPositiveSpendCompatibilityFields(t *testing.T) { + tests := []struct { + name string + eventID string + giftValue int64 + coinSpent int64 + wantSpend int64 + }{ + {name: "legacy gift_value fallback", eventID: "evt-legacy-gift-value", giftValue: 40, coinSpent: 0, wantSpend: 40}, + {name: "coin_spent preferred", eventID: "evt-coin-spent", giftValue: 0, coinSpent: 60, wantSpend: 60}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + repository := newRecordingTaskRepository() + svc := taskservice.New(repository, &fakeWalletClient{}) + envelope := roomGiftEnvelope(t, tt.eventID, &roomeventsv1.RoomGiftSent{ + SenderUserId: 10001, + TargetUserId: 20002, + GiftId: "gift-compat", + GiftCount: 3, + GiftValue: tt.giftValue, + CoinSpent: tt.coinSpent, + GiftTypeCode: "normal", + }) + + if _, err := svc.HandleRoomEvent(context.Background(), envelope); err != nil { + t.Fatalf("HandleRoomEvent failed: %v", err) + } + assertRecordedTaskEvents(t, repository.events, []expectedTaskEvent{ + {EventID: envelope.GetEventId() + ":task:gift_spend", MetricType: taskdomain.MetricGiftSpendCoin, Value: tt.wantSpend}, + {EventID: envelope.GetEventId() + ":task:gift_count", MetricType: taskdomain.MetricGiftSendCount, Value: 3}, + }) + }) + } +} + +// TestHandleRoomEventKeepsLuckyCountsForZeroValueGift 锁定幸运礼物的两层计数口径: +// 免费礼物不累计金币,但仍同时属于普通送礼次数和幸运礼物次数。 +func TestHandleRoomEventKeepsLuckyCountsForZeroValueGift(t *testing.T) { + repository := newRecordingTaskRepository() + svc := taskservice.New(repository, &fakeWalletClient{}) + envelope := roomGiftEnvelope(t, "evt-free-lucky-gift", &roomeventsv1.RoomGiftSent{ + SenderUserId: 10001, + TargetUserId: 20002, + GiftId: "free-lucky", + GiftCount: 5, + GiftValue: 0, + CoinSpent: 0, + PoolId: "lucky_pool_free", + GiftTypeCode: "normal", + }) + + if _, err := svc.HandleRoomEvent(context.Background(), envelope); err != nil { + t.Fatalf("HandleRoomEvent zero-value lucky gift failed: %v", err) + } + assertRecordedTaskEvents(t, repository.events, []expectedTaskEvent{ + {EventID: "evt-free-lucky-gift:task:gift_count", MetricType: taskdomain.MetricGiftSendCount, Value: 5}, + {EventID: "evt-free-lucky-gift:task:lucky_gift_count", MetricType: taskdomain.MetricLuckyGiftSendCount, Value: 5}, + }) +} + +// TestConsumeTaskEventStillRejectsZeroValue 保留通用领域边界:零增量应在事实转换层被过滤, +// 不能进入仓储的幂等表和进度表,否则会混淆“已消费”与“有有效进度”。 +func TestConsumeTaskEventStillRejectsZeroValue(t *testing.T) { + repository := newRecordingTaskRepository() + svc := taskservice.New(repository, &fakeWalletClient{}) + _, err := svc.ConsumeTaskEvent(context.Background(), taskdomain.Event{ + EventID: "evt-zero-domain-value", + EventType: "RoomGiftSent", + SourceService: "room-service", + UserID: 10001, + MetricType: taskdomain.MetricGiftSpendCoin, + Value: 0, + }) + if !xerr.IsCode(err, xerr.InvalidArgument) { + t.Fatalf("zero task value error=%v, want INVALID_ARGUMENT", err) + } + if repository.calls != 0 || len(repository.events) != 0 { + t.Fatalf("invalid zero-value event must not reach repository: calls=%d events=%+v", repository.calls, repository.events) + } +} + +// TestHandleRoomEventStillRejectsNegativeSpend 确认兼容分支只确认合法零值,不把负值上游事实降级成计数事件。 +func TestHandleRoomEventStillRejectsNegativeSpend(t *testing.T) { + tests := []struct { + name string + giftValue int64 + coinSpent int64 + }{ + {name: "negative coin spent does not fallback", giftValue: 100, coinSpent: -1}, + {name: "negative legacy gift value", giftValue: -1, coinSpent: 0}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + repository := newRecordingTaskRepository() + svc := taskservice.New(repository, &fakeWalletClient{}) + envelope := roomGiftEnvelope(t, "evt-negative-gift-value", &roomeventsv1.RoomGiftSent{ + SenderUserId: 10001, + TargetUserId: 20002, + GiftId: "broken-gift", + GiftCount: 1, + GiftValue: test.giftValue, + CoinSpent: test.coinSpent, + }) + + if _, err := svc.HandleRoomEvent(context.Background(), envelope); !xerr.IsCode(err, xerr.InvalidArgument) { + t.Fatalf("negative spend error=%v, want INVALID_ARGUMENT", err) + } + if repository.calls != 0 || len(repository.events) != 0 { + t.Fatalf("negative spend must fail before repository: calls=%d events=%+v", repository.calls, repository.events) + } + }) + } +} + // TestTaskEventQueryAndClaimFlow 覆盖任务定义、事件进度、App 查询、领奖和领奖幂等的主闭环。 func TestTaskEventQueryAndClaimFlow(t *testing.T) { repository := mysqltest.NewRepository(t) @@ -365,6 +520,64 @@ func (f *fakeTaskPolicyRepository) UpsertTaskDefinition(_ context.Context, comma }, true, nil } +type expectedTaskEvent struct { + EventID string + MetricType string + Value int64 +} + +// recordingTaskRepository 只实现 HandleRoomEvent 依赖的写入边界,并用 event_id 模拟 MySQL +// task_event_consumption 的幂等行为;测试因此可以同时断言投影内容和 MQ 重投结果。 +type recordingTaskRepository struct { + taskservice.Repository + + calls int + events []taskdomain.Event + seen map[string]struct{} +} + +func newRecordingTaskRepository() *recordingTaskRepository { + return &recordingTaskRepository{seen: make(map[string]struct{})} +} + +func (r *recordingTaskRepository) ConsumeTaskEvent(_ context.Context, event taskdomain.Event, _ string, _ int64) (taskdomain.EventResult, error) { + r.calls++ + if _, exists := r.seen[event.EventID]; exists { + return taskdomain.EventResult{EventID: event.EventID, Status: taskdomain.EventStatusConsumed}, nil + } + r.seen[event.EventID] = struct{}{} + r.events = append(r.events, event) + return taskdomain.EventResult{EventID: event.EventID, Status: taskdomain.EventStatusConsumed, MatchedTaskCount: 1}, nil +} + +func roomGiftEnvelope(t *testing.T, eventID string, gift *roomeventsv1.RoomGiftSent) *roomeventsv1.EventEnvelope { + t.Helper() + body, err := proto.Marshal(gift) + if err != nil { + t.Fatalf("marshal RoomGiftSent failed: %v", err) + } + return &roomeventsv1.EventEnvelope{ + AppCode: "lalu", + EventId: eventID, + RoomId: "lalu-regression-room", + EventType: "RoomGiftSent", + OccurredAtMs: time.Date(2026, 7, 9, 0, 0, 0, 0, time.UTC).UnixMilli(), + Body: body, + } +} + +func assertRecordedTaskEvents(t *testing.T, got []taskdomain.Event, want []expectedTaskEvent) { + t.Helper() + if len(got) != len(want) { + t.Fatalf("projected event count=%d, want=%d: %+v", len(got), len(want), got) + } + for i := range want { + if got[i].EventID != want[i].EventID || got[i].MetricType != want[i].MetricType || got[i].Value != want[i].Value { + t.Fatalf("projected event[%d]=%+v, want=%+v", i, got[i], want[i]) + } + } +} + type fakeWalletClient struct { calls int last *walletv1.CreditTaskRewardRequest diff --git a/services/activity-service/internal/service/wheel/service.go b/services/activity-service/internal/service/wheel/service.go index 17c5b24f..9368e70d 100644 --- a/services/activity-service/internal/service/wheel/service.go +++ b/services/activity-service/internal/service/wheel/service.go @@ -2,6 +2,7 @@ package wheel import ( "context" + "crypto/sha256" "encoding/json" "fmt" "log/slog" @@ -24,6 +25,9 @@ type Repository interface { ListWheelDraws(ctx context.Context, query domain.DrawQuery) ([]domain.DrawResult, int64, error) GetWheelDrawSummary(ctx context.Context, query domain.DrawQuery) (domain.DrawSummary, error) MarkWheelDrawsGranted(ctx context.Context, appCode string, drawIDs []string, transactionID string, nowMS int64) error + ClaimWheelRewardOutbox(ctx context.Context, workerID string, nowMS int64, lockUntilMS int64, limit int, maxRetry int) ([]domain.RewardOutboxRecord, error) + MarkWheelRewardOutboxDelivered(ctx context.Context, outboxID string, workerID string, drawIDs []string, transactionID string, nowMS int64) error + MarkWheelRewardOutboxFailed(ctx context.Context, outboxID string, workerID string, nowMS int64, nextRetryAtMS int64, maxRetry int, lastError string) error } type WalletClient interface { @@ -128,26 +132,41 @@ func (s *Service) fulfillWheelRewardFastPath(ctx context.Context, cmd domain.Dra if result.RewardStatus == domain.StatusGranted { return result } - if s.wallet == nil { - logx.Warn(ctx, "wheel_reward_wallet_not_configured", slog.String("draw_id", result.DrawID)) + fulfilled, err := s.fulfillWheelReward(ctx, cmd, result) + if err != nil { + // 抽奖结果已经在事务内确认,发放失败时不能回滚抽奖事实;保留 pending/outbox,补偿 worker 会使用相同 command_id 继续核账和补发。 + logx.Error(ctx, "wheel_reward_fast_path_failed", err, + slog.String("draw_id", result.DrawID), + slog.String("command_id", result.CommandID), + ) return result } + result = fulfilled + drawIDs := []string{result.DrawID} + if result.DrawID == "" { + drawIDs = append([]string(nil), result.DrawIDs...) + } + if err := s.repository.MarkWheelDrawsGranted(appcode.WithContext(context.WithoutCancel(ctx), appcode.FromContext(ctx)), appcode.FromContext(ctx), drawIDs, result.WalletTransactionID, s.now().UTC().UnixMilli()); err != nil { + logx.Error(ctx, "wheel_reward_fast_path_mark_granted_failed", err, slog.String("draw_id", result.DrawID), slog.String("wallet_transaction_id", result.WalletTransactionID)) + } + return result +} + +// fulfillWheelReward 调用 wallet owner 的幂等账务命令完成一组奖品发放。 +// CreditWheelReward/GrantResource 在钱包事务内都会先按稳定 command_id 和 request hash 查询既有交易或权益;因此 worker 在超时、进程崩溃或标记 delivered 失败后重放时,先得到原收据而不是再次入账。 +func (s *Service) fulfillWheelReward(ctx context.Context, cmd domain.DrawCommand, result domain.DrawResult) (domain.DrawResult, error) { + if s.wallet == nil { + return result, xerr.New(xerr.Unavailable, "wheel reward wallet client is not configured") + } rewards := wheelResultRewards(result) if len(rewards) == 0 { - return result + return result, xerr.New(xerr.InvalidArgument, "wheel reward snapshot is empty") } receipts := make([]string, 0, len(rewards)) for index, reward := range rewards { receipt, coinBalance, err := s.fulfillWheelRewardItem(ctx, cmd, result, reward, index, len(rewards)) if err != nil { - // 抽奖结果已经在事务内确认,发放失败时不能回滚抽奖事实;保留 pending/outbox,补偿 worker 或重试可以用同一 command_id 幂等补发。 - logx.Error(ctx, "wheel_reward_fast_path_failed", err, - slog.String("draw_id", result.DrawID), - slog.String("command_id", result.CommandID), - slog.String("reward_type", reward.RewardType), - slog.String("selected_tier_id", reward.SelectedTierID), - ) - return result + return result, fmt.Errorf("fulfill reward item %d type=%s tier=%s: %w", index+1, reward.RewardType, reward.SelectedTierID, err) } if receipt != "" { receipts = append(receipts, receipt) @@ -157,15 +176,20 @@ func (s *Service) fulfillWheelRewardFastPath(ctx context.Context, cmd domain.Dra } } result.RewardStatus = domain.StatusGranted - result.WalletTransactionID = strings.Join(receipts, ",") - drawIDs := []string{result.DrawID} - if result.DrawID == "" { - drawIDs = append([]string(nil), result.DrawIDs...) + result.WalletTransactionID = wheelRewardReceiptReference(receipts) + return result, nil +} + +func wheelRewardReceiptReference(receipts []string) string { + joined := strings.Join(receipts, ",") + const rewardTransactionIDMaxLength = 128 + if len(joined) <= rewardTransactionIDMaxLength { + return joined } - if err := s.repository.MarkWheelDrawsGranted(appcode.WithContext(context.WithoutCancel(ctx), appcode.FromContext(ctx)), appcode.FromContext(ctx), drawIDs, result.WalletTransactionID, s.now().UTC().UnixMilli()); err != nil { - logx.Error(ctx, "wheel_reward_fast_path_mark_granted_failed", err, slog.String("draw_id", result.DrawID), slog.String("wallet_transaction_id", result.WalletTransactionID)) - } - return result + // wheel_draw_records 的历史字段是 VARCHAR(128),而钱包 transaction/grant ID 各自约 68 字符; + // 多奖品直接逗号拼接会让钱包已经全部发放后仍无法标记 granted。超长集合保存稳定摘要,逐项原收据仍由钱包 owner 按派生 command_id 可审计查询。 + sum := sha256.Sum256([]byte(joined)) + return fmt.Sprintf("batch:%d:%x", len(receipts), sum) } func (s *Service) fulfillWheelRewardItem(ctx context.Context, cmd domain.DrawCommand, result domain.DrawResult, reward domain.DrawReward, index, total int) (string, int64, error) { diff --git a/services/activity-service/internal/service/wheel/service_test.go b/services/activity-service/internal/service/wheel/service_test.go index b4aec8ea..bff56fd8 100644 --- a/services/activity-service/internal/service/wheel/service_test.go +++ b/services/activity-service/internal/service/wheel/service_test.go @@ -2,6 +2,8 @@ package wheel import ( "context" + "errors" + "strings" "testing" "time" @@ -96,11 +98,35 @@ func TestDrawFulfillsBatchCoinAndResourceRewards(t *testing.T) { if result.CoinBalanceAfter != 9900 { t.Fatalf("coin balance mismatch: %d", result.CoinBalanceAfter) } + if len(result.WalletTransactionID) > 128 { + t.Fatalf("batch reward receipt reference exceeds wheel_draw_records boundary: %d", len(result.WalletTransactionID)) + } +} + +func TestWheelRewardReceiptReferenceCompactsMultiRewardReceipts(t *testing.T) { + receipts := []string{ + "wtx_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "rgr_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + } + first := wheelRewardReceiptReference(receipts) + second := wheelRewardReceiptReference(receipts) + if first != second || !strings.HasPrefix(first, "batch:2:") || len(first) > 128 { + t.Fatalf("receipt reference must be stable and bounded: first=%q second=%q", first, second) + } } type fakeWheelRepository struct { - executeResult domain.DrawResult - markGrantedDrawIDs []string + executeResult domain.DrawResult + markGrantedDrawIDs []string + claimRecords []domain.RewardOutboxRecord + claimApps []string + deliveredOutboxID string + deliveredWorkerID string + deliveredDrawIDs []string + deliveredTransactionID string + deliverErr error + failedOutboxID string + failedReason string } func (r *fakeWheelRepository) PublishWheelRuleConfig(context.Context, domain.RuleConfig, int64) (domain.RuleConfig, error) { @@ -128,6 +154,27 @@ func (r *fakeWheelRepository) MarkWheelDrawsGranted(_ context.Context, _ string, return nil } +func (r *fakeWheelRepository) ClaimWheelRewardOutbox(ctx context.Context, _ string, _ int64, _ int64, _ int, _ int) ([]domain.RewardOutboxRecord, error) { + r.claimApps = append(r.claimApps, appcode.FromContext(ctx)) + records := append([]domain.RewardOutboxRecord(nil), r.claimRecords...) + r.claimRecords = nil + return records, nil +} + +func (r *fakeWheelRepository) MarkWheelRewardOutboxDelivered(_ context.Context, outboxID string, workerID string, drawIDs []string, transactionID string, _ int64) error { + r.deliveredOutboxID = outboxID + r.deliveredWorkerID = workerID + r.deliveredDrawIDs = append([]string(nil), drawIDs...) + r.deliveredTransactionID = transactionID + return r.deliverErr +} + +func (r *fakeWheelRepository) MarkWheelRewardOutboxFailed(_ context.Context, outboxID string, _ string, _ int64, _ int64, _ int, lastError string) error { + r.failedOutboxID = outboxID + r.failedReason = lastError + return nil +} + type fakeWheelWallet struct { coinCredits []*walletv1.CreditWheelRewardRequest resourceGrants []*walletv1.GrantResourceRequest @@ -146,3 +193,85 @@ func (w *fakeWheelWallet) GrantResource(_ context.Context, req *walletv1.GrantRe w.resourceGrants = append(w.resourceGrants, req) return &walletv1.ResourceGrantResponse{Grant: &walletv1.ResourceGrant{GrantId: "grant-" + req.GetCommandId()}}, nil } + +func TestProcessPendingRewardsReplaysStableWalletCommandAndMarksDelivered(t *testing.T) { + repository := &fakeWheelRepository{claimRecords: []domain.RewardOutboxRecord{{ + AppCode: "fami", + OutboxID: "wheel_reward_wheel_draw_replay", + // 线上历史 payload 的 rewards 使用 Go 默认字段名;补偿解码必须兼容该不可变快照,不能改读当前奖档。 + Payload: []byte(`{"app_code":"fami","draw_id":"wheel_draw_replay","draw_ids":["wheel_draw_replay"],"command_id":"draw-command","user_id":1001,"wheel_id":"classic","rewards":[{"SelectedTierID":"tier-1","RewardType":"coin","RewardCoins":200}],"visible_region_id":9}`), + }}} + wallet := &fakeWheelWallet{coinBalanceAfter: 1200} + service := New(repository, WithWallet(wallet)) + service.SetClock(func() time.Time { return time.UnixMilli(1700000000000).UTC() }) + + result, err := service.ProcessPendingRewards(context.Background(), RewardWorkerOptions{ + WorkerID: "activity-1:wheel-reward", + AppCodes: []string{"fami"}, + }) + if err != nil { + t.Fatalf("ProcessPendingRewards returned error: %v", err) + } + if result.Claimed != 1 || result.Delivered != 1 || result.Failed != 0 { + t.Fatalf("unexpected batch result: %+v", result) + } + if len(wallet.coinCredits) != 1 { + t.Fatalf("wallet credit count = %d, want 1", len(wallet.coinCredits)) + } + request := wallet.coinCredits[0] + if request.GetCommandId() != "wheel_reward:wheel_draw_replay" || request.GetAppCode() != "fami" || request.GetTargetUserId() != 1001 || request.GetAmount() != 200 { + t.Fatalf("wallet reconciliation command mismatch: %+v", request) + } + if repository.deliveredOutboxID != "wheel_reward_wheel_draw_replay" || repository.deliveredWorkerID != "activity-1:wheel-reward" || repository.deliveredTransactionID != "tx-wheel_reward:wheel_draw_replay" { + t.Fatalf("delivered marker mismatch: %+v", repository) + } + if len(repository.deliveredDrawIDs) != 1 || repository.deliveredDrawIDs[0] != "wheel_draw_replay" { + t.Fatalf("delivered draw ids mismatch: %+v", repository.deliveredDrawIDs) + } + if len(repository.claimApps) != 1 || repository.claimApps[0] != "fami" { + t.Fatalf("claim must use explicit app partition, got %+v", repository.claimApps) + } +} + +func TestProcessPendingRewardsRejectsCrossAppPayloadBeforeWallet(t *testing.T) { + repository := &fakeWheelRepository{claimRecords: []domain.RewardOutboxRecord{{ + AppCode: "fami", + OutboxID: "wheel_reward_cross_app", + Payload: []byte(`{"app_code":"huwaa","draw_id":"cross_app","command_id":"cmd","user_id":1001,"wheel_id":"classic","reward_type":"coin","reward_coins":200,"selected_tier_id":"tier-1"}`), + }}} + wallet := &fakeWheelWallet{} + service := New(repository, WithWallet(wallet)) + service.SetClock(func() time.Time { return time.UnixMilli(1700000000000).UTC() }) + + result, err := service.ProcessPendingRewards(context.Background(), RewardWorkerOptions{WorkerID: "worker", AppCodes: []string{"fami"}}) + if err == nil || result.Claimed != 1 || result.Failed != 1 || result.Delivered != 0 { + t.Fatalf("cross-app payload must fail closed, result=%+v err=%v", result, err) + } + if len(wallet.coinCredits) != 0 || len(wallet.resourceGrants) != 0 { + t.Fatalf("cross-app payload must not reach wallet: coin=%d resource=%d", len(wallet.coinCredits), len(wallet.resourceGrants)) + } + if repository.failedOutboxID != "wheel_reward_cross_app" || repository.failedReason == "" { + t.Fatalf("failed outbox marker missing: %+v", repository) + } +} + +func TestProcessPendingRewardsLeavesCompletedWalletCommandForLeaseRecoveryWhenDeliveryMarkFails(t *testing.T) { + repository := &fakeWheelRepository{ + deliverErr: errors.New("mysql unavailable"), + claimRecords: []domain.RewardOutboxRecord{{ + AppCode: "lalu", + OutboxID: "wheel_reward_mark_failure", + Payload: []byte(`{"app_code":"lalu","draw_id":"mark_failure","command_id":"cmd","user_id":1001,"wheel_id":"classic","reward_type":"coin","reward_coins":20,"selected_tier_id":"tier-1"}`), + }}, + } + wallet := &fakeWheelWallet{} + service := New(repository, WithWallet(wallet)) + + result, err := service.ProcessPendingRewards(context.Background(), RewardWorkerOptions{WorkerID: "worker", AppCodes: []string{"lalu"}}) + if err == nil || result.Failed != 1 || len(wallet.coinCredits) != 1 { + t.Fatalf("delivery mark failure mismatch: result=%+v wallet=%d err=%v", result, len(wallet.coinCredits), err) + } + if repository.failedOutboxID != "" { + t.Fatalf("lost lease/delivery failure must stay delivering for safe idempotent reclaim, got failed marker %q", repository.failedOutboxID) + } +} diff --git a/services/activity-service/internal/service/wheel/worker.go b/services/activity-service/internal/service/wheel/worker.go new file mode 100644 index 00000000..b8de8c7f --- /dev/null +++ b/services/activity-service/internal/service/wheel/worker.go @@ -0,0 +1,218 @@ +package wheel + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log/slog" + "strings" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/logx" + "hyapp/pkg/xerr" + domain "hyapp/services/activity-service/internal/domain/wheel" +) + +// RewardWorkerOptions 定义补偿 worker 的租户、锁和重试边界。 +type RewardWorkerOptions struct { + WorkerID string + AppCodes []string + PollInterval time.Duration + BatchSize int + LockTTL time.Duration + MaxRetry int +} + +// RewardWorkerBatchResult 只统计本轮 claim 后的终态;失败项已经释放为 retryable/failed,不会静默丢失。 +type RewardWorkerBatchResult struct { + Claimed int + Delivered int + Failed int +} + +type wheelRewardOutboxPayload struct { + AppCode string `json:"app_code"` + DrawID string `json:"draw_id"` + DrawIDs []string `json:"draw_ids"` + CommandID string `json:"command_id"` + UserID int64 `json:"user_id"` + WheelID string `json:"wheel_id"` + SelectedTierID string `json:"selected_tier_id"` + RewardType string `json:"reward_type"` + RewardID string `json:"reward_id"` + RewardCount int64 `json:"reward_count"` + RewardCoins int64 `json:"reward_coins"` + Rewards []domain.DrawReward `json:"rewards"` + VisibleRegionID int64 `json:"visible_region_id"` +} + +// RunRewardWorker 持续逐 App 分区补偿 WheelRewardSettlement。 +// AppCode 必须写进 context 后再 claim 和调用钱包,避免后台根 context 被 appcode 默认值悄悄收敛成 lalu。 +func (s *Service) RunRewardWorker(ctx context.Context, options RewardWorkerOptions) { + options = normalizeRewardWorkerOptions(options) + ticker := time.NewTicker(options.PollInterval) + defer ticker.Stop() + for { + if _, err := s.ProcessPendingRewards(ctx, options); err != nil && ctx.Err() == nil { + logx.Error(ctx, "wheel_reward_outbox_batch_failed", err, slog.String("worker_id", options.WorkerID)) + } + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + } +} + +// ProcessPendingRewards 执行一轮有界补偿。 +// 钱包 RPC 使用 draw_id 派生的稳定 command_id;钱包 owner 会在同一事务内先查既有交易/权益及 request hash,命中则返回原收据,未命中才创建,因此 activity 只在收齐所有收据后标 delivered。 +func (s *Service) ProcessPendingRewards(ctx context.Context, options RewardWorkerOptions) (RewardWorkerBatchResult, error) { + if err := s.requireRepository(); err != nil { + return RewardWorkerBatchResult{}, err + } + options = normalizeRewardWorkerOptions(options) + var result RewardWorkerBatchResult + var batchErr error + for _, appCodeValue := range options.AppCodes { + if ctx.Err() != nil { + return result, errors.Join(batchErr, ctx.Err()) + } + appCtx := appcode.WithContext(ctx, appCodeValue) + nowMS := s.now().UTC().UnixMilli() + records, err := s.repository.ClaimWheelRewardOutbox(appCtx, options.WorkerID, nowMS, nowMS+options.LockTTL.Milliseconds(), options.BatchSize, options.MaxRetry) + if err != nil { + batchErr = errors.Join(batchErr, fmt.Errorf("claim app=%s: %w", appCodeValue, err)) + continue + } + result.Claimed += len(records) + for _, record := range records { + if err := s.processRewardOutboxRecord(appCtx, record, options); err != nil { + result.Failed++ + batchErr = errors.Join(batchErr, fmt.Errorf("process app=%s outbox=%s: %w", appCodeValue, record.OutboxID, err)) + continue + } + result.Delivered++ + } + } + return result, batchErr +} + +func (s *Service) processRewardOutboxRecord(ctx context.Context, record domain.RewardOutboxRecord, options RewardWorkerOptions) error { + payload, err := decodeWheelRewardOutboxPayload(record) + if err == nil { + var fulfilled domain.DrawResult + fulfilled, err = s.fulfillWheelReward(ctx, domain.DrawCommand{ + CommandID: payload.CommandID, + WheelID: payload.WheelID, + UserID: payload.UserID, + VisibleRegionID: payload.VisibleRegionID, + }, domain.DrawResult{ + DrawID: payload.DrawID, + DrawIDs: payload.DrawIDs, + CommandID: payload.CommandID, + WheelID: payload.WheelID, + SelectedTierID: payload.SelectedTierID, + RewardType: payload.RewardType, + RewardID: payload.RewardID, + RewardCount: payload.RewardCount, + RewardCoins: payload.RewardCoins, + RewardStatus: domain.StatusPending, + Rewards: payload.Rewards, + }) + if err == nil { + drawIDs := payload.DrawIDs + if len(drawIDs) == 0 { + drawIDs = []string{payload.DrawID} + } + // draw、统计和 outbox 的最终提交带 worker fencing token;若 lease 已被接管,这里会回滚而不是覆盖新 worker。 + return s.repository.MarkWheelRewardOutboxDelivered(ctx, record.OutboxID, options.WorkerID, drawIDs, fulfilled.WalletTransactionID, s.now().UTC().UnixMilli()) + } + } + + nowMS := s.now().UTC().UnixMilli() + nextRetryAtMS := nowMS + wheelRewardRetryDelay(record.RetryCount+1).Milliseconds() + markErr := s.repository.MarkWheelRewardOutboxFailed(ctx, record.OutboxID, options.WorkerID, nowMS, nextRetryAtMS, options.MaxRetry, err.Error()) + if markErr != nil { + return errors.Join(err, fmt.Errorf("mark wheel reward retryable: %w", markErr)) + } + return err +} + +func decodeWheelRewardOutboxPayload(record domain.RewardOutboxRecord) (wheelRewardOutboxPayload, error) { + var payload wheelRewardOutboxPayload + if err := json.Unmarshal(record.Payload, &payload); err != nil { + return payload, fmt.Errorf("decode wheel reward payload: %w", err) + } + recordApp := appcode.Normalize(record.AppCode) + payloadApp := appcode.Normalize(payload.AppCode) + if payload.AppCode != "" && payloadApp != recordApp { + return payload, xerr.New(xerr.Conflict, "wheel reward payload app_code does not match outbox partition") + } + payload.AppCode = recordApp + payload.DrawID = strings.TrimSpace(payload.DrawID) + payload.CommandID = strings.TrimSpace(payload.CommandID) + payload.WheelID = strings.TrimSpace(payload.WheelID) + if payload.DrawID == "" || payload.CommandID == "" || payload.WheelID == "" || payload.UserID <= 0 { + return payload, xerr.New(xerr.InvalidArgument, "wheel reward payload identity is incomplete") + } + if len(payload.DrawIDs) == 0 { + payload.DrawIDs = []string{payload.DrawID} + } + for index := range payload.DrawIDs { + payload.DrawIDs[index] = strings.TrimSpace(payload.DrawIDs[index]) + if payload.DrawIDs[index] == "" { + return payload, xerr.New(xerr.InvalidArgument, "wheel reward payload contains empty draw_id") + } + } + return payload, nil +} + +func normalizeRewardWorkerOptions(options RewardWorkerOptions) RewardWorkerOptions { + options.WorkerID = strings.TrimSpace(options.WorkerID) + if options.WorkerID == "" { + options.WorkerID = "activity-wheel-reward" + } + seen := make(map[string]struct{}, len(options.AppCodes)) + appCodes := make([]string, 0, len(options.AppCodes)) + for _, value := range options.AppCodes { + value = appcode.Normalize(value) + if _, exists := seen[value]; exists { + continue + } + seen[value] = struct{}{} + appCodes = append(appCodes, value) + } + if len(appCodes) == 0 { + appCodes = []string{"lalu", "fami", "huwaa"} + } + options.AppCodes = appCodes + if options.PollInterval <= 0 { + options.PollInterval = time.Second + } + if options.BatchSize <= 0 { + options.BatchSize = 50 + } + if options.LockTTL <= 0 { + options.LockTTL = 30 * time.Second + } + if options.MaxRetry <= 0 { + options.MaxRetry = 16 + } + return options +} + +func wheelRewardRetryDelay(attempt int) time.Duration { + if attempt < 1 { + attempt = 1 + } + if attempt > 7 { + attempt = 7 + } + delay := time.Second * time.Duration(1<<(attempt-1)) + if delay > time.Minute { + return time.Minute + } + return delay +} diff --git a/services/activity-service/internal/storage/mysql/broadcast_repository.go b/services/activity-service/internal/storage/mysql/broadcast_repository.go index 6d166451..b51073f4 100644 --- a/services/activity-service/internal/storage/mysql/broadcast_repository.go +++ b/services/activity-service/internal/storage/mysql/broadcast_repository.go @@ -54,6 +54,25 @@ func (r *Repository) SaveBroadcastOutbox(ctx context.Context, record broadcastdo return false, existing, nil } +// ListBroadcastOutboxAppCodes 枚举实际存在的租户键,常驻 worker 再按 App 使用现有 claim 前缀索引。 +// 主键以 app_code 开头,DISTINCT 只枚举少量租户,不需要为了跨 App 投递移除 app_code 条件扫描热表。 +func (r *Repository) ListBroadcastOutboxAppCodes(ctx context.Context) ([]string, error) { + rows, err := r.db.QueryContext(ctx, `SELECT DISTINCT app_code FROM im_broadcast_outbox ORDER BY app_code`) + if err != nil { + return nil, err + } + defer rows.Close() + appCodes := make([]string, 0, 4) + for rows.Next() { + var appCode string + if err := rows.Scan(&appCode); err != nil { + return nil, err + } + appCodes = append(appCodes, appCode) + } + return appCodes, rows.Err() +} + // GetBroadcastOutbox 按幂等键读取一条播报 outbox,测试和后台排障都会用到。 func (r *Repository) GetBroadcastOutbox(ctx context.Context, eventID string) (broadcastdomain.OutboxRecord, bool, error) { row := r.db.QueryRowContext(ctx, ` diff --git a/services/activity-service/internal/storage/mysql/growth_repository.go b/services/activity-service/internal/storage/mysql/growth_repository.go index 054810ba..f3c09d8d 100644 --- a/services/activity-service/internal/storage/mysql/growth_repository.go +++ b/services/activity-service/internal/storage/mysql/growth_repository.go @@ -6,16 +6,26 @@ import ( "encoding/json" "errors" "fmt" + "hash/fnv" "strconv" "strings" "time" + mysqldriver "github.com/go-sql-driver/mysql" "hyapp/pkg/appcode" "hyapp/pkg/idgen" "hyapp/pkg/xerr" domain "hyapp/services/activity-service/internal/domain/growth" ) +const ( + // MySQL 会主动回滚死锁受害事务;重试必须从 BeginTx 开始重建完整事务,不能在旧 tx 上续跑。 + growthTransactionMaxAttempts = 5 + // 四次等待分别以 10/20/30/40ms 为基线,再加入 0-10ms 的稳定抖动,既限制请求预算也避免热点事件同步重撞。 + growthTransactionRetryStep = 10 * time.Millisecond + growthTransactionRetryJitterWindow = 10 * time.Millisecond +) + type queryer interface { QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row @@ -118,9 +128,58 @@ func (r *Repository) BatchGetUserLevelDisplayProfiles(ctx context.Context, userI if err := rows.Err(); err != nil { return domain.LevelDisplayProfiles{}, err } + adminProfiles, err := r.BatchGetUserLevelAdminProfiles(ctx, userIDs, nowMS) + if err != nil { + return domain.LevelDisplayProfiles{}, err + } + adminTracks := make(map[int64]map[string]domain.AdminTrackProfile, len(adminProfiles.Profiles)) + for _, profile := range adminProfiles.Profiles { + byTrack := make(map[string]domain.AdminTrackProfile, len(profile.Tracks)) + for _, track := range profile.Tracks { + byTrack[track.Track] = track + } + adminTracks[profile.UserID] = byTrack + } + rulesByTrack := make(map[string][]domain.Rule, 2) + tiersByTrack := make(map[string][]domain.Tier, 2) + for _, track := range []string{domain.TrackWealth, domain.TrackCharm} { + rules, err := r.listActiveLevelRules(ctx, r.db, track) + if err != nil { + return domain.LevelDisplayProfiles{}, err + } + tiers, err := r.listActiveLevelTiers(ctx, r.db, track) + if err != nil { + return domain.LevelDisplayProfiles{}, err + } + rulesByTrack[track] = rules + tiersByTrack[track] = tiers + } result := make([]domain.UserLevelDisplayProfile, 0, len(userIDs)) for _, userID := range userIDs { - result = append(result, profiles[userID]) + item := profiles[userID] + // 物化资料仍是真实账户快照;整批 admin profile 只在读取时覆盖有效 wealth/charm overlay, + // 既让到期毫秒立即生效,也避免房间资料批量接口按用户和轨道产生 N+1。 + for _, track := range []string{domain.TrackWealth, domain.TrackCharm} { + adminTrack := adminTracks[userID][track] + if adminTrack.TemporaryLevelID == "" { + continue + } + rules := rulesByTrack[track] + tiers := tiersByTrack[track] + tier := tierForLevel(tiers, adminTrack.DisplayLevel) + badgeID, badgeLevel := levelRuleBadgeForLevel(rules, adminTrack.DisplayLevel) + display := domain.LevelDisplayTrackProfile{ + Track: track, Level: adminTrack.DisplayLevel, TierID: tier.TierID, + AvatarFrameResourceID: levelRuleAvatarFrameForLevel(rules, adminTrack.DisplayLevel, tier.DisplayAvatarFrameResourceID), + BadgeResourceID: badgeID, BadgeSourceLevel: badgeLevel, UpdatedAtMS: nowMS, + } + if track == domain.TrackWealth { + item.Wealth = display + } else { + item.Charm = display + } + } + result = append(result, item) } return domain.LevelDisplayProfiles{Profiles: result, ServerTimeMS: nowMS}, nil } @@ -187,6 +246,35 @@ func (r *Repository) ListLevelRewards(ctx context.Context, query domain.RewardQu // ConsumeLevelEvent 在同一事务内完成事件幂等、累计值、升级历史、奖励任务和展示投影。 func (r *Repository) ConsumeLevelEvent(ctx context.Context, event domain.ValueEvent, nowMS int64) (domain.EventResult, error) { + var lastErr error + for attempt := 0; attempt < growthTransactionMaxAttempts; attempt++ { + result, err := r.consumeLevelEventOnce(ctx, event, nowMS) + if err == nil { + return result, nil + } + if !isRetryableGrowthTransactionError(err) { + return domain.EventResult{}, err + } + lastErr = err + if attempt == growthTransactionMaxAttempts-1 { + break + } + // MQ 会并发回放同一热门用户的礼物事件;本地短退避先吸收瞬时死锁冲突,避免每次 1213 都升级为 broker 秒级重试。 + timer := time.NewTimer(growthTransactionRetryDelay(event.EventID, attempt)) + select { + case <-ctx.Done(): + if !timer.Stop() { + <-timer.C + } + return domain.EventResult{}, ctx.Err() + case <-timer.C: + } + } + return domain.EventResult{}, lastErr +} + +// consumeLevelEventOnce 只执行一次完整事务;调用方只会在 MySQL 已回滚的 1213 上重新 BeginTx。 +func (r *Repository) consumeLevelEventOnce(ctx context.Context, event domain.ValueEvent, nowMS int64) (domain.EventResult, error) { if r == nil || r.db == nil { return domain.EventResult{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") } @@ -248,6 +336,25 @@ func (r *Repository) ConsumeLevelEvent(ctx context.Context, event domain.ValueEv } rewardJobCount = count } + // While an overlay is active, only value events whose business time falls in [start,end) advance its displayed + // value. The real account above was updated independently and remains the sole permanent growth fact. + if temporary, found, err := r.activeTemporaryLevelForUpdate(ctx, tx, event.UserID, event.Track, nowMS); err != nil { + return domain.EventResult{}, err + } else if found && event.OccurredAtMS >= temporary.StartedAtMS && event.OccurredAtMS < temporary.ExpiresAtMS { + windowValue, err := r.temporaryWindowValue(ctx, tx, event.UserID, event.Track, temporary.StartedAtMS, temporary.ExpiresAtMS) + if err != nil { + return domain.EventResult{}, err + } + previousDisplayLevel := levelForValue(rules, temporary.TargetRequiredValue+windowValue-event.ValueDelta) + nextDisplayLevel := levelForValue(rules, temporary.TargetRequiredValue+windowValue) + if nextDisplayLevel > previousDisplayLevel { + count, err := r.createTemporaryRewardJobs(ctx, tx, event.UserID, event.Track, nextDisplayLevel, temporary.TemporaryLevelID, temporary.Generation, temporary.ExpiresAtMS, rules, tiers, nowMS) + if err != nil { + return domain.EventResult{}, err + } + rewardJobCount += count + } + } badgeResourceID, badgeSourceLevel := levelRuleBadgeForLevel(rules, nextLevel) avatarFrameResourceID := levelRuleAvatarFrameForLevel(rules, nextLevel, nextTier.DisplayAvatarFrameResourceID) if err := r.upsertLevelDisplayProfile(ctx, tx, event.UserID, event.Track, nextLevel, nextTier, avatarFrameResourceID, badgeResourceID, badgeSourceLevel, nowMS); err != nil { @@ -500,11 +607,12 @@ func (r *Repository) ClaimPendingLevelRewardJobs(ctx context.Context, workerID s } defer func() { _ = tx.Rollback() }() rows, err := tx.QueryContext(ctx, levelRewardSelectSQL()+` - WHERE app_code = ? AND status IN ('pending', 'failed') AND next_retry_at_ms <= ? + WHERE app_code = ? AND status IN ('pending', 'failed', 'running') AND next_retry_at_ms <= ? + AND (reward_origin = 'organic' OR is_permanent = 1 OR temporary_expires_at_ms > ?) AND (locked_until_ms = 0 OR locked_until_ms <= ?) ORDER BY created_at_ms ASC, reward_job_id ASC LIMIT ? FOR UPDATE SKIP LOCKED`, - appcode.FromContext(ctx), nowMS, nowMS, batchSize, + appcode.FromContext(ctx), nowMS, nowMS, nowMS, batchSize, ) if err != nil { return nil, err @@ -531,32 +639,141 @@ func (r *Repository) ClaimPendingLevelRewardJobs(ctx context.Context, workerID s return jobs, nil } -func (r *Repository) MarkLevelRewardGranted(ctx context.Context, rewardJobID string, walletGrantID string, nowMS int64) error { +func (r *Repository) MarkLevelRewardGranted(ctx context.Context, rewardJobID string, grants []domain.RewardWalletGrant, nowMS int64) error { if r == nil || r.db == nil { return xerr.New(xerr.Unavailable, "mysql repository is not configured") } - _, err := r.db.ExecContext(ctx, ` + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + firstGrantID := "" + for _, grant := range grants { + if strings.TrimSpace(grant.WalletGrantID) == "" { + continue + } + if firstGrantID == "" { + firstGrantID = grant.WalletGrantID + } + // Each external action has its own wallet command. INSERT IGNORE makes a crash after wallet success safe to replay. + _, err := tx.ExecContext(ctx, ` + INSERT IGNORE INTO growth_level_reward_wallet_grants + (app_code, reward_job_id, grant_kind, wallet_grant_id, revoke_status, created_at_ms, updated_at_ms) + VALUES (?, ?, ?, ?, 'none', ?, ?)`, + appcode.FromContext(ctx), rewardJobID, grant.GrantKind, grant.WalletGrantID, nowMS, nowMS) + if err != nil { + return err + } + } + _, err = tx.ExecContext(ctx, ` UPDATE growth_level_reward_jobs SET status = 'granted', wallet_grant_id = ?, locked_by = '', locked_until_ms = 0, failure_reason = '', updated_at_ms = ? WHERE app_code = ? AND reward_job_id = ?`, - walletGrantID, nowMS, appcode.FromContext(ctx), rewardJobID, + firstGrantID, nowMS, appcode.FromContext(ctx), rewardJobID, ) - return err + if err != nil { + return err + } + var temporaryID string + var rewardLevel int32 + var permanent bool + if err := tx.QueryRowContext(ctx, `SELECT temporary_level_id, reward_level, is_permanent FROM growth_level_reward_jobs WHERE app_code = ? AND reward_job_id = ? FOR UPDATE`, appcode.FromContext(ctx), rewardJobID).Scan(&temporaryID, &rewardLevel, &permanent); err != nil { + return err + } + if temporaryID != "" && !permanent { + var status string + var finalLevel int32 + if err := tx.QueryRowContext(ctx, `SELECT status, final_level FROM growth_temporary_levels WHERE app_code = ? AND temporary_level_id = ?`, appcode.FromContext(ctx), temporaryID).Scan(&status, &finalLevel); err != nil { + return err + } + if status != domain.TemporaryLevelStatusActive && rewardLevel > finalLevel { + // A worker can finish after the overlay expires. Persist the grants, then immediately expose them to revoke worker. + revokeStatus := domain.RevokeStatusPending + if firstGrantID == "" { + revokeStatus = domain.RevokeStatusSkipped + } + if _, err := tx.ExecContext(ctx, `UPDATE growth_level_reward_jobs SET revoke_status = ?, updated_at_ms = ? WHERE app_code = ? AND reward_job_id = ?`, revokeStatus, nowMS, appcode.FromContext(ctx), rewardJobID); err != nil { + return err + } + if _, err := tx.ExecContext(ctx, `UPDATE growth_level_reward_wallet_grants SET revoke_status = 'pending', updated_at_ms = ? WHERE app_code = ? AND reward_job_id = ?`, nowMS, appcode.FromContext(ctx), rewardJobID); err != nil { + return err + } + } + } + return tx.Commit() } -func (r *Repository) MarkLevelRewardFailed(ctx context.Context, rewardJobID string, failureReason string, nextRetryAtMS int64, nowMS int64) error { +func (r *Repository) MarkLevelRewardFailed(ctx context.Context, rewardJobID string, grants []domain.RewardWalletGrant, failureReason string, nextRetryAtMS int64, nowMS int64) error { if r == nil || r.db == nil { return xerr.New(xerr.Unavailable, "mysql repository is not configured") } - _, err := r.db.ExecContext(ctx, ` + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + for _, grant := range grants { + if strings.TrimSpace(grant.WalletGrantID) == "" { + continue + } + // Partial wallet success is a durable revoke fact even though the parent reward remains retryable. + if _, err := tx.ExecContext(ctx, ` + INSERT IGNORE INTO growth_level_reward_wallet_grants + (app_code, reward_job_id, grant_kind, wallet_grant_id, revoke_status, created_at_ms, updated_at_ms) + VALUES (?, ?, ?, ?, 'none', ?, ?)`, + appcode.FromContext(ctx), rewardJobID, grant.GrantKind, grant.WalletGrantID, nowMS, nowMS); err != nil { + return err + } + } + result, err := tx.ExecContext(ctx, ` UPDATE growth_level_reward_jobs SET status = 'failed', next_retry_at_ms = ?, locked_by = '', locked_until_ms = 0, failure_reason = ?, updated_at_ms = ? - WHERE app_code = ? AND reward_job_id = ? AND status <> 'granted'`, + WHERE app_code = ? AND reward_job_id = ? AND status = 'running'`, nextRetryAtMS, truncateGrowthFailure(failureReason), nowMS, appcode.FromContext(ctx), rewardJobID, ) - return err + if err != nil { + return err + } + if affected, err := result.RowsAffected(); err != nil { + return err + } else if affected == 0 { + return xerr.New(xerr.Conflict, "level reward job is no longer running") + } + var temporaryID string + var rewardLevel int32 + var permanent bool + if err := tx.QueryRowContext(ctx, `SELECT temporary_level_id, reward_level, is_permanent FROM growth_level_reward_jobs WHERE app_code = ? AND reward_job_id = ? FOR UPDATE`, appcode.FromContext(ctx), rewardJobID).Scan(&temporaryID, &rewardLevel, &permanent); err != nil { + return err + } + if temporaryID != "" && !permanent { + var status string + var finalLevel int32 + if err := tx.QueryRowContext(ctx, `SELECT status, final_level FROM growth_temporary_levels WHERE app_code = ? AND temporary_level_id = ?`, appcode.FromContext(ctx), temporaryID).Scan(&status, &finalLevel); err != nil { + return err + } + if status != domain.TemporaryLevelStatusActive && rewardLevel > finalLevel { + // Expiry/supersede can race the wallet calls. If it already won, expose the successful prefix directly to + // the revoke worker instead of leaving a failed parent whose assets are invisible forever. + var persistedGrantCount int + if err := tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM growth_level_reward_wallet_grants WHERE app_code = ? AND reward_job_id = ?`, appcode.FromContext(ctx), rewardJobID).Scan(&persistedGrantCount); err != nil { + return err + } + revokeStatus := domain.RevokeStatusSkipped + if persistedGrantCount > 0 { + revokeStatus = domain.RevokeStatusPending + } + if _, err := tx.ExecContext(ctx, `UPDATE growth_level_reward_jobs SET status = 'canceled', revoke_status = ?, updated_at_ms = ? WHERE app_code = ? AND reward_job_id = ?`, revokeStatus, nowMS, appcode.FromContext(ctx), rewardJobID); err != nil { + return err + } + if _, err := tx.ExecContext(ctx, `UPDATE growth_level_reward_wallet_grants SET revoke_status = 'pending', revoke_next_retry_at_ms = 0, updated_at_ms = ? WHERE app_code = ? AND reward_job_id = ? AND revoke_status IN ('none','failed')`, nowMS, appcode.FromContext(ctx), rewardJobID); err != nil { + return err + } + } + } + return tx.Commit() } func (r *Repository) levelTrackOverview(ctx context.Context, userID int64, track domain.Track, nowMS int64) (domain.TrackOverview, error) { @@ -575,10 +792,25 @@ func (r *Repository) levelTrackOverview(ctx context.Context, userID int64, track if err != nil { return domain.TrackOverview{}, err } - tier := tierByID(tiers, account.CurrentTierID) - badgeResourceID, badgeSourceLevel := levelRuleBadgeForLevel(rules, account.CurrentLevel) - avatarFrameResourceID := levelRuleAvatarFrameForLevel(rules, account.CurrentLevel, tier.DisplayAvatarFrameResourceID) - currentRequired, nextLevel, nextRequired := levelProgress(rules, account.CurrentLevel) + displayLevel := account.CurrentLevel + displayValue := account.TotalValue + if temporary, found, err := r.activeTemporaryLevelForRead(ctx, userID, track.Track, nowMS); err != nil { + return domain.TrackOverview{}, err + } else if found { + increment, err := r.temporaryWindowValue(ctx, r.db, userID, track.Track, temporary.StartedAtMS, temporary.ExpiresAtMS) + if err != nil { + return domain.TrackOverview{}, err + } + displayValue = temporary.TargetRequiredValue + increment + displayLevel = levelForValue(rules, displayValue) + if displayLevel < temporary.TargetLevel { + displayLevel = temporary.TargetLevel + } + } + tier := tierForLevel(tiers, displayLevel) + badgeResourceID, badgeSourceLevel := levelRuleBadgeForLevel(rules, displayLevel) + avatarFrameResourceID := levelRuleAvatarFrameForLevel(rules, displayLevel, tier.DisplayAvatarFrameResourceID) + currentRequired, nextLevel, nextRequired := levelProgress(rules, displayLevel) pendingCount, err := r.pendingLevelRewardCount(ctx, userID, track.Track) if err != nil { return domain.TrackOverview{}, err @@ -586,9 +818,9 @@ func (r *Repository) levelTrackOverview(ctx context.Context, userID int64, track return domain.TrackOverview{ Track: track.Track, Name: track.Name, - Level: account.CurrentLevel, - TierID: account.CurrentTierID, - TotalValue: account.TotalValue, + Level: displayLevel, + TierID: tier.TierID, + TotalValue: displayValue, CurrentLevelRequiredValue: currentRequired, NextLevel: nextLevel, NextLevelRequiredValue: nextRequired, @@ -619,11 +851,15 @@ func (r *Repository) insertLevelValueEvent(ctx context.Context, tx *sql.Tx, even } func (r *Repository) ensureLevelAccountForUpdate(ctx context.Context, tx *sql.Tx, userID int64, track string, nowMS int64) (domain.Account, error) { + // INSERT IGNORE 命中已有主键时会先持有共享记录锁,随后 FOR UPDATE 再升级排他锁;并发礼物落到同一 + // (app_code,user_id,track) 时,多个事务会各持 S 锁并互等 X 锁。空更新 upsert 从第一条语句就取得 X 锁, + // 让热点账户串行进入后续读改写,同时不改变账户累计值或更新时间。 _, err := tx.ExecContext(ctx, ` - INSERT IGNORE INTO user_growth_level_accounts ( + INSERT INTO user_growth_level_accounts ( app_code, user_id, track, total_value, current_level, current_tier_id, level_updated_at_ms, tier_updated_at_ms, version, updated_at_ms - ) VALUES (?, ?, ?, 0, 0, 0, 0, 0, 0, ?)`, + ) VALUES (?, ?, ?, 0, 0, 0, 0, 0, 0, ?) + ON DUPLICATE KEY UPDATE user_id = VALUES(user_id)`, appcode.FromContext(ctx), userID, track, nowMS, ) if err != nil { @@ -640,6 +876,28 @@ func (r *Repository) ensureLevelAccountForUpdate(ctx context.Context, tx *sql.Tx return scanLevelAccount(row) } +func isRetryableGrowthTransactionError(err error) bool { + var mysqlErr *mysqldriver.MySQLError + if !errors.As(err, &mysqlErr) { + return false + } + // 1213 会立即回滚死锁受害事务,适合短退避后本地重建。1205 可能已等待完整的 + // innodb_lock_wait_timeout;在无 deadline 的 MQ 回调里重试它会让唯一消费协程继续占用数分钟,应交给 broker 延迟重投。 + return mysqlErr.Number == 1213 +} + +func growthTransactionRetryDelay(eventID string, attempt int) time.Duration { + if attempt < 0 { + attempt = 0 + } + hash := fnv.New32a() + _, _ = hash.Write([]byte(eventID)) + _, _ = hash.Write([]byte{byte(attempt)}) + jitterSlots := uint32(growthTransactionRetryJitterWindow/time.Millisecond) + 1 + jitter := time.Duration(hash.Sum32()%jitterSlots) * time.Millisecond + return time.Duration(attempt+1)*growthTransactionRetryStep + jitter +} + func (r *Repository) updateLevelAccount(ctx context.Context, tx *sql.Tx, userID int64, track string, totalValue int64, level int32, tierID int64, levelUpdatedAtMS int64, tierUpdatedAtMS int64, nowMS int64) error { _, err := tx.ExecContext(ctx, ` UPDATE user_growth_level_accounts @@ -692,14 +950,48 @@ func (r *Repository) createLevelRewardJobs(ctx context.Context, tx *sql.Tx, user } func (r *Repository) insertLevelRewardJob(ctx context.Context, tx *sql.Tx, userID int64, track string, sourceType string, sourceID string, resourceGroupID int64, nowMS int64) (bool, error) { + // Natural growth permanently earns only the matching reward attached to the currently active overlay. Superseded, + // expired or already-revoking generations must finish revoke and get a fresh organic job; otherwise multiple old + // grants (or an RPC-timeout revoke that actually succeeded) could all be promoted into duplicate permanent assets. + promoted, err := tx.ExecContext(ctx, ` + UPDATE growth_level_reward_jobs j + JOIN growth_temporary_levels t + ON t.app_code = j.app_code AND t.temporary_level_id = j.temporary_level_id + SET j.is_permanent = 1, j.revoke_status = 'skipped', j.updated_at_ms = ? + WHERE j.app_code = ? AND j.user_id = ? AND j.track = ? + AND j.reward_source_type = ? AND j.reward_source_id = ? + AND j.reward_origin = 'temporary' AND j.is_permanent = 0 + AND j.status IN ('pending','failed','running','granted') AND j.revoke_status = 'none' + AND t.status = 'active' AND t.started_at_ms <= ? AND t.expires_at_ms > ?`, + nowMS, appcode.FromContext(ctx), userID, track, sourceType, sourceID, nowMS, nowMS) + if err != nil { + return false, err + } + if affected, err := promoted.RowsAffected(); err != nil { + return false, err + } else if affected > 0 { + _, err = tx.ExecContext(ctx, ` + UPDATE growth_level_reward_wallet_grants g + JOIN growth_level_reward_jobs j ON j.app_code = g.app_code AND j.reward_job_id = g.reward_job_id + JOIN growth_temporary_levels t ON t.app_code = j.app_code AND t.temporary_level_id = j.temporary_level_id + SET g.revoke_status = 'skipped', g.revoke_locked_by = '', g.revoke_locked_until_ms = 0, g.updated_at_ms = ? + WHERE j.app_code = ? AND j.user_id = ? AND j.track = ? AND j.reward_source_type = ? AND j.reward_source_id = ? + AND j.reward_origin = 'temporary' AND j.is_permanent = 1 + AND t.status = 'active' AND t.started_at_ms <= ? AND t.expires_at_ms > ? + AND g.revoke_status IN ('none','pending','failed')`, + nowMS, appcode.FromContext(ctx), userID, track, sourceType, sourceID, nowMS, nowMS) + return false, err + } rewardJobID := idgen.New("greward") + rewardLevel, _ := levelFromGrowthRewardSourceID(sourceID) result, err := tx.ExecContext(ctx, ` INSERT IGNORE INTO growth_level_reward_jobs ( app_code, reward_job_id, user_id, track, reward_source_type, reward_source_id, - resource_group_id, wallet_command_id, status, next_retry_at_ms, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', 0, ?, ?)`, + resource_group_id, wallet_command_id, reward_origin, grant_generation, reward_level, is_permanent, + status, next_retry_at_ms, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'organic', 0, ?, 1, 'pending', 0, ?, ?)`, appcode.FromContext(ctx), rewardJobID, userID, track, sourceType, sourceID, resourceGroupID, - "growth_level_reward:"+rewardJobID, nowMS, nowMS, + "growth_level_reward:"+rewardJobID, rewardLevel, nowMS, nowMS, ) if err != nil { return false, err @@ -1224,6 +1516,8 @@ func scanLevelReward(row rowScanner) (domain.RewardJob, error) { err := row.Scan( &item.AppCode, &item.RewardJobID, &item.UserID, &item.Track, &item.RewardSourceType, &item.RewardSourceID, &item.ResourceGroupID, &item.WalletCommandID, &item.WalletGrantID, + &item.RewardOrigin, &item.TemporaryLevelID, &item.GrantGeneration, &item.RewardLevel, + &item.Permanent, &item.TemporaryExpiresAtMS, &item.RevokeStatus, &item.Status, &item.AttemptCount, &item.FailureReason, &item.CreatedAtMS, &item.UpdatedAtMS, ) return item, err @@ -1246,7 +1540,8 @@ func levelTierSelectSQL() string { func levelRewardSelectSQL() string { return `SELECT app_code, reward_job_id, user_id, track, reward_source_type, reward_source_id, - resource_group_id, wallet_command_id, wallet_grant_id, status, attempt_count, + resource_group_id, wallet_command_id, wallet_grant_id, reward_origin, temporary_level_id, + grant_generation, reward_level, is_permanent, temporary_expires_at_ms, revoke_status, status, attempt_count, failure_reason, created_at_ms, updated_at_ms FROM growth_level_reward_jobs ` } diff --git a/services/activity-service/internal/storage/mysql/growth_repository_retry_test.go b/services/activity-service/internal/storage/mysql/growth_repository_retry_test.go new file mode 100644 index 00000000..27e50e2d --- /dev/null +++ b/services/activity-service/internal/storage/mysql/growth_repository_retry_test.go @@ -0,0 +1,50 @@ +package mysql + +import ( + "context" + "errors" + "fmt" + "testing" + "time" + + mysqldriver "github.com/go-sql-driver/mysql" +) + +func TestIsRetryableGrowthTransactionError(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + err error + want bool + }{ + {name: "deadlock", err: &mysqldriver.MySQLError{Number: 1213, Message: "deadlock"}, want: true}, + {name: "wrapped deadlock", err: fmt.Errorf("consume growth event: %w", &mysqldriver.MySQLError{Number: 1213, Message: "deadlock"}), want: true}, + {name: "lock wait timeout", err: &mysqldriver.MySQLError{Number: 1205, Message: "lock wait timeout"}, want: false}, + {name: "duplicate key", err: &mysqldriver.MySQLError{Number: 1062, Message: "duplicate"}, want: false}, + {name: "text only", err: errors.New("Error 1213: Deadlock found"), want: false}, + {name: "context canceled", err: context.Canceled, want: false}, + {name: "nil", err: nil, want: false}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + if got := isRetryableGrowthTransactionError(test.err); got != test.want { + t.Fatalf("isRetryableGrowthTransactionError()=%v want=%v err=%v", got, test.want, test.err) + } + }) + } +} + +func TestGrowthTransactionRetryDelayIsBounded(t *testing.T) { + t.Parallel() + + for attempt := 0; attempt < growthTransactionMaxAttempts-1; attempt++ { + delay := growthTransactionRetryDelay("growth-event-1", attempt) + minimum := time.Duration(attempt+1) * growthTransactionRetryStep + maximum := minimum + growthTransactionRetryJitterWindow + if delay < minimum || delay > maximum { + t.Fatalf("attempt %d delay=%s outside [%s,%s]", attempt, delay, minimum, maximum) + } + } +} diff --git a/services/activity-service/internal/storage/mysql/repository.go b/services/activity-service/internal/storage/mysql/repository.go index 51d15743..a8fc6fe9 100644 --- a/services/activity-service/internal/storage/mysql/repository.go +++ b/services/activity-service/internal/storage/mysql/repository.go @@ -84,6 +84,9 @@ func (r *Repository) Migrate(ctx context.Context) error { if err := r.ensureTaskRewardPolicyTables(ctx); err != nil { return err } + if err := r.ensureTemporaryGrowthLevelTables(ctx); err != nil { + return err + } return nil } diff --git a/services/activity-service/internal/storage/mysql/temporary_growth_level_migration.go b/services/activity-service/internal/storage/mysql/temporary_growth_level_migration.go new file mode 100644 index 00000000..be4af60e --- /dev/null +++ b/services/activity-service/internal/storage/mysql/temporary_growth_level_migration.go @@ -0,0 +1,88 @@ +package mysql + +import "context" + +// ensureTemporaryGrowthLevelTables keeps auto-migrated local/test databases compatible with the deploy migration. +// Production rollout still applies the explicit SQL before starting new binaries. +func (r *Repository) ensureTemporaryGrowthLevelTables(ctx context.Context) error { + additions := []struct { + column string + sql string + }{ + {"reward_origin", `ALTER TABLE growth_level_reward_jobs ADD COLUMN reward_origin VARCHAR(32) NOT NULL DEFAULT 'organic' AFTER wallet_grant_id`}, + {"temporary_level_id", `ALTER TABLE growth_level_reward_jobs ADD COLUMN temporary_level_id VARCHAR(96) NOT NULL DEFAULT '' AFTER reward_origin`}, + {"grant_generation", `ALTER TABLE growth_level_reward_jobs ADD COLUMN grant_generation INT NOT NULL DEFAULT 0 AFTER temporary_level_id`}, + {"reward_level", `ALTER TABLE growth_level_reward_jobs ADD COLUMN reward_level INT NOT NULL DEFAULT 0 AFTER grant_generation`}, + {"is_permanent", `ALTER TABLE growth_level_reward_jobs ADD COLUMN is_permanent TINYINT(1) NOT NULL DEFAULT 1 AFTER reward_level`}, + {"temporary_expires_at_ms", `ALTER TABLE growth_level_reward_jobs ADD COLUMN temporary_expires_at_ms BIGINT NOT NULL DEFAULT 0 AFTER is_permanent`}, + {"revoke_status", `ALTER TABLE growth_level_reward_jobs ADD COLUMN revoke_status VARCHAR(32) NOT NULL DEFAULT 'none' AFTER temporary_expires_at_ms`}, + } + for _, addition := range additions { + exists, err := r.columnExists(ctx, "growth_level_reward_jobs", addition.column) + if err != nil { + return err + } + if !exists { + if _, err := r.db.ExecContext(ctx, addition.sql); err != nil { + return err + } + } + } + oldIndex, err := r.indexExists(ctx, "growth_level_reward_jobs", "uk_growth_level_reward_once") + if err != nil { + return err + } + newIndex, err := r.indexExists(ctx, "growth_level_reward_jobs", "uk_growth_level_reward_generation") + if err != nil { + return err + } + if oldIndex { + if _, err := r.db.ExecContext(ctx, `ALTER TABLE growth_level_reward_jobs DROP INDEX uk_growth_level_reward_once`); err != nil { + return err + } + } + if !newIndex { + if _, err := r.db.ExecContext(ctx, `CREATE UNIQUE INDEX uk_growth_level_reward_generation ON growth_level_reward_jobs (app_code, user_id, track, reward_source_type, reward_source_id, reward_origin, grant_generation)`); err != nil { + return err + } + } + statements := []string{ + `CREATE TABLE IF NOT EXISTS growth_level_reward_wallet_grants ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu', reward_job_id VARCHAR(96) NOT NULL, + grant_kind VARCHAR(32) NOT NULL, wallet_grant_id VARCHAR(96) NOT NULL, + revoke_status VARCHAR(32) NOT NULL DEFAULT 'none', revoke_attempt_count INT NOT NULL DEFAULT 0, + revoke_next_retry_at_ms BIGINT NOT NULL DEFAULT 0, revoke_locked_by VARCHAR(128) NOT NULL DEFAULT '', + revoke_locked_until_ms BIGINT NOT NULL DEFAULT 0, revoke_failure_reason VARCHAR(255) NOT NULL DEFAULT '', + revoked_at_ms BIGINT NOT NULL DEFAULT 0, created_at_ms BIGINT NOT NULL, updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, reward_job_id, wallet_grant_id), + KEY idx_growth_reward_grant_revoke (app_code, revoke_status, revoke_next_retry_at_ms, created_at_ms) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS growth_temporary_level_commands ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu', command_id VARCHAR(128) NOT NULL, + request_hash VARCHAR(64) NOT NULL, user_id BIGINT NOT NULL, operator_admin_id BIGINT NOT NULL, + created_at_ms BIGINT NOT NULL, PRIMARY KEY (app_code, command_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS growth_temporary_levels ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu', temporary_level_id VARCHAR(96) NOT NULL, + command_id VARCHAR(128) NOT NULL, user_id BIGINT NOT NULL, track VARCHAR(32) NOT NULL, + target_level INT NOT NULL, target_required_value BIGINT NOT NULL, baseline_total_value BIGINT NOT NULL, + started_at_ms BIGINT NOT NULL, expires_at_ms BIGINT NOT NULL, status VARCHAR(32) NOT NULL, + generation INT NOT NULL, operator_admin_id BIGINT NOT NULL, reason VARCHAR(255) NOT NULL DEFAULT '', + final_level INT NOT NULL DEFAULT 0, activation_notice_status VARCHAR(32) NOT NULL DEFAULT 'pending', + expiry_notice_status VARCHAR(32) NOT NULL DEFAULT 'skipped', attempt_count INT NOT NULL DEFAULT 0, + next_retry_at_ms BIGINT NOT NULL DEFAULT 0, locked_by VARCHAR(128) NOT NULL DEFAULT '', + locked_until_ms BIGINT NOT NULL DEFAULT 0, failure_reason VARCHAR(255) NOT NULL DEFAULT '', + created_at_ms BIGINT NOT NULL, updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, temporary_level_id), + UNIQUE KEY uk_growth_temp_command_track (app_code, command_id, track), + KEY idx_growth_temp_user_track (app_code, user_id, track, status, expires_at_ms), + KEY idx_growth_temp_work (app_code, status, next_retry_at_ms, expires_at_ms, created_at_ms) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + } + for _, statement := range statements { + if _, err := r.db.ExecContext(ctx, statement); err != nil { + return err + } + } + return nil +} diff --git a/services/activity-service/internal/storage/mysql/temporary_growth_level_repository.go b/services/activity-service/internal/storage/mysql/temporary_growth_level_repository.go new file mode 100644 index 00000000..ba8b48dd --- /dev/null +++ b/services/activity-service/internal/storage/mysql/temporary_growth_level_repository.go @@ -0,0 +1,761 @@ +package mysql + +import ( + "context" + "database/sql" + "fmt" + "math" + "strconv" + "strings" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/idgen" + "hyapp/pkg/xerr" + domain "hyapp/services/activity-service/internal/domain/growth" +) + +const temporaryLevelDayMS = int64(24 * time.Hour / time.Millisecond) + +func (r *Repository) activeTemporaryLevelForUpdate(ctx context.Context, tx *sql.Tx, userID int64, track string, nowMS int64) (domain.TemporaryLevel, bool, error) { + row := tx.QueryRowContext(ctx, temporaryLevelSelectSQL()+` + WHERE app_code = ? AND user_id = ? AND track = ? AND status = 'active' + AND started_at_ms <= ? AND expires_at_ms > ? ORDER BY generation DESC LIMIT 1 FOR UPDATE`, + appcode.FromContext(ctx), userID, track, nowMS, nowMS) + var item domain.TemporaryLevel + err := row.Scan(&item.AppCode, &item.TemporaryLevelID, &item.CommandID, &item.UserID, &item.Track, + &item.TargetLevel, &item.TargetRequiredValue, &item.BaselineTotalValue, &item.StartedAtMS, &item.ExpiresAtMS, + &item.Status, &item.Generation, &item.OperatorAdminID, &item.Reason, &item.FinalLevel, + &item.ActivationNoticeStatus, &item.ExpiryNoticeStatus, &item.AttemptCount, &item.FailureReason, + &item.CreatedAtMS, &item.UpdatedAtMS) + if err == sql.ErrNoRows { + return domain.TemporaryLevel{}, false, nil + } + return item, err == nil, err +} + +func (r *Repository) activeTemporaryLevelForRead(ctx context.Context, userID int64, track string, nowMS int64) (domain.TemporaryLevel, bool, error) { + row := r.db.QueryRowContext(ctx, temporaryLevelSelectSQL()+` + WHERE app_code = ? AND user_id = ? AND track = ? AND status = 'active' + AND started_at_ms <= ? AND expires_at_ms > ? ORDER BY generation DESC LIMIT 1`, + appcode.FromContext(ctx), userID, track, nowMS, nowMS) + var item domain.TemporaryLevel + err := row.Scan(&item.AppCode, &item.TemporaryLevelID, &item.CommandID, &item.UserID, &item.Track, + &item.TargetLevel, &item.TargetRequiredValue, &item.BaselineTotalValue, &item.StartedAtMS, &item.ExpiresAtMS, + &item.Status, &item.Generation, &item.OperatorAdminID, &item.Reason, &item.FinalLevel, + &item.ActivationNoticeStatus, &item.ExpiryNoticeStatus, &item.AttemptCount, &item.FailureReason, + &item.CreatedAtMS, &item.UpdatedAtMS) + if err == sql.ErrNoRows { + return domain.TemporaryLevel{}, false, nil + } + return item, err == nil, err +} + +func levelFromGrowthRewardSourceID(sourceID string) (int32, bool) { + value := strings.TrimSpace(sourceID) + if !strings.HasPrefix(value, "level:") { + return 0, false + } + parsed, err := strconv.ParseInt(strings.TrimPrefix(value, "level:"), 10, 32) + if err != nil || parsed < 0 { + return 0, false + } + return int32(parsed), true +} + +// BatchGetUserLevelAdminProfiles 一次读取整批真实账户和有效 overlay。列表页不能按用户×轨道产生 N+1 查询; +// expires_at_ms 仍在 SQL 中按 [start,end) 判定,保证 cron 延迟时读路径也不会继续展示临时等级。 +func (r *Repository) BatchGetUserLevelAdminProfiles(ctx context.Context, userIDs []int64, nowMS int64) (domain.AdminLevelProfiles, error) { + if r == nil || r.db == nil { + return domain.AdminLevelProfiles{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + userIDs = uniquePositiveInt64s(userIDs) + if len(userIDs) == 0 { + return domain.AdminLevelProfiles{Profiles: []domain.AdminUserLevelProfile{}, ServerTimeMS: nowMS}, nil + } + profiles := make([]domain.AdminUserLevelProfile, len(userIDs)) + profileIndex := make(map[int64]int, len(userIDs)) + for index, userID := range userIDs { + profiles[index] = domain.AdminUserLevelProfile{UserID: userID, Tracks: []domain.AdminTrackProfile{ + {Track: domain.TrackWealth}, {Track: domain.TrackGame}, {Track: domain.TrackCharm}, + }, ServerTimeMS: nowMS} + profileIndex[userID] = index + } + trackIndex := map[string]int{domain.TrackWealth: 0, domain.TrackGame: 1, domain.TrackCharm: 2} + args := make([]any, 0, len(userIDs)+1) + args = append(args, appcode.FromContext(ctx)) + for _, userID := range userIDs { + args = append(args, userID) + } + rows, err := r.db.QueryContext(ctx, ` + SELECT user_id, track, total_value, current_level + FROM user_growth_level_accounts + WHERE app_code = ? AND user_id IN (`+placeholders(len(userIDs))+`) + AND track IN ('wealth','game','charm')`, args...) + if err != nil { + return domain.AdminLevelProfiles{}, err + } + for rows.Next() { + var userID int64 + var track string + var totalValue int64 + var level int32 + if err := rows.Scan(&userID, &track, &totalValue, &level); err != nil { + _ = rows.Close() + return domain.AdminLevelProfiles{}, err + } + profilePosition, profileFound := profileIndex[userID] + trackPosition, trackFound := trackIndex[track] + if !profileFound || !trackFound { + continue + } + item := &profiles[profilePosition].Tracks[trackPosition] + item.RealTotalValue = totalValue + item.RealLevel = level + item.DisplayValue = totalValue + item.DisplayLevel = level + } + if err := rows.Err(); err != nil { + _ = rows.Close() + return domain.AdminLevelProfiles{}, err + } + if err := rows.Close(); err != nil { + return domain.AdminLevelProfiles{}, err + } + + rulesByTrack := make(map[string][]domain.Rule, 2) + for _, track := range []string{domain.TrackWealth, domain.TrackCharm} { + rules, err := r.listActiveLevelRules(ctx, r.db, track) + if err != nil { + return domain.AdminLevelProfiles{}, err + } + rulesByTrack[track] = rules + } + overlayArgs := make([]any, 0, len(userIDs)+3) + overlayArgs = append(overlayArgs, appcode.FromContext(ctx), nowMS, nowMS) + for _, userID := range userIDs { + overlayArgs = append(overlayArgs, userID) + } + rows, err = r.db.QueryContext(ctx, ` + SELECT t.user_id, t.track, t.temporary_level_id, t.target_level, t.target_required_value, + t.started_at_ms, t.expires_at_ms, t.generation, COALESCE(SUM(e.value_delta), 0) + FROM growth_temporary_levels t + LEFT JOIN growth_level_value_events e + ON e.app_code = t.app_code AND e.user_id = t.user_id AND e.track = t.track + AND e.occurred_at_ms >= t.started_at_ms AND e.occurred_at_ms < t.expires_at_ms + WHERE t.app_code = ? AND t.status = 'active' AND t.started_at_ms <= ? AND t.expires_at_ms > ? + AND t.user_id IN (`+placeholders(len(userIDs))+`) + GROUP BY t.user_id, t.track, t.temporary_level_id, t.target_level, t.target_required_value, + t.started_at_ms, t.expires_at_ms, t.generation + ORDER BY t.user_id ASC, t.track ASC, t.generation DESC`, overlayArgs...) + if err != nil { + return domain.AdminLevelProfiles{}, err + } + seenOverlay := make(map[string]struct{}, len(userIDs)*2) + for rows.Next() { + var userID int64 + var track string + var temporaryID string + var targetLevel int32 + var targetRequiredValue int64 + var startedAtMS, expiresAtMS int64 + var generation int32 + var windowIncrement int64 + if err := rows.Scan(&userID, &track, &temporaryID, &targetLevel, &targetRequiredValue, &startedAtMS, &expiresAtMS, &generation, &windowIncrement); err != nil { + _ = rows.Close() + return domain.AdminLevelProfiles{}, err + } + key := fmt.Sprintf("%d:%s", userID, track) + if _, exists := seenOverlay[key]; exists { + continue + } + seenOverlay[key] = struct{}{} + profilePosition, profileFound := profileIndex[userID] + trackPosition, trackFound := trackIndex[track] + if !profileFound || !trackFound { + continue + } + item := &profiles[profilePosition].Tracks[trackPosition] + item.TemporaryLevelID = temporaryID + item.TemporaryTargetLevel = targetLevel + item.StartedAtMS = startedAtMS + item.ExpiresAtMS = expiresAtMS + item.DisplayValue = targetRequiredValue + windowIncrement + item.DisplayLevel = levelForValue(rulesByTrack[track], item.DisplayValue) + if item.DisplayLevel < targetLevel { + // 目标规则在下发时已校验并快照阈值;有效期内后续禁用配置不能让已生效 overlay 倒退。 + item.DisplayLevel = targetLevel + } + } + if err := rows.Err(); err != nil { + _ = rows.Close() + return domain.AdminLevelProfiles{}, err + } + if err := rows.Close(); err != nil { + return domain.AdminLevelProfiles{}, err + } + return domain.AdminLevelProfiles{Profiles: profiles, ServerTimeMS: nowMS}, nil +} + +func (r *Repository) adminTrackProfile(ctx context.Context, q queryer, userID int64, track string, nowMS int64) (domain.AdminTrackProfile, error) { + item := domain.AdminTrackProfile{Track: track} + row := q.QueryRowContext(ctx, ` + SELECT total_value, current_level + FROM user_growth_level_accounts + WHERE app_code = ? AND user_id = ? AND track = ?`, appcode.FromContext(ctx), userID, track) + if err := row.Scan(&item.RealTotalValue, &item.RealLevel); err != nil && err != sql.ErrNoRows { + return domain.AdminTrackProfile{}, err + } + item.DisplayLevel = item.RealLevel + item.DisplayValue = item.RealTotalValue + var targetRequired int64 + err := q.QueryRowContext(ctx, ` + SELECT temporary_level_id, target_level, target_required_value, started_at_ms, expires_at_ms + FROM growth_temporary_levels + WHERE app_code = ? AND user_id = ? AND track = ? AND status = 'active' + AND started_at_ms <= ? AND expires_at_ms > ? + ORDER BY generation DESC LIMIT 1`, + appcode.FromContext(ctx), userID, track, nowMS, nowMS, + ).Scan(&item.TemporaryLevelID, &item.TemporaryTargetLevel, &targetRequired, &item.StartedAtMS, &item.ExpiresAtMS) + if err == sql.ErrNoRows { + return item, nil + } + if err != nil { + return domain.AdminTrackProfile{}, err + } + increment, err := r.temporaryWindowValue(ctx, q, userID, track, item.StartedAtMS, item.ExpiresAtMS) + if err != nil { + return domain.AdminTrackProfile{}, err + } + rules, err := r.listActiveLevelRules(ctx, q, track) + if err != nil { + return domain.AdminTrackProfile{}, err + } + item.DisplayValue = targetRequired + increment + item.DisplayLevel = levelForValue(rules, item.DisplayValue) + if item.DisplayLevel < item.TemporaryTargetLevel { + item.DisplayLevel = item.TemporaryTargetLevel + } + return item, nil +} + +func (r *Repository) temporaryWindowValue(ctx context.Context, q queryer, userID int64, track string, startMS int64, endMS int64) (int64, error) { + var value int64 + // occurred_at_ms, not processing time, defines the experience window. Late events outside the window cannot leak into overlay progress. + err := q.QueryRowContext(ctx, ` + SELECT COALESCE(SUM(value_delta), 0) + FROM growth_level_value_events + WHERE app_code = ? AND user_id = ? AND track = ? AND occurred_at_ms >= ? AND occurred_at_ms < ?`, + appcode.FromContext(ctx), userID, track, startMS, endMS, + ).Scan(&value) + return value, err +} + +// AdjustTemporaryUserLevels locks both requested tracks in deterministic order and commits command idempotency, +// supersession, overlay rows and temporary reward jobs as one transaction. +func (r *Repository) AdjustTemporaryUserLevels(ctx context.Context, command domain.AdjustTemporaryLevelsCommand, nowMS int64) (domain.AdminUserLevelProfile, error) { + if r == nil || r.db == nil { + return domain.AdminUserLevelProfile{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return domain.AdminUserLevelProfile{}, err + } + defer func() { _ = tx.Rollback() }() + + result, err := tx.ExecContext(ctx, ` + INSERT IGNORE INTO growth_temporary_level_commands + (app_code, command_id, request_hash, user_id, operator_admin_id, created_at_ms) + VALUES (?, ?, ?, ?, ?, ?)`, appcode.FromContext(ctx), command.CommandID, command.RequestHash, command.UserID, command.OperatorAdminID, nowMS) + if err != nil { + return domain.AdminUserLevelProfile{}, err + } + inserted, err := result.RowsAffected() + if err != nil { + return domain.AdminUserLevelProfile{}, err + } + if inserted == 0 { + var requestHash string + var userID int64 + if err := tx.QueryRowContext(ctx, `SELECT request_hash, user_id FROM growth_temporary_level_commands WHERE app_code = ? AND command_id = ? FOR UPDATE`, appcode.FromContext(ctx), command.CommandID).Scan(&requestHash, &userID); err != nil { + return domain.AdminUserLevelProfile{}, err + } + if requestHash != command.RequestHash || userID != command.UserID { + return domain.AdminUserLevelProfile{}, xerr.New(xerr.IdempotencyConflict, "temporary level command payload conflicts with existing command") + } + if err := tx.Commit(); err != nil { + return domain.AdminUserLevelProfile{}, err + } + return r.singleAdminLevelProfile(ctx, command.UserID, nowMS) + } + + for _, adjustment := range command.Adjustments { + account, err := r.ensureLevelAccountForUpdate(ctx, tx, command.UserID, adjustment.Track, nowMS) + if err != nil { + return domain.AdminUserLevelProfile{}, err + } + rules, err := r.listActiveLevelRules(ctx, tx, adjustment.Track) + if err != nil { + return domain.AdminUserLevelProfile{}, err + } + targetRequired, found := requiredValueForLevel(rules, adjustment.Level) + if !found { + return domain.AdminUserLevelProfile{}, xerr.New(xerr.NotFound, "active level rule not found") + } + if adjustment.Level <= account.CurrentLevel { + return domain.AdminUserLevelProfile{}, xerr.New(xerr.Conflict, "temporary target level must be higher than real level") + } + // 新调整只能覆盖仍在有效期内的 overlay;先把已越过 end 边界但 cron 尚未处理的旧事实按正常到期语义物化。 + if err := r.materializeExpiredTemporaryLevelsForUpdate(ctx, tx, command.UserID, adjustment.Track, rules, nowMS); err != nil { + return domain.AdminUserLevelProfile{}, err + } + generation, err := r.nextTemporaryGeneration(ctx, tx, command.UserID, adjustment.Track) + if err != nil { + return domain.AdminUserLevelProfile{}, err + } + // Superseded records never emit an expiry notice. Common rewards are moved to the new generation below; + // only rewards outside the new target remain associated with the old record and enter revoke processing. + oldIDs, err := r.supersedeTemporaryLevels(ctx, tx, command.UserID, adjustment.Track, nowMS) + if err != nil { + return domain.AdminUserLevelProfile{}, err + } + temporaryID := idgen.New("tmplevel") + durationMS := int64(adjustment.DurationDays) * temporaryLevelDayMS + if durationMS <= 0 || durationMS/temporaryLevelDayMS != int64(adjustment.DurationDays) || nowMS > math.MaxInt64-durationMS { + return domain.AdminUserLevelProfile{}, xerr.New(xerr.InvalidArgument, "temporary level duration exceeds epoch millisecond range") + } + expiresAtMS := nowMS + durationMS + _, err = tx.ExecContext(ctx, ` + INSERT INTO growth_temporary_levels ( + app_code, temporary_level_id, command_id, user_id, track, target_level, target_required_value, + baseline_total_value, started_at_ms, expires_at_ms, status, generation, operator_admin_id, reason, + activation_notice_status, expiry_notice_status, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', ?, ?, ?, 'pending', 'skipped', ?, ?)`, + appcode.FromContext(ctx), temporaryID, command.CommandID, command.UserID, adjustment.Track, + adjustment.Level, targetRequired, account.TotalValue, nowMS, expiresAtMS, generation, + command.OperatorAdminID, command.Reason, nowMS, nowMS, + ) + if err != nil { + return domain.AdminUserLevelProfile{}, err + } + for _, oldID := range oldIDs { + if err := r.carryTemporaryRewardsToGeneration(ctx, tx, oldID, temporaryID, generation, adjustment.Level, expiresAtMS, nowMS); err != nil { + return domain.AdminUserLevelProfile{}, err + } + if err := r.prepareTemporaryRewardsForRevocation(ctx, tx, oldID, 0, nowMS); err != nil { + return domain.AdminUserLevelProfile{}, err + } + } + tiers, err := r.listActiveLevelTiers(ctx, tx, adjustment.Track) + if err != nil { + return domain.AdminUserLevelProfile{}, err + } + if _, err := r.createTemporaryRewardJobs(ctx, tx, command.UserID, adjustment.Track, adjustment.Level, temporaryID, generation, expiresAtMS, rules, tiers, nowMS); err != nil { + return domain.AdminUserLevelProfile{}, err + } + } + if err := tx.Commit(); err != nil { + return domain.AdminUserLevelProfile{}, err + } + return r.singleAdminLevelProfile(ctx, command.UserID, nowMS) +} + +// carryTemporaryRewardsToGeneration transfers rewards that remain covered by the replacement target instead of +// revoking and regranting them. This makes one source/level have exactly one live temporary generation, so a later +// organic promotion cannot accidentally preserve both the old and the new wallet grants. +func (r *Repository) carryTemporaryRewardsToGeneration(ctx context.Context, tx *sql.Tx, oldTemporaryID string, newTemporaryID string, generation int32, targetLevel int32, expiresAtMS int64, nowMS int64) error { + _, err := tx.ExecContext(ctx, ` + UPDATE growth_level_reward_jobs + SET temporary_level_id = ?, grant_generation = ?, temporary_expires_at_ms = ?, updated_at_ms = ? + WHERE app_code = ? AND temporary_level_id = ? AND reward_origin = 'temporary' + AND is_permanent = 0 AND reward_level <= ? + AND status IN ('pending','failed','running','granted') + AND revoke_status = 'none'`, + newTemporaryID, generation, expiresAtMS, nowMS, + appcode.FromContext(ctx), oldTemporaryID, targetLevel, + ) + return err +} + +func (r *Repository) singleAdminLevelProfile(ctx context.Context, userID int64, nowMS int64) (domain.AdminUserLevelProfile, error) { + profiles, err := r.BatchGetUserLevelAdminProfiles(ctx, []int64{userID}, nowMS) + if err != nil { + return domain.AdminUserLevelProfile{}, err + } + if len(profiles.Profiles) == 0 { + return domain.AdminUserLevelProfile{UserID: userID, ServerTimeMS: nowMS}, nil + } + return profiles.Profiles[0], nil +} + +func (r *Repository) nextTemporaryGeneration(ctx context.Context, tx *sql.Tx, userID int64, track string) (int32, error) { + var generation int32 + err := tx.QueryRowContext(ctx, `SELECT COALESCE(MAX(generation), 0) + 1 FROM growth_temporary_levels WHERE app_code = ? AND user_id = ? AND track = ?`, appcode.FromContext(ctx), userID, track).Scan(&generation) + return generation, err +} + +func (r *Repository) supersedeTemporaryLevels(ctx context.Context, tx *sql.Tx, userID int64, track string, nowMS int64) ([]string, error) { + rows, err := tx.QueryContext(ctx, `SELECT temporary_level_id FROM growth_temporary_levels WHERE app_code = ? AND user_id = ? AND track = ? AND status = 'active' AND expires_at_ms > ? FOR UPDATE`, appcode.FromContext(ctx), userID, track, nowMS) + if err != nil { + return nil, err + } + var ids []string + for rows.Next() { + var id string + if err := rows.Scan(&id); err != nil { + _ = rows.Close() + return nil, err + } + ids = append(ids, id) + } + if err := rows.Close(); err != nil { + return nil, err + } + if len(ids) > 0 { + _, err = tx.ExecContext(ctx, `UPDATE growth_temporary_levels SET status = 'superseded', expiry_notice_status = 'skipped', locked_by = '', locked_until_ms = 0, updated_at_ms = ? WHERE app_code = ? AND user_id = ? AND track = ? AND status = 'active' AND expires_at_ms > ?`, nowMS, appcode.FromContext(ctx), userID, track, nowMS) + } + return ids, err +} + +func (r *Repository) materializeExpiredTemporaryLevelsForUpdate(ctx context.Context, tx *sql.Tx, userID int64, track string, rules []domain.Rule, nowMS int64) error { + rows, err := tx.QueryContext(ctx, temporaryLevelSelectSQL()+` + WHERE app_code = ? AND user_id = ? AND track = ? AND status = 'active' AND expires_at_ms <= ? + ORDER BY generation ASC FOR UPDATE`, appcode.FromContext(ctx), userID, track, nowMS) + if err != nil { + return err + } + items, err := scanTemporaryLevels(rows) + _ = rows.Close() + if err != nil { + return err + } + for _, item := range items { + increment, err := r.temporaryWindowValue(ctx, tx, item.UserID, item.Track, item.StartedAtMS, item.ExpiresAtMS) + if err != nil { + return err + } + item.FinalLevel = levelForValue(rules, item.BaselineTotalValue+increment) + if item.ActivationNoticeStatus == domain.NoticeStatusPending || item.ActivationNoticeStatus == domain.NoticeStatusFailed { + item.ActivationNoticeStatus = domain.NoticeStatusSkipped + } + if item.FinalLevel < item.TargetLevel { + item.ExpiryNoticeStatus = domain.NoticeStatusPending + } else { + item.ExpiryNoticeStatus = domain.NoticeStatusSkipped + } + if err := r.prepareTemporaryRewardsForRevocation(ctx, tx, item.TemporaryLevelID, item.FinalLevel, nowMS); err != nil { + return err + } + if _, err := tx.ExecContext(ctx, ` + UPDATE growth_temporary_levels + SET status = 'expired', final_level = ?, activation_notice_status = ?, expiry_notice_status = ?, + locked_by = '', locked_until_ms = 0, next_retry_at_ms = 0, updated_at_ms = ? + WHERE app_code = ? AND temporary_level_id = ? AND status = 'active'`, + item.FinalLevel, item.ActivationNoticeStatus, item.ExpiryNoticeStatus, nowMS, + appcode.FromContext(ctx), item.TemporaryLevelID, + ); err != nil { + return err + } + } + return nil +} + +func (r *Repository) createTemporaryRewardJobs(ctx context.Context, tx *sql.Tx, userID int64, track string, targetLevel int32, temporaryID string, generation int32, expiresAtMS int64, rules []domain.Rule, tiers []domain.Tier, nowMS int64) (int64, error) { + var created int64 + for _, rule := range rules { + if rule.Level < 1 || rule.Level > targetLevel || (rule.RewardResourceGroupID <= 0 && avatarFrameResourceIDFromDisplayConfig(rule.DisplayConfigJSON) <= 0 && shortBadgeResourceIDFromDisplayConfig(rule.DisplayConfigJSON) <= 0) { + continue + } + changed, err := r.insertTemporaryRewardJob(ctx, tx, userID, track, domain.RewardSourceLevel, fmt.Sprintf("level:%d", rule.Level), rule.Level, rule.RewardResourceGroupID, temporaryID, generation, expiresAtMS, nowMS) + if err != nil { + return 0, err + } + if changed { + created++ + } + } + for _, tier := range tiers { + if tier.MinLevel < 1 || tier.MinLevel > targetLevel || tier.RewardResourceGroupID <= 0 { + continue + } + changed, err := r.insertTemporaryRewardJob(ctx, tx, userID, track, domain.RewardSourceTier, fmt.Sprintf("tier:%d", tier.TierID), tier.MinLevel, tier.RewardResourceGroupID, temporaryID, generation, expiresAtMS, nowMS) + if err != nil { + return 0, err + } + if changed { + created++ + } + } + return created, nil +} + +func (r *Repository) insertTemporaryRewardJob(ctx context.Context, tx *sql.Tx, userID int64, track string, sourceType string, sourceID string, rewardLevel int32, resourceGroupID int64, temporaryID string, generation int32, expiresAtMS int64, nowMS int64) (bool, error) { + // Any organic/permanent row means this user already owns the reward; temporary generations must not duplicate it. + var permanentCount int + if err := tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM growth_level_reward_jobs WHERE app_code = ? AND user_id = ? AND track = ? AND reward_source_type = ? AND reward_source_id = ? AND is_permanent = 1 AND status <> 'canceled'`, appcode.FromContext(ctx), userID, track, sourceType, sourceID).Scan(&permanentCount); err != nil { + return false, err + } + if permanentCount > 0 { + return false, nil + } + rewardJobID := idgen.New("greward") + result, err := tx.ExecContext(ctx, ` + INSERT IGNORE INTO growth_level_reward_jobs ( + app_code, reward_job_id, user_id, track, reward_source_type, reward_source_id, resource_group_id, + wallet_command_id, reward_origin, temporary_level_id, grant_generation, reward_level, + is_permanent, temporary_expires_at_ms, revoke_status, status, next_retry_at_ms, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'temporary', ?, ?, ?, 0, ?, 'none', 'pending', 0, ?, ?)`, + appcode.FromContext(ctx), rewardJobID, userID, track, sourceType, sourceID, resourceGroupID, + "growth_level_reward:"+rewardJobID, temporaryID, generation, rewardLevel, expiresAtMS, nowMS, nowMS) + if err != nil { + return false, err + } + affected, err := result.RowsAffected() + return affected > 0, err +} + +// ClaimTemporaryLevelWork first materializes expiry facts in the same transaction, then leases only message work. +// Reward revocations are leased from their own child rows because one reward job may contain three wallet grants. +func (r *Repository) ClaimTemporaryLevelWork(ctx context.Context, workerID string, nowMS int64, lockTTL time.Duration, batchSize int) ([]domain.TemporaryLevelWork, error) { + if r == nil || r.db == nil { + return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer func() { _ = tx.Rollback() }() + rows, err := tx.QueryContext(ctx, temporaryLevelSelectSQL()+` + WHERE app_code = ? AND next_retry_at_ms <= ? AND (locked_until_ms = 0 OR locked_until_ms <= ?) + AND ((status = 'active' AND expires_at_ms <= ?) + OR (status = 'active' AND expires_at_ms > ? AND activation_notice_status IN ('pending','failed')) + OR (status = 'expired' AND expiry_notice_status IN ('pending','failed'))) + ORDER BY expires_at_ms ASC, created_at_ms ASC LIMIT ? FOR UPDATE SKIP LOCKED`, + appcode.FromContext(ctx), nowMS, nowMS, nowMS, nowMS, batchSize) + if err != nil { + return nil, err + } + levels, err := scanTemporaryLevels(rows) + _ = rows.Close() + if err != nil { + return nil, err + } + works := make([]domain.TemporaryLevelWork, 0, len(levels)) + lockUntil := nowMS + lockTTL.Milliseconds() + for _, item := range levels { + if item.Status == domain.TemporaryLevelStatusActive && item.ExpiresAtMS <= nowMS { + increment, err := r.temporaryWindowValue(ctx, tx, item.UserID, item.Track, item.StartedAtMS, item.ExpiresAtMS) + if err != nil { + return nil, err + } + rules, err := r.listActiveLevelRules(ctx, tx, item.Track) + if err != nil { + return nil, err + } + item.FinalLevel = levelForValue(rules, item.BaselineTotalValue+increment) + item.Status = domain.TemporaryLevelStatusExpired + if item.ActivationNoticeStatus == domain.NoticeStatusPending || item.ActivationNoticeStatus == domain.NoticeStatusFailed { + // A stale activation message after expiry is misleading; expiry truth is still sent when the user fell below target. + item.ActivationNoticeStatus = domain.NoticeStatusSkipped + } + if item.FinalLevel < item.TargetLevel { + item.ExpiryNoticeStatus = domain.NoticeStatusPending + } else { + item.ExpiryNoticeStatus = domain.NoticeStatusSkipped + } + if err := r.prepareTemporaryRewardsForRevocation(ctx, tx, item.TemporaryLevelID, item.FinalLevel, nowMS); err != nil { + return nil, err + } + _, err = tx.ExecContext(ctx, ` + UPDATE growth_temporary_levels + SET status = 'expired', final_level = ?, activation_notice_status = ?, expiry_notice_status = ?, updated_at_ms = ? + WHERE app_code = ? AND temporary_level_id = ?`, item.FinalLevel, item.ActivationNoticeStatus, item.ExpiryNoticeStatus, nowMS, appcode.FromContext(ctx), item.TemporaryLevelID) + if err != nil { + return nil, err + } + } + work := domain.TemporaryLevelWork{TemporaryLevel: item} + work.SendActivationNotice = item.Status == domain.TemporaryLevelStatusActive && item.ExpiresAtMS > nowMS && (item.ActivationNoticeStatus == domain.NoticeStatusPending || item.ActivationNoticeStatus == domain.NoticeStatusFailed) + work.SendExpiryNotice = item.Status == domain.TemporaryLevelStatusExpired && (item.ExpiryNoticeStatus == domain.NoticeStatusPending || item.ExpiryNoticeStatus == domain.NoticeStatusFailed) + _, err := tx.ExecContext(ctx, `UPDATE growth_temporary_levels SET locked_by = ?, locked_until_ms = ?, attempt_count = attempt_count + 1, updated_at_ms = ? WHERE app_code = ? AND temporary_level_id = ?`, workerID, lockUntil, nowMS, appcode.FromContext(ctx), item.TemporaryLevelID) + if err != nil { + return nil, err + } + works = append(works, work) + } + if err := tx.Commit(); err != nil { + return nil, err + } + return works, nil +} + +// prepareTemporaryRewardsForRevocation promotes rewards earned by the real final level and schedules every wallet +// grant above it for revoke. Ungranted jobs above the final level are canceled, preventing a late worker grant. +func (r *Repository) prepareTemporaryRewardsForRevocation(ctx context.Context, tx *sql.Tx, temporaryID string, finalLevel int32, nowMS int64) error { + _, err := tx.ExecContext(ctx, ` + UPDATE growth_level_reward_jobs + SET is_permanent = 1, revoke_status = 'skipped', updated_at_ms = ? + WHERE app_code = ? AND temporary_level_id = ? AND is_permanent = 0 AND reward_level <= ?`, + nowMS, appcode.FromContext(ctx), temporaryID, finalLevel) + if err != nil { + return err + } + _, err = tx.ExecContext(ctx, ` + UPDATE growth_level_reward_jobs + SET status = 'canceled', + revoke_status = CASE + WHEN EXISTS (SELECT 1 FROM growth_level_reward_wallet_grants g WHERE g.app_code = growth_level_reward_jobs.app_code AND g.reward_job_id = growth_level_reward_jobs.reward_job_id) + THEN 'pending' ELSE 'skipped' END, + locked_by = '', locked_until_ms = 0, updated_at_ms = ? + WHERE app_code = ? AND temporary_level_id = ? AND is_permanent = 0 AND reward_level > ? AND status IN ('pending','failed')`, + nowMS, appcode.FromContext(ctx), temporaryID, finalLevel) + if err != nil { + return err + } + _, err = tx.ExecContext(ctx, ` + UPDATE growth_level_reward_jobs j + SET revoke_status = CASE + WHEN EXISTS (SELECT 1 FROM growth_level_reward_wallet_grants g WHERE g.app_code = j.app_code AND g.reward_job_id = j.reward_job_id) + THEN 'pending' ELSE 'skipped' END, + updated_at_ms = ? + WHERE j.app_code = ? AND j.temporary_level_id = ? AND j.is_permanent = 0 AND j.reward_level > ? AND j.status = 'granted'`, + nowMS, appcode.FromContext(ctx), temporaryID, finalLevel) + if err != nil { + return err + } + _, err = tx.ExecContext(ctx, ` + UPDATE growth_level_reward_wallet_grants g + JOIN growth_level_reward_jobs j ON j.app_code = g.app_code AND j.reward_job_id = g.reward_job_id + SET g.revoke_status = 'pending', g.revoke_next_retry_at_ms = 0, g.updated_at_ms = ? + WHERE j.app_code = ? AND j.temporary_level_id = ? AND j.is_permanent = 0 AND j.reward_level > ? + AND j.status IN ('granted','canceled') AND g.revoke_status IN ('none','failed')`, + nowMS, appcode.FromContext(ctx), temporaryID, finalLevel) + return err +} + +func (r *Repository) MarkTemporaryLevelNotices(ctx context.Context, temporaryLevelID string, activationStatus string, expiryStatus string, failureReason string, nextRetryAtMS int64, nowMS int64) error { + if r == nil || r.db == nil { + return xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + sets := []string{"locked_by = ''", "locked_until_ms = 0", "failure_reason = ?", "next_retry_at_ms = ?", "updated_at_ms = ?"} + args := []any{truncateGrowthFailure(failureReason), nextRetryAtMS, nowMS} + if activationStatus != "" { + sets = append(sets, "activation_notice_status = ?") + args = append(args, activationStatus) + } + if expiryStatus != "" { + sets = append(sets, "expiry_notice_status = ?") + args = append(args, expiryStatus) + } + args = append(args, appcode.FromContext(ctx), temporaryLevelID) + _, err := r.db.ExecContext(ctx, `UPDATE growth_temporary_levels SET `+strings.Join(sets, ", ")+` WHERE app_code = ? AND temporary_level_id = ?`, args...) + return err +} + +func (r *Repository) ClaimLevelRewardRevocations(ctx context.Context, workerID string, nowMS int64, lockTTL time.Duration, batchSize int) ([]domain.RewardRevocation, error) { + if r == nil || r.db == nil { + return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer func() { _ = tx.Rollback() }() + rows, err := tx.QueryContext(ctx, ` + SELECT g.reward_job_id, g.wallet_grant_id, j.temporary_level_id, j.user_id, j.track, g.revoke_attempt_count + FROM growth_level_reward_wallet_grants g + JOIN growth_level_reward_jobs j ON j.app_code = g.app_code AND j.reward_job_id = g.reward_job_id + WHERE g.app_code = ? AND g.revoke_status IN ('pending','failed','running') AND g.revoke_next_retry_at_ms <= ? + AND (g.revoke_locked_until_ms = 0 OR g.revoke_locked_until_ms <= ?) + ORDER BY g.created_at_ms ASC LIMIT ? FOR UPDATE SKIP LOCKED`, appcode.FromContext(ctx), nowMS, nowMS, batchSize) + if err != nil { + return nil, err + } + items := make([]domain.RewardRevocation, 0) + for rows.Next() { + var item domain.RewardRevocation + if err := rows.Scan(&item.RewardJobID, &item.WalletGrantID, &item.TemporaryLevelID, &item.UserID, &item.Track, &item.AttemptCount); err != nil { + _ = rows.Close() + return nil, err + } + items = append(items, item) + } + _ = rows.Close() + lockUntil := nowMS + lockTTL.Milliseconds() + for _, item := range items { + _, err := tx.ExecContext(ctx, `UPDATE growth_level_reward_wallet_grants SET revoke_status = 'running', revoke_attempt_count = revoke_attempt_count + 1, revoke_locked_by = ?, revoke_locked_until_ms = ?, updated_at_ms = ? WHERE app_code = ? AND reward_job_id = ? AND wallet_grant_id = ?`, workerID, lockUntil, nowMS, appcode.FromContext(ctx), item.RewardJobID, item.WalletGrantID) + if err != nil { + return nil, err + } + } + if err := tx.Commit(); err != nil { + return nil, err + } + return items, nil +} + +func (r *Repository) MarkLevelRewardRevoked(ctx context.Context, rewardJobID string, walletGrantID string, nowMS int64) error { + if r == nil || r.db == nil { + return xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + _, err = tx.ExecContext(ctx, `UPDATE growth_level_reward_wallet_grants SET revoke_status = 'revoked', revoked_at_ms = ?, revoke_locked_by = '', revoke_locked_until_ms = 0, revoke_failure_reason = '', updated_at_ms = ? WHERE app_code = ? AND reward_job_id = ? AND wallet_grant_id = ?`, nowMS, nowMS, appcode.FromContext(ctx), rewardJobID, walletGrantID) + if err != nil { + return err + } + var remaining int + if err := tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM growth_level_reward_wallet_grants WHERE app_code = ? AND reward_job_id = ? AND revoke_status NOT IN ('revoked','skipped')`, appcode.FromContext(ctx), rewardJobID).Scan(&remaining); err != nil { + return err + } + if remaining == 0 { + if _, err := tx.ExecContext(ctx, `UPDATE growth_level_reward_jobs SET revoke_status = 'revoked', updated_at_ms = ? WHERE app_code = ? AND reward_job_id = ?`, nowMS, appcode.FromContext(ctx), rewardJobID); err != nil { + return err + } + } + return tx.Commit() +} + +func (r *Repository) MarkLevelRewardRevokeFailed(ctx context.Context, rewardJobID string, walletGrantID string, failureReason string, nextRetryAtMS int64, nowMS int64) error { + if r == nil || r.db == nil { + return xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + if _, err := tx.ExecContext(ctx, `UPDATE growth_level_reward_wallet_grants SET revoke_status = 'failed', revoke_next_retry_at_ms = ?, revoke_locked_by = '', revoke_locked_until_ms = 0, revoke_failure_reason = ?, updated_at_ms = ? WHERE app_code = ? AND reward_job_id = ? AND wallet_grant_id = ?`, nextRetryAtMS, truncateGrowthFailure(failureReason), nowMS, appcode.FromContext(ctx), rewardJobID, walletGrantID); err != nil { + return err + } + if _, err := tx.ExecContext(ctx, `UPDATE growth_level_reward_jobs SET revoke_status = 'failed', updated_at_ms = ? WHERE app_code = ? AND reward_job_id = ? AND revoke_status <> 'revoked'`, nowMS, appcode.FromContext(ctx), rewardJobID); err != nil { + return err + } + return tx.Commit() +} + +func temporaryLevelSelectSQL() string { + return `SELECT app_code, temporary_level_id, command_id, user_id, track, target_level, target_required_value, + baseline_total_value, started_at_ms, expires_at_ms, status, generation, operator_admin_id, reason, + final_level, activation_notice_status, expiry_notice_status, attempt_count, failure_reason, + created_at_ms, updated_at_ms FROM growth_temporary_levels ` +} + +func scanTemporaryLevels(rows *sql.Rows) ([]domain.TemporaryLevel, error) { + items := make([]domain.TemporaryLevel, 0) + for rows.Next() { + var item domain.TemporaryLevel + if err := rows.Scan(&item.AppCode, &item.TemporaryLevelID, &item.CommandID, &item.UserID, &item.Track, + &item.TargetLevel, &item.TargetRequiredValue, &item.BaselineTotalValue, &item.StartedAtMS, &item.ExpiresAtMS, + &item.Status, &item.Generation, &item.OperatorAdminID, &item.Reason, &item.FinalLevel, + &item.ActivationNoticeStatus, &item.ExpiryNoticeStatus, &item.AttemptCount, &item.FailureReason, + &item.CreatedAtMS, &item.UpdatedAtMS); err != nil { + return nil, err + } + items = append(items, item) + } + return items, rows.Err() +} diff --git a/services/activity-service/internal/storage/mysql/wheel_outbox_repository.go b/services/activity-service/internal/storage/mysql/wheel_outbox_repository.go new file mode 100644 index 00000000..2021c1ff --- /dev/null +++ b/services/activity-service/internal/storage/mysql/wheel_outbox_repository.go @@ -0,0 +1,212 @@ +package mysql + +import ( + "context" + "strings" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + domain "hyapp/services/activity-service/internal/domain/wheel" +) + +// ClaimWheelRewardOutbox 抢占一个 App 分区内可补偿的转盘发奖事实。 +// 行锁和 SKIP LOCKED 允许双节点并行;过期 delivering 可被接管,而仍持有效 lease 的记录不会被第二个节点重复处理。 +func (r *Repository) ClaimWheelRewardOutbox(ctx context.Context, workerID string, nowMS int64, lockUntilMS int64, limit int, maxRetry int) ([]domain.RewardOutboxRecord, error) { + if r == nil || r.db == nil { + return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + workerID = strings.TrimSpace(workerID) + if workerID == "" { + return nil, xerr.New(xerr.InvalidArgument, "wheel reward worker_id is required") + } + if limit <= 0 { + limit = 50 + } + if maxRetry <= 0 { + maxRetry = 16 + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer func() { _ = tx.Rollback() }() + appCode := appcode.FromContext(ctx) + rows, err := tx.QueryContext(ctx, ` + SELECT app_code, outbox_id, payload, status, retry_count, next_retry_at_ms, + locked_by, lock_until_ms, last_error, created_at_ms, updated_at_ms + FROM activity_outbox + WHERE app_code = ? + AND event_type = ? + AND retry_count < ? + AND ( + (status IN (?, ?) AND next_retry_at_ms <= ?) + OR (status = ? AND lock_until_ms <= ?) + ) + ORDER BY created_at_ms, outbox_id + LIMIT ? + FOR UPDATE SKIP LOCKED`, + appCode, + domain.EventTypeWheelRewardSettlement, + maxRetry, + domain.OutboxStatusPending, + domain.OutboxStatusRetryable, + nowMS, + domain.OutboxStatusDelivering, + nowMS, + limit, + ) + if err != nil { + return nil, err + } + records := make([]domain.RewardOutboxRecord, 0, limit) + for rows.Next() { + var record domain.RewardOutboxRecord + if err := rows.Scan( + &record.AppCode, + &record.OutboxID, + &record.Payload, + &record.Status, + &record.RetryCount, + &record.NextRetryAtMS, + &record.LockedBy, + &record.LockUntilMS, + &record.LastError, + &record.CreatedAtMS, + &record.UpdatedAtMS, + ); err != nil { + _ = rows.Close() + return nil, err + } + records = append(records, record) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + for index := range records { + result, err := tx.ExecContext(ctx, ` + UPDATE activity_outbox + SET status = ?, locked_by = ?, lock_until_ms = ?, updated_at_ms = ? + WHERE app_code = ? AND outbox_id = ?`, + domain.OutboxStatusDelivering, + workerID, + lockUntilMS, + nowMS, + records[index].AppCode, + records[index].OutboxID, + ) + if err != nil { + return nil, err + } + if affected, err := result.RowsAffected(); err != nil || affected != 1 { + if err != nil { + return nil, err + } + return nil, xerr.New(xerr.Conflict, "wheel reward outbox claim lost") + } + records[index].Status = domain.OutboxStatusDelivering + records[index].LockedBy = workerID + records[index].LockUntilMS = lockUntilMS + } + if err := tx.Commit(); err != nil { + return nil, err + } + return records, nil +} + +// MarkWheelRewardOutboxDelivered 把钱包返回的既有或新建收据与 draw 状态、统计和 outbox 在一个 MySQL 事务中提交。 +// locked_by 是 fencing token;worker lease 过期并被接管后,旧 worker 即使晚返回也不能覆盖新 owner 的处理结果。 +func (r *Repository) MarkWheelRewardOutboxDelivered(ctx context.Context, outboxID string, workerID string, drawIDs []string, transactionID string, nowMS int64) error { + if r == nil || r.db == nil { + return xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + outboxID = strings.TrimSpace(outboxID) + workerID = strings.TrimSpace(workerID) + if outboxID == "" || workerID == "" { + return xerr.New(xerr.InvalidArgument, "wheel reward outbox delivery identity is incomplete") + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + appCode := appcode.FromContext(ctx) + if err := markWheelDrawsGrantedTx(ctx, tx, appCode, drawIDs, transactionID, nowMS, true); err != nil { + return err + } + result, err := tx.ExecContext(ctx, ` + UPDATE activity_outbox + SET status = ?, locked_by = '', lock_until_ms = 0, last_error = '', updated_at_ms = ? + WHERE app_code = ? AND outbox_id = ? AND event_type = ? AND status = ? AND locked_by = ?`, + domain.OutboxStatusDelivered, + nowMS, + appCode, + outboxID, + domain.EventTypeWheelRewardSettlement, + domain.OutboxStatusDelivering, + workerID, + ) + if err != nil { + return err + } + if affected, err := result.RowsAffected(); err != nil || affected != 1 { + if err != nil { + return err + } + return xerr.New(xerr.Conflict, "wheel reward outbox delivery lock lost") + } + return tx.Commit() +} + +// MarkWheelRewardOutboxFailed 释放失败 lease,并以有界重试次数决定 retryable 或 failed。 +func (r *Repository) MarkWheelRewardOutboxFailed(ctx context.Context, outboxID string, workerID string, nowMS int64, nextRetryAtMS int64, maxRetry int, lastError string) error { + if r == nil || r.db == nil { + return xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + if maxRetry <= 0 { + maxRetry = 16 + } + result, err := r.db.ExecContext(ctx, ` + UPDATE activity_outbox + SET status = CASE WHEN retry_count + 1 >= ? THEN ? ELSE ? END, + next_retry_at_ms = CASE WHEN retry_count + 1 >= ? THEN 0 ELSE ? END, + retry_count = retry_count + 1, + locked_by = '', + lock_until_ms = 0, + last_error = ?, + updated_at_ms = ? + WHERE app_code = ? AND outbox_id = ? AND event_type = ? AND status = ? AND locked_by = ?`, + maxRetry, + domain.OutboxStatusFailed, + domain.OutboxStatusRetryable, + maxRetry, + nextRetryAtMS, + truncateWheelOutboxError(lastError), + nowMS, + appcode.FromContext(ctx), + strings.TrimSpace(outboxID), + domain.EventTypeWheelRewardSettlement, + domain.OutboxStatusDelivering, + strings.TrimSpace(workerID), + ) + if err != nil { + return err + } + if affected, err := result.RowsAffected(); err != nil || affected != 1 { + if err != nil { + return err + } + return xerr.New(xerr.Conflict, "wheel reward outbox failure lock lost") + } + return nil +} + +func truncateWheelOutboxError(message string) string { + message = strings.TrimSpace(message) + if len(message) <= 512 { + return message + } + return message[:512] +} diff --git a/services/activity-service/internal/storage/mysql/wheel_outbox_repository_test.go b/services/activity-service/internal/storage/mysql/wheel_outbox_repository_test.go new file mode 100644 index 00000000..707828c2 --- /dev/null +++ b/services/activity-service/internal/storage/mysql/wheel_outbox_repository_test.go @@ -0,0 +1,79 @@ +package mysql + +import ( + "context" + "testing" + + "hyapp/internal/testutil/mysqlschema" + "hyapp/pkg/appcode" + domain "hyapp/services/activity-service/internal/domain/wheel" +) + +func TestWheelRewardOutboxClaimAndDeliveryAreLeaseFencedAndAtomic(t *testing.T) { + schema := mysqlschema.New(t, mysqlschema.Config{ + EnvVar: "ACTIVITY_SERVICE_MYSQL_TEST_DSN", + InitDBPath: mysqlschema.InitDBPath(t, mysqlschema.CallerFile(t, 1), "..", "..", "..", "deploy", "mysql", "initdb", "001_activity_service.sql"), + DatabasePrefix: "hyapp_activity_wheel_outbox_test", + }) + repository, err := Open(context.Background(), schema.DSN) + if err != nil { + t.Fatalf("open repository: %v", err) + } + t.Cleanup(func() { _ = repository.Close() }) + ctx := appcode.WithContext(context.Background(), "fami") + const ( + drawID = "wheel_draw_outbox_test" + outboxID = "wheel_reward_wheel_draw_outbox_test" + ) + if _, err := schema.DB.ExecContext(ctx, ` + INSERT INTO wheel_draw_stats ( + app_code, wheel_id, total_draws, unique_users, total_spent_coins, total_rtp_value_coins, + pending_draws, granted_draws, failed_draws, created_at_ms, updated_at_ms + ) VALUES ('fami', 'classic', 1, 1, 100, 20, 1, 0, 0, 1000, 1000)`); err != nil { + t.Fatalf("seed wheel stats: %v", err) + } + if _, err := schema.DB.ExecContext(ctx, ` + INSERT INTO wheel_draw_records ( + app_code, draw_id, command_id, user_id, device_id, visible_region_id, wheel_id, coin_spent, + rule_version, rtp_window_index, selected_tier_id, reward_type, reward_id, reward_count, + reward_coins, rtp_value_coins, candidate_tiers_json, rtp_snapshot_json, prize_metadata_json, + reward_status, paid_at_ms, created_at_ms, updated_at_ms + ) VALUES ('fami', ?, 'draw-command', 1001, 'device', 9, 'classic', 100, + 1, 1, 'tier-1', 'coin', '', 1, 20, 20, '{}', '{}', '{}', 'pending', 1000, 1000, 1000)`, drawID); err != nil { + t.Fatalf("seed wheel draw: %v", err) + } + if _, err := schema.DB.ExecContext(ctx, ` + INSERT INTO activity_outbox ( + app_code, outbox_id, event_type, payload, status, retry_count, next_retry_at_ms, + locked_by, lock_until_ms, last_error, created_at_ms, updated_at_ms + ) VALUES ('fami', ?, ?, '{}', 'pending', 0, 1000, '', 0, '', 1000, 1000)`, outboxID, domain.EventTypeWheelRewardSettlement); err != nil { + t.Fatalf("seed wheel outbox: %v", err) + } + + records, err := repository.ClaimWheelRewardOutbox(ctx, "worker-1", 1000, 31000, 10, 16) + if err != nil || len(records) != 1 || records[0].LockedBy != "worker-1" || records[0].Status != domain.OutboxStatusDelivering { + t.Fatalf("claim mismatch: records=%+v err=%v", records, err) + } + secondClaim, err := repository.ClaimWheelRewardOutbox(ctx, "worker-2", 2000, 32000, 10, 16) + if err != nil || len(secondClaim) != 0 { + t.Fatalf("active lease must block second worker: records=%+v err=%v", secondClaim, err) + } + if err := repository.MarkWheelRewardOutboxDelivered(ctx, outboxID, "worker-1", []string{drawID}, "wtx-existing", 3000); err != nil { + t.Fatalf("mark delivered: %v", err) + } + + var drawStatus, receiptRef, outboxStatus, lockedBy string + var pending, granted int64 + if err := schema.DB.QueryRowContext(ctx, `SELECT reward_status, reward_transaction_id FROM wheel_draw_records WHERE app_code = 'fami' AND draw_id = ?`, drawID).Scan(&drawStatus, &receiptRef); err != nil { + t.Fatalf("read draw state: %v", err) + } + if err := schema.DB.QueryRowContext(ctx, `SELECT pending_draws, granted_draws FROM wheel_draw_stats WHERE app_code = 'fami' AND wheel_id = 'classic'`).Scan(&pending, &granted); err != nil { + t.Fatalf("read wheel stats: %v", err) + } + if err := schema.DB.QueryRowContext(ctx, `SELECT status, locked_by FROM activity_outbox WHERE app_code = 'fami' AND outbox_id = ?`, outboxID).Scan(&outboxStatus, &lockedBy); err != nil { + t.Fatalf("read outbox state: %v", err) + } + if drawStatus != domain.StatusGranted || receiptRef != "wtx-existing" || pending != 0 || granted != 1 || outboxStatus != domain.OutboxStatusDelivered || lockedBy != "" { + t.Fatalf("delivery state mismatch: draw=%s receipt=%s pending=%d granted=%d outbox=%s lock=%q", drawStatus, receiptRef, pending, granted, outboxStatus, lockedBy) + } +} diff --git a/services/activity-service/internal/storage/mysql/wheel_repository.go b/services/activity-service/internal/storage/mysql/wheel_repository.go index 04d41a12..fa50c90d 100644 --- a/services/activity-service/internal/storage/mysql/wheel_repository.go +++ b/services/activity-service/internal/storage/mysql/wheel_repository.go @@ -317,11 +317,38 @@ func (r *Repository) MarkWheelDrawsGranted(ctx context.Context, appCode string, return err } defer func() { _ = tx.Rollback() }() + if err := markWheelDrawsGrantedTx(ctx, tx, appCode, drawIDs, transactionID, nowMS, false); err != nil { + return err + } for _, drawID := range drawIDs { drawID = strings.TrimSpace(drawID) if drawID == "" { continue } + if _, err := tx.ExecContext(ctx, ` + UPDATE activity_outbox + SET status = 'delivered', locked_by = '', lock_until_ms = 0, last_error = '', updated_at_ms = ? + WHERE app_code = ? AND outbox_id = ?`, + nowMS, appCode, "wheel_reward_"+drawID, + ); err != nil { + return err + } + } + return tx.Commit() +} + +// markWheelDrawsGrantedTx 原子搬移 draw 和汇总状态;requireDraw 用于补偿 worker 防止损坏 payload 把 outbox 错标 delivered。 +func markWheelDrawsGrantedTx(ctx context.Context, tx *sql.Tx, appCode string, drawIDs []string, transactionID string, nowMS int64, requireDraw bool) error { + seen := make(map[string]struct{}, len(drawIDs)) + for _, drawID := range drawIDs { + drawID = strings.TrimSpace(drawID) + if drawID == "" { + continue + } + if _, exists := seen[drawID]; exists { + continue + } + seen[drawID] = struct{}{} var wheelID, oldStatus string if err := tx.QueryRowContext(ctx, ` SELECT wheel_id, reward_status @@ -331,6 +358,9 @@ func (r *Repository) MarkWheelDrawsGranted(ctx context.Context, appCode string, appCode, drawID, ).Scan(&wheelID, &oldStatus); err != nil { if errors.Is(err, sql.ErrNoRows) { + if requireDraw { + return xerr.New(xerr.NotFound, "wheel draw record not found") + } continue } return err @@ -356,16 +386,11 @@ func (r *Repository) MarkWheelDrawsGranted(ctx context.Context, appCode string, return err } } - if _, err := tx.ExecContext(ctx, ` - UPDATE activity_outbox - SET status = 'delivered', locked_by = '', lock_until_ms = 0, last_error = '', updated_at_ms = ? - WHERE app_code = ? AND outbox_id = ?`, - nowMS, appCode, "wheel_reward_"+drawID, - ); err != nil { - return err - } } - return tx.Commit() + if requireDraw && len(seen) == 0 { + return xerr.New(xerr.InvalidArgument, "wheel draw_ids are empty") + } + return nil } func (r *Repository) executeSingleWheelDraw(cmd domain.DrawCommand, commandID string, unitSpent int64, config domain.RuleConfig, window *wheelRTPWindow, pool *wheelPool, nowMS int64) (domain.DrawResult, int64, error) { diff --git a/services/activity-service/internal/transport/grpc/cron_server.go b/services/activity-service/internal/transport/grpc/cron_server.go index d21c8fc4..1f6965dd 100644 --- a/services/activity-service/internal/transport/grpc/cron_server.go +++ b/services/activity-service/internal/transport/grpc/cron_server.go @@ -85,6 +85,19 @@ func (s *CronServer) ProcessLevelRewardBatch(ctx context.Context, req *activityv }, nil } +// ProcessTemporaryLevelBatch advances expired overlays, sends owner-created inbox notices and revokes temporary grants. +func (s *CronServer) ProcessTemporaryLevelBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + if s.growth == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "growth service is not configured")) + } + claimed, processed, success, failure, hasMore, err := s.growth.ProcessTemporaryLevelBatch(ctx, req.GetWorkerId(), int(req.GetBatchSize()), durationFromMillis(req.GetLockTtlMs())) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &activityv1.CronBatchResponse{ClaimedCount: claimed, ProcessedCount: processed, SuccessCount: success, FailureCount: failure, HasMore: hasMore}, nil +} + // ProcessAchievementRewardBatch claims and grants pending achievement badge/resource rewards. func (s *CronServer) ProcessAchievementRewardBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) diff --git a/services/activity-service/internal/transport/grpc/growth_server.go b/services/activity-service/internal/transport/grpc/growth_server.go index 6b2baa59..a5e890fb 100644 --- a/services/activity-service/internal/transport/grpc/growth_server.go +++ b/services/activity-service/internal/transport/grpc/growth_server.go @@ -199,6 +199,35 @@ func (s *AdminGrowthLevelServer) ListLevelConfig(ctx context.Context, req *activ return resp, nil } +func (s *AdminGrowthLevelServer) BatchGetUserLevelAdminProfiles(ctx context.Context, req *activityv1.BatchGetUserLevelAdminProfilesRequest) (*activityv1.BatchGetUserLevelAdminProfilesResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + profiles, err := s.svc.BatchGetUserLevelAdminProfiles(ctx, req.GetUserIds()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &activityv1.BatchGetUserLevelAdminProfilesResponse{Profiles: make([]*activityv1.AdminUserLevelProfile, 0, len(profiles.Profiles)), ServerTimeMs: profiles.ServerTimeMS} + for _, profile := range profiles.Profiles { + resp.Profiles = append(resp.Profiles, adminUserLevelProfileToProto(profile)) + } + return resp, nil +} + +func (s *AdminGrowthLevelServer) AdjustTemporaryUserLevels(ctx context.Context, req *activityv1.AdjustTemporaryUserLevelsRequest) (*activityv1.AdjustTemporaryUserLevelsResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + adjustments := make([]domain.TemporaryLevelAdjustment, 0, len(req.GetAdjustments())) + for _, item := range req.GetAdjustments() { + adjustments = append(adjustments, domain.TemporaryLevelAdjustment{Track: item.GetTrack(), Level: item.GetLevel(), DurationDays: item.GetDurationDays()}) + } + profile, err := s.svc.AdjustTemporaryUserLevels(ctx, domain.AdjustTemporaryLevelsCommand{ + CommandID: req.GetCommandId(), UserID: req.GetUserId(), Adjustments: adjustments, + OperatorAdminID: req.GetOperatorAdminId(), Reason: req.GetReason(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &activityv1.AdjustTemporaryUserLevelsResponse{Profile: adminUserLevelProfileToProto(profile), ServerTimeMs: profile.ServerTimeMS}, nil +} + func (s *AdminGrowthLevelServer) UpsertLevelTrack(ctx context.Context, req *activityv1.UpsertLevelTrackRequest) (*activityv1.UpsertLevelTrackResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) item, created, err := s.svc.UpsertLevelTrack(ctx, domain.TrackCommand{ @@ -343,19 +372,26 @@ func levelDisplayTrackProfileToProto(item domain.LevelDisplayTrackProfile) *acti func levelRewardToProto(item domain.RewardJob) *activityv1.LevelRewardJob { return &activityv1.LevelRewardJob{ - RewardJobId: item.RewardJobID, - UserId: item.UserID, - Track: item.Track, - RewardSourceType: item.RewardSourceType, - RewardSourceId: item.RewardSourceID, - ResourceGroupId: item.ResourceGroupID, - WalletCommandId: item.WalletCommandID, - WalletGrantId: item.WalletGrantID, - Status: item.Status, - AttemptCount: item.AttemptCount, - FailureReason: item.FailureReason, - CreatedAtMs: item.CreatedAtMS, - UpdatedAtMs: item.UpdatedAtMS, + RewardJobId: item.RewardJobID, + UserId: item.UserID, + Track: item.Track, + RewardSourceType: item.RewardSourceType, + RewardSourceId: item.RewardSourceID, + ResourceGroupId: item.ResourceGroupID, + WalletCommandId: item.WalletCommandID, + WalletGrantId: item.WalletGrantID, + Status: item.Status, + AttemptCount: item.AttemptCount, + FailureReason: item.FailureReason, + CreatedAtMs: item.CreatedAtMS, + UpdatedAtMs: item.UpdatedAtMS, + RewardOrigin: item.RewardOrigin, + TemporaryLevelId: item.TemporaryLevelID, + GrantGeneration: item.GrantGeneration, + RewardLevel: item.RewardLevel, + Permanent: item.Permanent, + TemporaryExpiresAtMs: item.TemporaryExpiresAtMS, + RevokeStatus: item.RevokeStatus, } } @@ -367,3 +403,16 @@ func registrationLevelBadgeGrantToProto(item domain.RegistrationLevelBadgeGrant) GrantId: item.GrantID, } } + +func adminUserLevelProfileToProto(item domain.AdminUserLevelProfile) *activityv1.AdminUserLevelProfile { + result := &activityv1.AdminUserLevelProfile{UserId: item.UserID, Tracks: make([]*activityv1.AdminLevelTrackProfile, 0, len(item.Tracks))} + for _, track := range item.Tracks { + result.Tracks = append(result.Tracks, &activityv1.AdminLevelTrackProfile{ + Track: track.Track, RealLevel: track.RealLevel, RealTotalValue: track.RealTotalValue, + DisplayLevel: track.DisplayLevel, DisplayValue: track.DisplayValue, + TemporaryLevelId: track.TemporaryLevelID, TemporaryTargetLevel: track.TemporaryTargetLevel, + StartedAtMs: track.StartedAtMS, ExpiresAtMs: track.ExpiresAtMS, + }) + } + return result +} diff --git a/services/cron-service/configs/config.docker.yaml b/services/cron-service/configs/config.docker.yaml index ab1a1e18..1bb51067 100644 --- a/services/cron-service/configs/config.docker.yaml +++ b/services/cron-service/configs/config.docker.yaml @@ -33,12 +33,24 @@ tasks: timeout: "10s" lock_ttl: "30s" batch_size: 500 + vip_daily_coin_rebate: + enabled: true + interval: "1m" + timeout: "30s" + lock_ttl: "1m" + batch_size: 500 growth_level_reward: enabled: true interval: "5s" timeout: "10s" lock_ttl: "30s" batch_size: 100 + temporary_growth_level: + enabled: true + interval: "5s" + timeout: "15s" + lock_ttl: "30s" + batch_size: 100 weekly_star_settlement: enabled: true interval: "5s" @@ -77,3 +89,22 @@ tasks: batch_size: 100 pending_publish_max_age: "2m" publishing_session_max_age: "12h" + room_open_session_compensation: + enabled: true + interval: "60s" + timeout: "10s" + lock_ttl: "60s" + batch_size: 200 + open_session_max_age: "24h" + manager_user_block_expiry: + enabled: true + interval: "30s" + timeout: "10s" + lock_ttl: "30s" + batch_size: 100 + admin_user_ban_expiry: + enabled: true + interval: "30s" + timeout: "10s" + lock_ttl: "30s" + batch_size: 100 diff --git a/services/cron-service/configs/config.tencent.example.yaml b/services/cron-service/configs/config.tencent.example.yaml index 89c484b2..411dd7f6 100644 --- a/services/cron-service/configs/config.tencent.example.yaml +++ b/services/cron-service/configs/config.tencent.example.yaml @@ -33,12 +33,24 @@ tasks: timeout: "10s" lock_ttl: "30s" batch_size: 500 + vip_daily_coin_rebate: + enabled: true + interval: "1m" + timeout: "30s" + lock_ttl: "1m" + batch_size: 500 growth_level_reward: enabled: true interval: "5s" timeout: "10s" lock_ttl: "30s" batch_size: 100 + temporary_growth_level: + enabled: true + interval: "5s" + timeout: "15s" + lock_ttl: "30s" + batch_size: 100 weekly_star_settlement: enabled: true interval: "5s" @@ -77,3 +89,22 @@ tasks: batch_size: 100 pending_publish_max_age: "2m" publishing_session_max_age: "12h" + room_open_session_compensation: + enabled: true + interval: "60s" + timeout: "10s" + lock_ttl: "60s" + batch_size: 200 + open_session_max_age: "24h" + manager_user_block_expiry: + enabled: true + interval: "30s" + timeout: "10s" + lock_ttl: "30s" + batch_size: 100 + admin_user_ban_expiry: + enabled: true + interval: "30s" + timeout: "10s" + lock_ttl: "30s" + batch_size: 100 diff --git a/services/cron-service/configs/config.yaml b/services/cron-service/configs/config.yaml index 5d3fae42..932b0269 100644 --- a/services/cron-service/configs/config.yaml +++ b/services/cron-service/configs/config.yaml @@ -33,12 +33,24 @@ tasks: timeout: "10s" lock_ttl: "30s" batch_size: 500 + vip_daily_coin_rebate: + enabled: true + interval: "1m" + timeout: "30s" + lock_ttl: "1m" + batch_size: 500 growth_level_reward: enabled: true interval: "5s" timeout: "10s" lock_ttl: "30s" batch_size: 100 + temporary_growth_level: + enabled: true + interval: "5s" + timeout: "15s" + lock_ttl: "30s" + batch_size: 100 weekly_star_settlement: enabled: true interval: "5s" @@ -77,3 +89,22 @@ tasks: batch_size: 100 pending_publish_max_age: "2m" publishing_session_max_age: "12h" + room_open_session_compensation: + enabled: true + interval: "60s" + timeout: "10s" + lock_ttl: "60s" + batch_size: 200 + open_session_max_age: "24h" + manager_user_block_expiry: + enabled: true + interval: "30s" + timeout: "10s" + lock_ttl: "30s" + batch_size: 100 + admin_user_ban_expiry: + enabled: true + interval: "30s" + timeout: "10s" + lock_ttl: "30s" + batch_size: 100 diff --git a/services/cron-service/internal/app/app.go b/services/cron-service/internal/app/app.go index d6ff89bf..987893db 100644 --- a/services/cron-service/internal/app/app.go +++ b/services/cron-service/internal/app/app.go @@ -117,12 +117,15 @@ func New(cfg config.Config) (*App, error) { taskScheduler := scheduler.New(cfg.NodeID, cfg.Tasks, repository, map[string]scheduler.Handler{ "login_ip_risk": userCron.ProcessLoginIPRiskBatch, "user_region_rebuild": userCron.ProcessRegionRebuildBatch, - "mic_open_session_compensation": userCron.CompensateMicOpenSessions, + "mic_open_session_compensation": userCron.CompensateMicOpenSessions, + "room_open_session_compensation": userCron.CompensateRoomOpenSessions, "manager_user_block_expiry": userCron.ExpireManagerUserBlocks, + "admin_user_ban_expiry": userCron.ExpireAdminUserBans, // CP 榜单任务只占 cron 租约并调用 user-service;真实 MySQL 读取、wallet 头像框补齐和 Redis 替换都在 user-service 内完成。 "cp_intimacy_leaderboard": userCron.RefreshCPIntimacyLeaderboard, "message_fanout": activityCron.ProcessMessageFanoutBatch, "growth_level_reward": activityCron.ProcessLevelRewardBatch, + "temporary_growth_level": activityCron.ProcessTemporaryLevelBatch, "achievement_reward": activityCron.ProcessAchievementRewardBatch, "weekly_star_settlement": activityCron.ProcessWeeklyStarSettlementBatch, "agency_opening_settlement": activityCron.ProcessAgencyOpeningSettlementBatch, @@ -130,6 +133,7 @@ func New(cfg config.Config) (*App, error) { "room_turnover_reward_settlement": activityCron.ProcessRoomTurnoverRewardSettlementBatch, "game_level_event_relay": gameCron.ProcessLevelEventOutboxBatch, "host_salary_daily_settlement": walletCron.ProcessHostSalaryDailySettlementBatch, + "vip_daily_coin_rebate": walletCron.ProcessVIPDailyCoinRebateBatch, "host_salary_half_month_settlement": walletCron.ProcessHostSalaryHalfMonthSettlementBatch, "host_salary_month_end": walletCron.ProcessHostSalaryMonthEndBatch, }, appRegistry) diff --git a/services/cron-service/internal/config/config.go b/services/cron-service/internal/config/config.go index a2ba9eb6..bf1c1831 100644 --- a/services/cron-service/internal/config/config.go +++ b/services/cron-service/internal/config/config.go @@ -52,6 +52,8 @@ type TaskConfig struct { PendingPublishMaxAge string `yaml:"pending_publish_max_age"` // PublishingSessionMaxAge is used by mic_open_session_compensation for publishing sessions. PublishingSessionMaxAge string `yaml:"publishing_session_max_age"` + // OpenSessionMaxAge is used by room_open_session_compensation for open in-room sessions. + OpenSessionMaxAge string `yaml:"open_session_max_age"` } // Default returns local development defaults that follow the 13xxx port rule. @@ -140,6 +142,15 @@ func defaultTasks() map[string]TaskConfig { LockTTL: "30s", BatchSize: 500, }, + "vip_daily_coin_rebate": { + // scheduler 启动即执行,因此不能用 24h 间隔假装 UTC 0 点;每分钟触发一次, + // wallet owner 以 UTC task_day 的 completed run 快速 no-op,并在跨日后创建新批次。 + Enabled: true, + Interval: "1m", + Timeout: "30s", + LockTTL: "1m", + BatchSize: 500, + }, "growth_level_reward": { Enabled: true, Interval: "5s", @@ -147,6 +158,13 @@ func defaultTasks() map[string]TaskConfig { LockTTL: "30s", BatchSize: 100, }, + "temporary_growth_level": { + Enabled: true, + Interval: "5s", + Timeout: "15s", + LockTTL: "30s", + BatchSize: 100, + }, "achievement_reward": { Enabled: true, Interval: "5s", @@ -226,6 +244,15 @@ func defaultTasks() map[string]TaskConfig { PendingPublishMaxAge: "2m", PublishingSessionMaxAge: "12h", }, + "room_open_session_compensation": { + Enabled: true, + Interval: "60s", + Timeout: "10s", + LockTTL: "60s", + BatchSize: 200, + // stale worker 2 分钟兜底断线离房;24h 只兜 room-service 节点崩溃或 outbox 丢失的极端场景。 + OpenSessionMaxAge: "24h", + }, "manager_user_block_expiry": { Enabled: true, Interval: "30s", @@ -233,6 +260,13 @@ func defaultTasks() map[string]TaskConfig { LockTTL: "30s", BatchSize: 100, }, + "admin_user_ban_expiry": { + Enabled: true, + Interval: "30s", + Timeout: "10s", + LockTTL: "30s", + BatchSize: 100, + }, } } @@ -281,6 +315,10 @@ func mergeTaskConfig(def TaskConfig, current TaskConfig) TaskConfig { if current.PublishingSessionMaxAge == "" { current.PublishingSessionMaxAge = def.PublishingSessionMaxAge } + current.OpenSessionMaxAge = strings.TrimSpace(current.OpenSessionMaxAge) + if current.OpenSessionMaxAge == "" { + current.OpenSessionMaxAge = def.OpenSessionMaxAge + } if current.BatchSize <= 0 { current.BatchSize = def.BatchSize } diff --git a/services/cron-service/internal/config/config_test.go b/services/cron-service/internal/config/config_test.go index 4cd0200e..2f4abcb9 100644 --- a/services/cron-service/internal/config/config_test.go +++ b/services/cron-service/internal/config/config_test.go @@ -32,3 +32,10 @@ func TestDefaultCPIntimacyLeaderboardCronTaskRunsEveryFiveMinutes(t *testing.T) t.Fatalf("cp intimacy leaderboard cron task has unexpected defaults: %+v", task) } } + +func TestDefaultVIPDailyCoinRebateIsEnabled(t *testing.T) { + task, ok := Default().Tasks["vip_daily_coin_rebate"] + if !ok || !task.Enabled || task.Interval != "1m" || task.BatchSize != 500 { + t.Fatalf("VIP daily coin rebate task mismatch: %+v", task) + } +} diff --git a/services/cron-service/internal/integration/activity.go b/services/cron-service/internal/integration/activity.go index 4baf13ce..174bb97e 100644 --- a/services/cron-service/internal/integration/activity.go +++ b/services/cron-service/internal/integration/activity.go @@ -36,6 +36,15 @@ func (c *ActivityCronClient) ProcessLevelRewardBatch(ctx context.Context, req sc return activityCronBatchResult(resp), nil } +// ProcessTemporaryLevelBatch handles overlay expiry, notice retries and wallet-grant revocations in activity-service. +func (c *ActivityCronClient) ProcessTemporaryLevelBatch(ctx context.Context, req scheduler.BatchRequest) (scheduler.BatchResult, error) { + resp, err := c.client.ProcessTemporaryLevelBatch(ctx, activityCronBatchRequest(req)) + if err != nil { + return scheduler.BatchResult{}, err + } + return activityCronBatchResult(resp), nil +} + // ProcessAchievementRewardBatch handles achievement_reward_jobs. func (c *ActivityCronClient) ProcessAchievementRewardBatch(ctx context.Context, req scheduler.BatchRequest) (scheduler.BatchResult, error) { resp, err := c.client.ProcessAchievementRewardBatch(ctx, activityCronBatchRequest(req)) diff --git a/services/cron-service/internal/integration/user.go b/services/cron-service/internal/integration/user.go index a14b5c13..120a06ba 100644 --- a/services/cron-service/internal/integration/user.go +++ b/services/cron-service/internal/integration/user.go @@ -38,12 +38,24 @@ func (c *UserCronClient) CompensateMicOpenSessions(ctx context.Context, req sche return userCronResult(resp), err } +// CompensateRoomOpenSessions handles abnormal user_room_sessions closure. +func (c *UserCronClient) CompensateRoomOpenSessions(ctx context.Context, req scheduler.BatchRequest) (scheduler.BatchResult, error) { + resp, err := c.client.CompensateRoomOpenSessions(ctx, userCronRequest(req)) + return userCronResult(resp), err +} + // ExpireManagerUserBlocks handles timed manager-center bans after their unblock time. func (c *UserCronClient) ExpireManagerUserBlocks(ctx context.Context, req scheduler.BatchRequest) (scheduler.BatchResult, error) { resp, err := c.client.ExpireManagerUserBlocks(ctx, userCronRequest(req)) return userCronResult(resp), err } +// ExpireAdminUserBans handles timed admin bans; permanent bans are never selected by user-service. +func (c *UserCronClient) ExpireAdminUserBans(ctx context.Context, req scheduler.BatchRequest) (scheduler.BatchResult, error) { + resp, err := c.client.ExpireAdminUserBans(ctx, userCronRequest(req)) + return userCronResult(resp), err +} + // RefreshCPIntimacyLeaderboard 只触发 user-service 重建 CP 亲密值 zset 读模型,cron 不读取业务表。 func (c *UserCronClient) RefreshCPIntimacyLeaderboard(ctx context.Context, req scheduler.BatchRequest) (scheduler.BatchResult, error) { // userCronRequest 会带 run_id、worker_id、app_code 和 batch_size;user-service 用这些值做日志追踪、租户隔离和刷新上限。 @@ -66,6 +78,7 @@ func userCronRequest(req scheduler.BatchRequest) *userv1.CronBatchRequest { LockTtlMs: req.LockTTL.Milliseconds(), PendingPublishMaxAgeMs: req.PendingPublishMaxAge.Milliseconds(), PublishingSessionMaxAgeMs: req.PublishingSessionMaxAge.Milliseconds(), + OpenSessionMaxAgeMs: req.OpenSessionMaxAge.Milliseconds(), } } diff --git a/services/cron-service/internal/integration/wallet.go b/services/cron-service/internal/integration/wallet.go index 79c823f7..35ea45b8 100644 --- a/services/cron-service/internal/integration/wallet.go +++ b/services/cron-service/internal/integration/wallet.go @@ -27,6 +27,25 @@ func (c *WalletCronClient) ProcessHostSalaryDailySettlementBatch(ctx context.Con return walletCronBatchResult(resp), err } +// ProcessVIPDailyCoinRebateBatch 每分钟触发 wallet owner 的 UTC 日 run。cron 不传 task_day, +// 避免调度进程所在时区或延迟重启改变业务日期;wallet 会以自己的 UTC 时钟创建/复用当日快照。 +func (c *WalletCronClient) ProcessVIPDailyCoinRebateBatch(ctx context.Context, req scheduler.BatchRequest) (scheduler.BatchResult, error) { + resp, err := c.client.ProcessVipDailyCoinRebateBatch(ctx, &walletv1.ProcessVipDailyCoinRebateBatchRequest{ + RequestId: req.RunID, + AppCode: req.AppCode, + BatchSize: int32(req.BatchSize), + }) + if err != nil || resp == nil { + return scheduler.BatchResult{}, err + } + return scheduler.BatchResult{ + ClaimedCount: int(resp.GetScannedCount()), + ProcessedCount: int(resp.GetScannedCount()), + SuccessCount: int(resp.GetCreatedCount()), + HasMore: resp.GetHasMore(), + }, nil +} + func (c *WalletCronClient) ProcessHostSalaryHalfMonthSettlementBatch(ctx context.Context, req scheduler.BatchRequest) (scheduler.BatchResult, error) { if !isHostSalaryHalfMonthDue(walletCronNowUTC()) { // 半月结是否适用于某个主播由后台工资政策 settlement_mode 决定;cron 这里只负责在月中打开一次全局触发窗口。 diff --git a/services/cron-service/internal/scheduler/scheduler.go b/services/cron-service/internal/scheduler/scheduler.go index d6412c50..88958e7a 100644 --- a/services/cron-service/internal/scheduler/scheduler.go +++ b/services/cron-service/internal/scheduler/scheduler.go @@ -39,6 +39,7 @@ type BatchRequest struct { LockTTL time.Duration PendingPublishMaxAge time.Duration PublishingSessionMaxAge time.Duration + OpenSessionMaxAge time.Duration } // BatchResult summarizes one owner-service batch response. @@ -227,6 +228,7 @@ func (s *Scheduler) runOnce(ctx context.Context, taskName string, appCode string LockTTL: lockTTL, PendingPublishMaxAge: parseDuration(task.PendingPublishMaxAge, 0), PublishingSessionMaxAge: parseDuration(task.PublishingSessionMaxAge, 0), + OpenSessionMaxAge: parseDuration(task.OpenSessionMaxAge, 0), }) cancel() diff --git a/services/game-service/Dockerfile b/services/game-service/Dockerfile index ad976db5..8cf511e8 100644 --- a/services/game-service/Dockerfile +++ b/services/game-service/Dockerfile @@ -15,13 +15,14 @@ COPY . . RUN --mount=type=cache,id=hyapp-go-mod,target=/go/pkg/mod \ --mount=type=cache,id=hyapp-go-build,target=/root/.cache/go-build \ mkdir -p /out && \ - GOWORK=off CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/ ./services/game-service/cmd/server ./cmd/grpc-health-probe + GOWORK=off CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/ ./services/game-service/cmd/server ./services/game-service/cmd/replay-outbox ./cmd/grpc-health-probe FROM alpine:3.20 ENV TZ=UTC WORKDIR /app RUN apk add --no-cache ca-certificates && adduser -D -g '' appuser COPY --from=builder /out/server /app/server +COPY --from=builder /out/replay-outbox /app/replay-outbox COPY --from=builder /out/grpc-health-probe /app/grpc-health-probe COPY services/game-service/configs/config.docker.yaml /app/config.yaml USER appuser diff --git a/services/game-service/cmd/replay-outbox/main.go b/services/game-service/cmd/replay-outbox/main.go new file mode 100644 index 00000000..93795ec8 --- /dev/null +++ b/services/game-service/cmd/replay-outbox/main.go @@ -0,0 +1,59 @@ +// Command replay-outbox publishes a hard-bounded game owner-outbox batch for +// one App without starting game APIs or downstream gRPC connections. +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "os" + "strings" + "time" + + "hyapp/pkg/outboxreplay" + "hyapp/services/game-service/internal/app" + "hyapp/services/game-service/internal/config" +) + +func main() { + configPath := flag.String("config", "services/game-service/configs/config.yaml", "path to game-service config") + appCode := flag.String("app-code", "", "exact App partition to replay") + limit := flag.Int("limit", 0, "process-wide maximum rows, 1..500") + execute := flag.Bool("execute", false, "publish the bounded batch; default is a no-write dry-run") + confirmation := flag.String("confirm", "", "required execution confirmation") + timeout := flag.Duration("timeout", 2*time.Minute, "total command timeout, at most 30m") + flag.Parse() + + scope, err := outboxreplay.NewScope(*appCode, *limit, 500) + if err != nil { + fatal(err) + } + if *timeout <= 0 || *timeout > 30*time.Minute { + fatal(errors.New("timeout must be within (0,30m]")) + } + if *execute && strings.TrimSpace(*confirmation) != outboxreplay.ExecuteConfirmation { + fatal(fmt.Errorf("--execute requires --confirm=%s", outboxreplay.ExecuteConfirmation)) + } + cfg, err := config.Load(strings.TrimSpace(*configPath)) + if err != nil { + fatal(err) + } + if !*execute { + fmt.Printf("mode=dry-run app_code=%s limit=%d no_writes=true\n", scope.AppCode, scope.Limit) + return + } + + ctx, cancel := context.WithTimeout(context.Background(), *timeout) + defer cancel() + result, err := app.ReplayGameOutboxOnce(ctx, cfg, scope) + if err != nil { + fatal(fmt.Errorf("game replay stopped after %d rows: %w", result.Processed, err)) + } + fmt.Printf("mode=execute app_code=%s limit=%d processed=%d drained=%t\n", scope.AppCode, scope.Limit, result.Processed, result.Drained) +} + +func fatal(err error) { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) +} diff --git a/services/game-service/configs/config.docker.yaml b/services/game-service/configs/config.docker.yaml index 73bed56c..331b1083 100644 --- a/services/game-service/configs/config.docker.yaml +++ b/services/game-service/configs/config.docker.yaml @@ -34,6 +34,7 @@ rocketmq: producer_group: "hyapp-game-outbox-producer" outbox_worker: enabled: true + app_codes: [] poll_interval: "1s" batch_size: 100 publish_timeout: "3s" diff --git a/services/game-service/configs/config.tencent.example.yaml b/services/game-service/configs/config.tencent.example.yaml index e422243c..ae4eb0dc 100644 --- a/services/game-service/configs/config.tencent.example.yaml +++ b/services/game-service/configs/config.tencent.example.yaml @@ -34,6 +34,8 @@ rocketmq: producer_group: "hyapp-game-outbox-producer" outbox_worker: enabled: true + # 空值自动发现全部 App;历史积压回放时先设 ["lalu", "fami"],核对后再加入 "huwaa"。 + app_codes: [] poll_interval: "60s" batch_size: 10 publish_timeout: "3s" diff --git a/services/game-service/configs/config.yaml b/services/game-service/configs/config.yaml index e985c80f..c20e4de7 100644 --- a/services/game-service/configs/config.yaml +++ b/services/game-service/configs/config.yaml @@ -34,6 +34,7 @@ rocketmq: producer_group: "hyapp-game-outbox-producer" outbox_worker: enabled: true + app_codes: [] poll_interval: "1s" batch_size: 100 publish_timeout: "3s" diff --git a/services/game-service/internal/app/app.go b/services/game-service/internal/app/app.go index ef3f796c..1fa4ae65 100644 --- a/services/game-service/internal/app/app.go +++ b/services/game-service/internal/app/app.go @@ -11,10 +11,12 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" gamev1 "hyapp.local/api/proto/game/v1" + "hyapp/pkg/appcode" "hyapp/pkg/gamemq" "hyapp/pkg/grpchealth" "hyapp/pkg/healthhttp" "hyapp/pkg/logx" + "hyapp/pkg/outboxpartition" "hyapp/pkg/rocketmqx" servicegrpc "hyapp/pkg/servicekit/grpcserver" servicehealth "hyapp/pkg/servicekit/health" @@ -40,11 +42,13 @@ type App struct { activityConn *grpc.ClientConn robotConn *grpc.ClientConn outboxProducer *rocketmqx.Producer - outboxCtx context.Context - outboxCancel context.CancelFunc - outboxWG sync.WaitGroup - cfg config.Config - closeOnce sync.Once + // outboxPartitions 缓存真实 App 列表并让双实例从不同轮转起点公平 claim。 + outboxPartitions *outboxpartition.Partitions + outboxCtx context.Context + outboxCancel context.CancelFunc + outboxWG sync.WaitGroup + cfg config.Config + closeOnce sync.Once } func New(cfg config.Config) (*App, error) { @@ -154,7 +158,7 @@ func New(cfg config.Config) (*App, error) { } outboxCtx, outboxCancel := context.WithCancel(context.Background()) - return &App{server: server, listener: listener, health: health, healthHTTP: healthHTTP, repo: repo, walletConn: walletConn, userConn: userConn, activityConn: activityConn, robotConn: robotConn, outboxProducer: outboxProducer, outboxCtx: outboxCtx, outboxCancel: outboxCancel, cfg: cfg}, nil + return &App{server: server, listener: listener, health: health, healthHTTP: healthHTTP, repo: repo, walletConn: walletConn, userConn: userConn, activityConn: activityConn, robotConn: robotConn, outboxProducer: outboxProducer, outboxPartitions: outboxpartition.New(cfg.OutboxWorker.AppCodes, outboxpartition.DefaultDiscoveryRefreshInterval), outboxCtx: outboxCtx, outboxCancel: outboxCancel, cfg: cfg}, nil } func (a *App) Run() error { @@ -229,7 +233,17 @@ func (a *App) runOutboxWorker() { func (a *App) processOutboxBatch() (int, error) { workerID := "game-outbox-" + a.cfg.NodeID - records, err := a.repo.ClaimPendingGameOutbox(a.outboxCtx, workerID, a.cfg.OutboxWorker.BatchSize) + appCodes, discoveryErr := a.outboxPartitions.Resolve(a.outboxCtx, a.repo.ListGameOutboxAppCodes) + if discoveryErr != nil { + if len(appCodes) == 0 { + return 0, discoveryErr + } + // 已缓存 App 继续发布;低频刷新失败不应把整个 game outbox worker 停掉。 + logx.Warn(a.outboxCtx, "game_outbox_app_discovery_stale", slog.String("worker_id", workerID), slog.String("error", discoveryErr.Error())) + } + records, err := outboxpartition.ClaimFair(a.outboxCtx, a.outboxPartitions.Order(appCodes), a.cfg.OutboxWorker.BatchSize, func(claimCtx context.Context, appCode string, limit int) ([]mysqlstorage.GameOutboxRecord, error) { + return a.repo.ClaimPendingGameOutbox(appcode.WithContext(claimCtx, appCode), workerID, limit) + }) if err != nil { return 0, err } diff --git a/services/game-service/internal/app/outbox_replay.go b/services/game-service/internal/app/outbox_replay.go new file mode 100644 index 00000000..1dda7731 --- /dev/null +++ b/services/game-service/internal/app/outbox_replay.go @@ -0,0 +1,63 @@ +package app + +import ( + "context" + "errors" + "fmt" + "time" + + "hyapp/pkg/outboxpartition" + "hyapp/pkg/outboxreplay" + "hyapp/pkg/rocketmqx" + servicemq "hyapp/pkg/servicekit/mq" + "hyapp/services/game-service/internal/config" + mysqlstorage "hyapp/services/game-service/internal/storage/mysql" +) + +// ReplayGameOutboxOnce runs the ordinary game fact publisher for one App and a +// process-wide hard limit. It opens no gRPC listener and no downstream service +// connection, so an empty partition is a read-only no-op apart from MQ producer +// registration. +func ReplayGameOutboxOnce(ctx context.Context, cfg config.Config, scope outboxreplay.Scope) (outboxreplay.Result, error) { + validatedScope, err := outboxreplay.NewScope(scope.AppCode, scope.Limit, 500) + if err != nil { + return outboxreplay.Result{}, err + } + if !cfg.RocketMQ.GameOutbox.Enabled { + return outboxreplay.Result{}, errors.New("game RocketMQ outbox is not enabled") + } + repository, err := mysqlstorage.Open(ctx, cfg.MySQLDSN) + if err != nil { + return outboxreplay.Result{}, err + } + defer func() { _ = repository.Close() }() + producer, err := rocketmqx.NewProducer(rocketMQProducerConfig(cfg.RocketMQ)) + if err != nil { + return outboxreplay.Result{}, err + } + if err := servicemq.StartProducers([]*rocketmqx.Producer{producer}); err != nil { + return outboxreplay.Result{}, err + } + defer servicemq.ShutdownProducers([]*rocketmqx.Producer{producer}) + + replayConfig := cfg + replayConfig.OutboxWorker.BatchSize = 1 + replayConfig.OutboxWorker.AppCodes = []string{validatedScope.AppCode} + replayCtx, cancel := context.WithCancel(ctx) + defer cancel() + replayApp := &App{ + repo: repository, + outboxProducer: producer, + outboxPartitions: outboxpartition.New(replayConfig.OutboxWorker.AppCodes, outboxpartition.DefaultDiscoveryRefreshInterval), + outboxCtx: replayCtx, + outboxCancel: cancel, + cfg: replayConfig, + } + workerID := fmt.Sprintf("ops-game-outbox-%s-%d", validatedScope.AppCode, time.Now().UTC().UnixMilli()) + return outboxreplay.Run(ctx, validatedScope, func(context.Context) (int, error) { + // processOutboxBatch derives the same worker prefix as the live service; + // attach the unique run ID to NodeID so lock ownership remains auditable. + replayApp.cfg.NodeID = workerID + return replayApp.processOutboxBatch() + }) +} diff --git a/services/game-service/internal/config/config.go b/services/game-service/internal/config/config.go index 1e26ccdf..1903874e 100644 --- a/services/game-service/internal/config/config.go +++ b/services/game-service/internal/config/config.go @@ -9,6 +9,7 @@ import ( "hyapp/pkg/configx" "hyapp/pkg/logx" + "hyapp/pkg/outboxpartition" "hyapp/pkg/tencentim" ) @@ -95,7 +96,9 @@ type GameOutboxConfig struct { } type OutboxWorkerConfig struct { - Enabled bool `yaml:"enabled"` + Enabled bool `yaml:"enabled"` + // AppCodes 是可投递租户 allowlist;空值自动发现,非空用于分阶段回放历史积压。 + AppCodes []string `yaml:"app_codes"` PollInterval time.Duration `yaml:"poll_interval"` BatchSize int `yaml:"batch_size"` PublishTimeout time.Duration `yaml:"publish_timeout"` @@ -210,6 +213,7 @@ func (cfg *Config) Normalize() error { if cfg.OutboxWorker.PublishTimeout <= 0 { cfg.OutboxWorker.PublishTimeout = 3 * time.Second } + cfg.OutboxWorker.AppCodes = outboxpartition.Normalize(cfg.OutboxWorker.AppCodes, false) if cfg.OutboxWorker.Enabled && !cfg.RocketMQ.GameOutbox.Enabled { return errors.New("outbox_worker requires rocketmq.game_outbox.enabled") } diff --git a/services/game-service/internal/config/config_test.go b/services/game-service/internal/config/config_test.go index 9b0b498e..e5c92ddb 100644 --- a/services/game-service/internal/config/config_test.go +++ b/services/game-service/internal/config/config_test.go @@ -2,6 +2,27 @@ package config import "testing" +func TestOutboxWorkerAppCodesNormalizeForStagedReplay(t *testing.T) { + cfg := Default() + cfg.OutboxWorker.AppCodes = []string{" LALU ", "fami", "HUWAA", "fami"} + if err := cfg.Normalize(); err != nil { + t.Fatalf("normalize staged outbox config: %v", err) + } + if len(cfg.OutboxWorker.AppCodes) != 3 || cfg.OutboxWorker.AppCodes[0] != "lalu" || cfg.OutboxWorker.AppCodes[1] != "fami" || cfg.OutboxWorker.AppCodes[2] != "huwaa" { + t.Fatalf("normalized outbox app_codes = %v", cfg.OutboxWorker.AppCodes) + } +} + +func TestLocalOutboxWorkerEmptyAppCodesUsesDiscovery(t *testing.T) { + cfg, err := Load("../../configs/config.yaml") + if err != nil { + t.Fatalf("load local config: %v", err) + } + if len(cfg.OutboxWorker.AppCodes) != 0 { + t.Fatalf("local empty app_codes must retain automatic discovery: %v", cfg.OutboxWorker.AppCodes) + } +} + func TestLoadConfigsKeepActivityServiceAddr(t *testing.T) { tests := []struct { name string diff --git a/services/game-service/internal/storage/mysql/outbox_app_codes_test.go b/services/game-service/internal/storage/mysql/outbox_app_codes_test.go new file mode 100644 index 00000000..71a0c17c --- /dev/null +++ b/services/game-service/internal/storage/mysql/outbox_app_codes_test.go @@ -0,0 +1,30 @@ +package mysql + +import ( + "context" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestListGameOutboxAppCodesForcesPrimaryPrefixIndex(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT app_code FROM game_outbox FORCE INDEX (PRIMARY) ORDER BY app_code`)). + WillReturnRows(sqlmock.NewRows([]string{"app_code"}).AddRow("fami").AddRow("huwaa").AddRow("lalu")) + + apps, err := (&Repository{db: db}).ListGameOutboxAppCodes(context.Background()) + if err != nil { + t.Fatalf("list game outbox apps: %v", err) + } + if len(apps) != 3 || apps[0] != "fami" || apps[1] != "huwaa" || apps[2] != "lalu" { + t.Fatalf("game outbox apps mismatch: %v", apps) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("game app discovery query mismatch: %v", err) + } +} diff --git a/services/game-service/internal/storage/mysql/outbox_claim_priority_test.go b/services/game-service/internal/storage/mysql/outbox_claim_priority_test.go new file mode 100644 index 00000000..eea0668f --- /dev/null +++ b/services/game-service/internal/storage/mysql/outbox_claim_priority_test.go @@ -0,0 +1,81 @@ +package mysql + +import ( + "context" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "hyapp/pkg/appcode" +) + +func TestClaimPendingGameOutboxPrioritizesRecoveryOverPendingAtBatchOne(t *testing.T) { + tests := []struct { + name string + eventID string + recoveryStatus string + expectEmptyRetry bool + }{ + {name: "due retry", eventID: "game-retry", recoveryStatus: "retryable"}, + {name: "expired run", eventID: "game-expired", recoveryStatus: "running", expectEmptyRetry: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + + mock.ExpectBegin() + if tt.expectEmptyRetry { + mock.ExpectQuery(gameOutboxRetryClaimPattern()). + WithArgs("lalu", sqlmock.AnyArg(), 1). + WillReturnRows(gameOutboxClaimRows()) + } + claimPattern := gameOutboxRetryClaimPattern() + if tt.recoveryStatus == "running" { + claimPattern = gameOutboxExpiredClaimPattern() + } + mock.ExpectQuery(claimPattern). + WithArgs("lalu", sqlmock.AnyArg(), 1). + WillReturnRows(gameOutboxClaimRows().AddRow( + "lalu", tt.eventID, "GameOrderSettled", "order-1", int64(1001), "self", "dice", "debit", + int64(10), `{}`, int64(200), + )) + mock.ExpectExec(`(?s)UPDATE game_outbox\s+SET status = 'running', worker_id = \?, lock_until_ms = \?, updated_at_ms = \?\s+WHERE app_code = \? AND event_id = \?`). + WithArgs("worker-1", sqlmock.AnyArg(), sqlmock.AnyArg(), "lalu", tt.eventID). + WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectCommit() + + records, err := (&Repository{db: db}).ClaimPendingGameOutbox( + appcode.WithContext(context.Background(), "lalu"), "worker-1", 1, + ) + if err != nil { + t.Fatalf("claim game recovery row: %v", err) + } + if len(records) != 1 || records[0].EventID != tt.eventID { + t.Fatalf("batch=1 must claim %s before an older pending row: %+v", tt.recoveryStatus, records) + } + // No pending query is registered: issuing one after the recovery row filled + // the batch makes sqlmock fail, protecting the starvation boundary directly. + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("game claim priority mismatch: %v", err) + } + }) + } +} + +func gameOutboxClaimRows() *sqlmock.Rows { + return sqlmock.NewRows([]string{ + "app_code", "event_id", "event_type", "order_id", "user_id", "platform_code", "game_id", "op_type", + "coin_amount", "payload_json", "created_at_ms", + }) +} + +func gameOutboxRetryClaimPattern() string { + return `(?s)FROM game_outbox FORCE INDEX \(idx_game_outbox_status_created\).*WHERE app_code = \? AND status = 'retryable' AND next_retry_at_ms <= \? AND lock_until_ms = 0.*FOR UPDATE SKIP LOCKED` +} + +func gameOutboxExpiredClaimPattern() string { + return `(?s)FROM game_outbox FORCE INDEX \(idx_game_outbox_lock\).*WHERE app_code = \? AND status = 'running' AND lock_until_ms <= \?.*FOR UPDATE SKIP LOCKED` +} diff --git a/services/game-service/internal/storage/mysql/repository.go b/services/game-service/internal/storage/mysql/repository.go index 7fa55b19..8386fe0b 100644 --- a/services/game-service/internal/storage/mysql/repository.go +++ b/services/game-service/internal/storage/mysql/repository.go @@ -1152,6 +1152,29 @@ type GameOutboxRecord struct { LockUntilMS int64 } +// ListGameOutboxAppCodes enumerates real tenant keys from the game owner table. +// PRIMARY is ordered by (app_code,event_id), allowing cached discovery without +// weakening the app_code prefix used by every SKIP LOCKED claim transaction. +func (r *Repository) ListGameOutboxAppCodes(ctx context.Context) ([]string, error) { + if r == nil || r.db == nil { + return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + rows, err := r.db.QueryContext(ctx, `SELECT DISTINCT app_code FROM game_outbox FORCE INDEX (PRIMARY) ORDER BY app_code`) + if err != nil { + return nil, err + } + defer rows.Close() + appCodes := make([]string, 0, 4) + for rows.Next() { + var appCode string + if err := rows.Scan(&appCode); err != nil { + return nil, err + } + appCodes = append(appCodes, appCode) + } + return appCodes, rows.Err() +} + // ClaimPendingGameOutbox locks pending game facts so one worker publishes them. func (r *Repository) ClaimPendingGameOutbox(ctx context.Context, workerID string, batchSize int) ([]GameOutboxRecord, error) { if r == nil || r.db == nil { @@ -1173,21 +1196,14 @@ func (r *Repository) ClaimPendingGameOutbox(ctx context.Context, workerID string lockUntilMS := nowMS + (30 * time.Second).Milliseconds() appCode := appcode.FromContext(ctx) records := make([]GameOutboxRecord, 0, batchSize) + // Recovery work must claim before fresh pending facts. A continuously growing + // pending backlog can otherwise fill every batch and permanently strand both + // due retries and running rows whose worker lease expired. Keep the states in + // separate SKIP LOCKED queries so each branch retains its status-leading index. claimBranches := []struct { query string args []any }{ - { - query: ` - SELECT app_code, event_id, event_type, order_id, user_id, platform_code, game_id, op_type, - coin_amount, CAST(payload_json AS CHAR), created_at_ms - FROM game_outbox FORCE INDEX (idx_game_outbox_status_created) - WHERE app_code = ? AND status = 'pending' AND next_retry_at_ms <= ? AND lock_until_ms = 0 - ORDER BY created_at_ms ASC, event_id ASC - LIMIT ? FOR UPDATE SKIP LOCKED - `, - args: []any{appCode, nowMS}, - }, { query: ` SELECT app_code, event_id, event_type, order_id, user_id, platform_code, game_id, op_type, @@ -1207,6 +1223,17 @@ func (r *Repository) ClaimPendingGameOutbox(ctx context.Context, workerID string WHERE app_code = ? AND status = 'running' AND lock_until_ms <= ? ORDER BY created_at_ms ASC, event_id ASC LIMIT ? FOR UPDATE SKIP LOCKED + `, + args: []any{appCode, nowMS}, + }, + { + query: ` + SELECT app_code, event_id, event_type, order_id, user_id, platform_code, game_id, op_type, + coin_amount, CAST(payload_json AS CHAR), created_at_ms + FROM game_outbox FORCE INDEX (idx_game_outbox_status_created) + WHERE app_code = ? AND status = 'pending' AND next_retry_at_ms <= ? AND lock_until_ms = 0 + ORDER BY created_at_ms ASC, event_id ASC + LIMIT ? FOR UPDATE SKIP LOCKED `, args: []any{appCode, nowMS}, }, @@ -1356,23 +1383,34 @@ func (r *Repository) queryUnlockedLevelEventClaimKeys(ctx context.Context, tx *s } func (r *Repository) queryExpiredLevelEventClaimKeys(ctx context.Context, tx *sql.Tx, appCode string, nowMS int64, batchSize int) ([]levelEventClaimKey, error) { - // 过期锁接管仍使用 retry/lock 前缀缩小范围,但只锁主键,避免大 JSON 字段进入排序和慢日志。 - return queryLevelEventClaimKeysByStatus(batchSize, func(status string) ([]levelEventClaimKey, error) { + // 过期接管只处理 running:pending/failed 已由 unlocked path 负责。拆开 legacy lock=0 + // 与正常过期锁可继续利用 (app_code,status,locked_until_ms,...) 前缀,同时明确 + // 排除未来锁,避免并发 cron 抢走仍由其它 worker 处理的事实。 + query := func(lockCondition string, args ...any) ([]levelEventClaimKey, error) { + queryArgs := []any{appCode, "running"} + queryArgs = append(queryArgs, args...) + queryArgs = append(queryArgs, nowMS, batchSize) rows, err := tx.QueryContext(ctx, ` SELECT event_id, created_at_ms FROM game_level_event_outbox FORCE INDEX (idx_game_level_event_claim) - WHERE app_code = ? AND status = ? AND next_retry_at_ms <= ? - AND locked_until_ms > 0 AND locked_until_ms <= ? + WHERE app_code = ? AND status = ? AND `+lockCondition+` AND next_retry_at_ms <= ? ORDER BY created_at_ms ASC, event_id ASC - LIMIT ? FOR UPDATE SKIP LOCKED`, - appCode, status, nowMS, nowMS, batchSize, - ) + LIMIT ? FOR UPDATE SKIP LOCKED`, queryArgs...) if err != nil { return nil, err } defer rows.Close() return scanLevelEventClaimKeys(rows) - }) + } + legacyUnlocked, err := query("locked_until_ms = 0") + if err != nil { + return nil, err + } + expired, err := query("locked_until_ms > 0 AND locked_until_ms <= ?", nowMS) + if err != nil { + return nil, err + } + return mergeLevelEventClaimKeys(batchSize, legacyUnlocked, expired), nil } func queryLevelEventClaimKeysByStatus(batchSize int, query func(status string) ([]levelEventClaimKey, error)) ([]levelEventClaimKey, error) { diff --git a/services/game-service/internal/storage/mysql/repository_test.go b/services/game-service/internal/storage/mysql/repository_test.go index 3af80bd6..f68e83c3 100644 --- a/services/game-service/internal/storage/mysql/repository_test.go +++ b/services/game-service/internal/storage/mysql/repository_test.go @@ -483,6 +483,65 @@ func TestClaimPendingLevelEventsPreservesCreatedOrderAcrossStatuses(t *testing.T } } +func TestClaimPendingLevelEventsReclaimsLegacyAndExpiredRunningLocks(t *testing.T) { + caller := mysqlschema.CallerFile(t, 1) + schema := mysqlschema.New(t, mysqlschema.Config{ + InitDBPath: mysqlschema.InitDBPath(t, caller, "../../../deploy/mysql/initdb/001_game_service.sql"), + DatabasePrefix: "hy_game_test", + }) + + ctx := appcode.WithContext(context.Background(), "lalu") + repo, err := Open(ctx, schema.DSN) + if err != nil { + t.Fatalf("open repository failed: %v", err) + } + t.Cleanup(func() { _ = repo.Close() }) + + insertRunning := func(eventID string, lockedUntilMS int64, createdAtMS int64) { + t.Helper() + if _, err := repo.db.ExecContext(ctx, ` + INSERT INTO game_level_event_outbox ( + app_code, event_id, order_id, user_id, game_id, provider_order_id, + provider_round_id, coin_amount, wallet_transaction_id, payload_json, + status, attempt_count, next_retry_at_ms, locked_by, locked_until_ms, + failure_reason, occurred_at_ms, created_at_ms, updated_at_ms + ) VALUES ('lalu', ?, ?, 42, 'game_1', ?, '', 100, 'wallet_tx_1', '{}', + 'running', 1, 0, 'old-worker', ?, '', ?, ?, ?)`, + eventID, "order_"+eventID, "provider_"+eventID, lockedUntilMS, createdAtMS, createdAtMS, createdAtMS, + ); err != nil { + t.Fatalf("insert running level event %s failed: %v", eventID, err) + } + } + insertRunning("legacy_zero_lock", 0, 1000) + insertRunning("expired_lock", 2500, 1100) + insertRunning("future_lock", 4000, 1200) + + events, err := repo.ClaimPendingLevelEvents(ctx, "replacement-worker", 3000, 30*time.Second, 10) + if err != nil { + t.Fatalf("claim expired running events failed: %v", err) + } + if len(events) != 2 || events[0].EventID != "legacy_zero_lock" || events[1].EventID != "expired_lock" { + t.Fatalf("reclaimed running events mismatch: %+v", events) + } + for _, event := range events { + if event.Status != "running" { + t.Fatalf("reclaimed event must expose its pre-claim running state: %+v", event) + } + } + + var lockedBy string + var lockedUntilMS int64 + if err := repo.db.QueryRowContext(ctx, ` + SELECT locked_by, locked_until_ms FROM game_level_event_outbox + WHERE app_code = 'lalu' AND event_id = 'future_lock'`, + ).Scan(&lockedBy, &lockedUntilMS); err != nil { + t.Fatalf("query future running lock failed: %v", err) + } + if lockedBy != "old-worker" || lockedUntilMS != 4000 { + t.Fatalf("future lock was stolen: locked_by=%q locked_until_ms=%d", lockedBy, lockedUntilMS) + } +} + func TestLevelEventClaimKeyQueryUsesOrderIndexWithoutFilesort(t *testing.T) { caller := mysqlschema.CallerFile(t, 1) schema := mysqlschema.New(t, mysqlschema.Config{ diff --git a/services/gateway-service/configs/config.docker.yaml b/services/gateway-service/configs/config.docker.yaml index 7813a36f..c153f530 100644 --- a/services/gateway-service/configs/config.docker.yaml +++ b/services/gateway-service/configs/config.docker.yaml @@ -25,6 +25,12 @@ cors: - "X-Bundle-ID" - "X-App-Platform" - "X-Platform" + - "X-App-Version" + - "X-App-Build-Number" + - "X-Device-Model" + - "X-Device-Manufacturer" + - "X-OS-Version" + - "X-Device-IMEI" expose_headers: ["X-Request-ID"] allow_credentials: true max_age_sec: 600 @@ -86,6 +92,26 @@ auth_rate_limit: display_user_id_ip_limit: 10 display_user_id_limit: 50 session_id_ip_limit: 30 +gift_capacity_limit: + # Docker 多 gateway 副本共享 Redis;各聚合层在自己的 Cluster slot 内原子扣减,用户层同时占用在途槽。 + enabled: true + redis_addr: "redis:6379" + redis_password: "" + redis_db: 0 + key_prefix: "gateway:gift:capacity:" + user_bucket_capacity: 5000 + user_refill_tokens_per_second: 500 + room_bucket_capacity: 20000 + room_refill_tokens_per_second: 5000 + app_bucket_capacity: 100000 + app_refill_tokens_per_second: 25000 + max_targets_per_request: 30 + max_gift_count_per_request: 999 + max_gift_units_per_request: 5000 + max_in_flight: 2 + in_flight_lease: "10s" + redis_operation_timeout: "500ms" + request_timeout: "5s" login_risk: enabled: true redis_addr: "redis:6379" diff --git a/services/gateway-service/configs/config.tencent.example.yaml b/services/gateway-service/configs/config.tencent.example.yaml index b2c47eb9..9b369326 100644 --- a/services/gateway-service/configs/config.tencent.example.yaml +++ b/services/gateway-service/configs/config.tencent.example.yaml @@ -29,6 +29,12 @@ cors: - "X-Bundle-ID" - "X-App-Platform" - "X-Platform" + - "X-App-Version" + - "X-App-Build-Number" + - "X-Device-Model" + - "X-Device-Manufacturer" + - "X-OS-Version" + - "X-Device-IMEI" expose_headers: ["X-Request-ID"] allow_credentials: true max_age_sec: 600 @@ -90,6 +96,27 @@ auth_rate_limit: display_user_id_ip_limit: 10 display_user_id_limit: 50 session_id_ip_limit: 30 +gift_capacity_limit: + # 所有生产 gateway 节点必须共享同一 Redis,才能让 app:user:room 令牌和 app:user 并发槽全局生效。 + enabled: true + redis_addr: "TENCENT_REDIS_HOST:6379" + redis_password: "TENCENT_REDIS_PASSWORD" + redis_db: 0 + key_prefix: "gateway:gift:capacity:" + # 按 gift_count*去重目标数计费;生产值需结合幸运礼物 p99 和 Broker SLO 灰度校准。 + user_bucket_capacity: 5000 + user_refill_tokens_per_second: 500 + room_bucket_capacity: 20000 + room_refill_tokens_per_second: 5000 + app_bucket_capacity: 100000 + app_refill_tokens_per_second: 25000 + max_targets_per_request: 30 + max_gift_count_per_request: 999 + max_gift_units_per_request: 5000 + max_in_flight: 2 + in_flight_lease: "10s" + redis_operation_timeout: "500ms" + request_timeout: "5s" login_risk: enabled: true redis_addr: "TENCENT_REDIS_HOST:6379" @@ -134,7 +161,8 @@ tencent_rtc: # 语音房场景固定使用 VoiceChatRoom,所有客户端必须保持一致。 app_scene: "voice_chat_room" # 腾讯 RTC 控制台配置的事件回调地址,用于房间退出和音频发布状态同步。 - callback_url: "https://api.example.com/api/v1/tencent-rtc/callback?app_code=default" + # 多 App 共用腾讯 RTC 应用时不要拼 app_code;gateway 会按回调 room_id 向 room-service 解析真实租户。 + callback_url: "https://api.example.com/api/v1/tencent-rtc/callback" # 腾讯 RTC 回调签名 key,必须和控制台配置保持一致;不要复用 UserSig SecretKey。 callback_sign_key: "TENCENT_RTC_CALLBACK_SIGN_KEY" diff --git a/services/gateway-service/configs/config.yaml b/services/gateway-service/configs/config.yaml index ca1f682b..f239d637 100644 --- a/services/gateway-service/configs/config.yaml +++ b/services/gateway-service/configs/config.yaml @@ -25,6 +25,12 @@ cors: - "X-Bundle-ID" - "X-App-Platform" - "X-Platform" + - "X-App-Version" + - "X-App-Build-Number" + - "X-Device-Model" + - "X-Device-Manufacturer" + - "X-OS-Version" + - "X-Device-IMEI" expose_headers: ["X-Request-ID"] allow_credentials: true max_age_sec: 600 @@ -86,6 +92,27 @@ auth_rate_limit: display_user_id_ip_limit: 10 display_user_id_limit: 50 session_id_ip_limit: 30 +gift_capacity_limit: + # 送礼按 gift_count*去重 target_count 扣令牌;这同时覆盖多人账务和幸运礼物逐份开奖成本。 + enabled: true + redis_addr: "127.0.0.1:13379" + redis_password: "" + redis_db: 0 + key_prefix: "gateway:gift:capacity:" + # 桶容量至少容纳一个合法最大工作量;持续速率在极端 999 数量后施加自然冷却。 + user_bucket_capacity: 5000 + user_refill_tokens_per_second: 500 + room_bucket_capacity: 20000 + room_refill_tokens_per_second: 5000 + app_bucket_capacity: 100000 + app_refill_tokens_per_second: 25000 + max_targets_per_request: 30 + max_gift_count_per_request: 999 + max_gift_units_per_request: 5000 + max_in_flight: 2 + in_flight_lease: "10s" + redis_operation_timeout: "500ms" + request_timeout: "5s" login_risk: enabled: true redis_addr: "127.0.0.1:13379" diff --git a/services/gateway-service/internal/app/app.go b/services/gateway-service/internal/app/app.go index 880056d6..1fbb600f 100644 --- a/services/gateway-service/internal/app/app.go +++ b/services/gateway-service/internal/app/app.go @@ -25,6 +25,7 @@ import ( "hyapp/services/gateway-service/internal/financewithdrawal" "hyapp/services/gateway-service/internal/healthcheck" httptransport "hyapp/services/gateway-service/internal/transport/http" + "hyapp/services/gateway-service/internal/transport/http/giftlimit" ) // App 装配 gateway 的 HTTP 入口。 @@ -293,6 +294,12 @@ func New(cfg config.Config) (*App, error) { return nil, err } redisClose = joinClose(redisClose, authRedisClose) + giftCapacityRedisClose, err := configureGiftCapacityLimit(handler, cfg.GiftCapacityLimit) + if err != nil { + closeGatewayDeps(redisClose) + return nil, err + } + redisClose = joinClose(redisClose, giftCapacityRedisClose) loginRiskRedisClose, err := configureLoginRisk(handler, cfg.LoginRisk, loginRiskConfig) if err != nil { closeGatewayDeps(redisClose) @@ -417,6 +424,41 @@ func configureAuthRateLimit(handler *httptransport.Handler, cfg config.AuthRateL return redisClient.Close, nil } +func configureGiftCapacityLimit(handler *httptransport.Handler, cfg config.GiftCapacityLimitConfig) (func() error, error) { + handler.SetGiftRequestLimits(cfg.MaxGiftCountPerRequest, cfg.MaxGiftUnitsPerRequest) + if !cfg.Enabled { + handler.SetGiftCapacityProtection(false, cfg.RequestTimeout, cfg.MaxTargetsPerRequest, nil) + return nil, nil + } + redisAddr := strings.TrimSpace(cfg.RedisAddr) + if redisAddr == "" { + return nil, errors.New("gift capacity limit redis_addr is required") + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + redisClient, err := giftlimit.NewRedisClient(ctx, redisAddr, cfg.RedisPassword, cfg.RedisDB) + if err != nil { + return nil, fmt.Errorf("connect gift capacity limit redis: %w", err) + } + runtimeConfig := giftlimit.Config{ + Enabled: true, + KeyPrefix: cfg.KeyPrefix, + UserBucketCapacity: cfg.UserBucketCapacity, + UserRefillTokensPerSecond: cfg.UserRefillTokensPerSecond, + RoomBucketCapacity: cfg.RoomBucketCapacity, + RoomRefillTokensPerSecond: cfg.RoomRefillTokensPerSecond, + AppBucketCapacity: cfg.AppBucketCapacity, + AppRefillTokensPerSecond: cfg.AppRefillTokensPerSecond, + MaxInFlight: cfg.MaxInFlight, + InFlightLease: cfg.InFlightLease, + RedisOperationTimeout: cfg.RedisOperationTimeout, + } + // 与 public auth 频控相同,生产送礼保护必须使用共享 Redis;进程内计数无法约束多副本总流量。 + handler.SetGiftCapacityProtection(true, cfg.RequestTimeout, cfg.MaxTargetsPerRequest, giftlimit.NewRedisLimiter(redisClient, runtimeConfig)) + return redisClient.Close, nil +} + func configureUserLeaderboard(handler *httptransport.Handler, cfg config.LeaderboardConfig) (func() error, error) { redisAddr := strings.TrimSpace(cfg.RedisAddr) if redisAddr == "" { diff --git a/services/gateway-service/internal/app/app_test.go b/services/gateway-service/internal/app/app_test.go index c6d20422..b36e121b 100644 --- a/services/gateway-service/internal/app/app_test.go +++ b/services/gateway-service/internal/app/app_test.go @@ -30,3 +30,22 @@ func TestConfigureAuthRateLimitDisabledDoesNotRequireRedis(t *testing.T) { t.Fatal("disabled auth rate limit must not allocate redis client") } } + +func TestConfigureGiftCapacityLimitRequiresRedisAddrWhenEnabled(t *testing.T) { + handler := httptransport.NewHandler(nil) + _, err := configureGiftCapacityLimit(handler, config.GiftCapacityLimitConfig{Enabled: true}) + if err == nil || !strings.Contains(err.Error(), "redis_addr is required") { + t.Fatalf("enabled gift capacity limit must require redis_addr, err=%v", err) + } +} + +func TestConfigureGiftCapacityLimitDisabledDoesNotRequireRedis(t *testing.T) { + handler := httptransport.NewHandler(nil) + closeRedis, err := configureGiftCapacityLimit(handler, config.GiftCapacityLimitConfig{Enabled: false}) + if err != nil { + t.Fatalf("disabled gift capacity limit should not connect redis: %v", err) + } + if closeRedis != nil { + t.Fatal("disabled gift capacity limit must not allocate redis client") + } +} diff --git a/services/gateway-service/internal/appconfig/gift_combo.go b/services/gateway-service/internal/appconfig/gift_combo.go new file mode 100644 index 00000000..e22f3c1f --- /dev/null +++ b/services/gateway-service/internal/appconfig/gift_combo.go @@ -0,0 +1,179 @@ +package appconfig + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strings" + + "hyapp/pkg/giftlimits" +) + +const ( + giftComboConfigGroup = "room-gift-combo" + giftComboConfigKey = "default" +) + +const getGiftComboConfigSQL = ` + SELECT app_code, COALESCE(value, ''), is_deleted, updated_at_ms + FROM admin_app_configs + WHERE app_code IN ('', ?) AND ` + "`group`" + ` = ? AND ` + "`key`" + ` = ? + ORDER BY app_code ASC` + +// GiftComboConfig 是礼物面板下发给 Flutter 的运行时策略。 +// 这里仅控制客户端如何封批和施加背压;账务数量、幂等和容量上限仍由 owner service 强制执行, +// 因而旧客户端、篡改客户端或缓存了旧配置的客户端都不能绕过服务端保护。 +type GiftComboConfig struct { + Enabled bool `json:"enabled"` + APIVersion int32 `json:"api_version"` + WindowMS int32 `json:"window_ms"` + MaxGiftCountPerBatch int32 `json:"max_gift_count_per_batch"` + MaxGiftUnitsPerBatch int32 `json:"max_gift_units_per_batch"` + MaxInFlight int32 `json:"max_in_flight"` + MaxPendingBatches int32 `json:"max_pending_batches"` + RetryMaxAttempts int32 `json:"retry_max_attempts"` + RetryBaseDelayMS int32 `json:"retry_base_delay_ms"` + RolloutBasisPoints int32 `json:"rollout_basis_points"` + UpdatedAtMS int64 `json:"updated_at_ms"` +} + +type storedGiftComboConfig struct { + appCode string + value string + isDeleted bool + updatedAtMS int64 +} + +// DefaultGiftComboConfig keeps the feature usable when no admin row exists. A production kill switch +// is an explicit scoped row with enabled=false, so a transient config read failure is never silently +// converted into a different batching policy by this method. +func DefaultGiftComboConfig() GiftComboConfig { + return GiftComboConfig{ + Enabled: true, + APIVersion: 2, + WindowMS: 300, + MaxGiftCountPerBatch: 999, + MaxGiftUnitsPerBatch: 5_000, + MaxInFlight: 2, + MaxPendingBatches: 20, + RetryMaxAttempts: 5, + RetryBaseDelayMS: 250, + // 缺少后台配置时必须保持 0% 灰度。发布代码不能等价于全量打开财务主链路; + // 运维确认 V2、Saga 和 MQ 指标后,再按 App 显式提高该值。 + RolloutBasisPoints: 0, + } +} + +// GiftComboConfig returns the app-scoped remote policy. A scoped tombstone intentionally suppresses +// the global baseline and falls back to code defaults, matching the admin_app_configs overlay contract. +func (r *MySQLReader) GiftComboConfig(ctx context.Context, appCode string) (GiftComboConfig, error) { + if r == nil || r.db == nil { + return GiftComboConfig{}, errors.New("app config reader is not configured") + } + + appCode = normalizeAppCode(appCode) + rows, err := r.db.QueryContext(ctx, getGiftComboConfigSQL, appCode, giftComboConfigGroup, giftComboConfigKey) + if err != nil { + return GiftComboConfig{}, err + } + defer rows.Close() + + var baseline *storedGiftComboConfig + var scoped *storedGiftComboConfig + for rows.Next() { + var item storedGiftComboConfig + if err := rows.Scan(&item.appCode, &item.value, &item.isDeleted, &item.updatedAtMS); err != nil { + return GiftComboConfig{}, err + } + if strings.EqualFold(strings.TrimSpace(item.appCode), appCode) { + copy := item + scoped = © + continue + } + copy := item + baseline = © + } + if err := rows.Err(); err != nil { + return GiftComboConfig{}, err + } + + selected := baseline + if scoped != nil { + if scoped.isDeleted { + return DefaultGiftComboConfig(), nil + } + selected = scoped + } + if selected == nil || selected.isDeleted || strings.TrimSpace(selected.value) == "" { + return DefaultGiftComboConfig(), nil + } + config, err := ParseGiftComboConfig(selected.value) + if err != nil { + return GiftComboConfig{}, fmt.Errorf("parse gift combo config for app %s: %w", appCode, err) + } + config.UpdatedAtMS = selected.updatedAtMS + return config, nil +} + +// ParseGiftComboConfig overlays a stored JSON object on safe defaults. This allows newly introduced +// fields to acquire defaults while old admin rows remain valid during rolling upgrades. +func ParseGiftComboConfig(raw string) (GiftComboConfig, error) { + config := DefaultGiftComboConfig() + if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &config); err != nil { + return GiftComboConfig{}, err + } + if config.APIVersion == 1 { + // 早期灰度配置曾把 1 当作客户端降级开关。读取时一次性提升为当前 V2 契约,避免 + // 已有 admin_app_configs 行让礼物面板降级失败;新的配置写入仍只能提交 2。 + config.APIVersion = 2 + } + return NormalizeGiftComboConfig(config) +} + +// NormalizeGiftComboConfig rejects policies that could disable client backpressure or exceed the V2 +// contract. Validation is deliberately shared with admin writes, but callers still enforce server caps. +func NormalizeGiftComboConfig(config GiftComboConfig) (GiftComboConfig, error) { + if config.APIVersion == 0 { + config.APIVersion = 2 + } + if config.APIVersion != 2 { + // api_version 是当前客户端契约标记,不是远程降级开关。旧 Flutter 自己固定调用 V1; + // 新 Flutter 必须始终使用具备完整幂等结果重放语义的 V2。 + return GiftComboConfig{}, fmt.Errorf("api_version must be 2") + } + if config.WindowMS < 100 || config.WindowMS > 1_000 { + return GiftComboConfig{}, fmt.Errorf("window_ms must be between 100 and 1000") + } + if config.MaxGiftCountPerBatch < 1 || config.MaxGiftCountPerBatch > 999 { + return GiftComboConfig{}, fmt.Errorf("max_gift_count_per_batch must be between 1 and 999") + } + if config.MaxGiftUnitsPerBatch < 1 || int64(config.MaxGiftUnitsPerBatch) > giftlimits.MaxTotalGiftUnits { + return GiftComboConfig{}, fmt.Errorf("max_gift_units_per_batch must be between 1 and %d", giftlimits.MaxTotalGiftUnits) + } + if config.MaxInFlight < 1 || config.MaxInFlight > 2 { + return GiftComboConfig{}, fmt.Errorf("max_in_flight must be between 1 and 2") + } + if config.MaxPendingBatches < 1 || config.MaxPendingBatches > 50 { + return GiftComboConfig{}, fmt.Errorf("max_pending_batches must be between 1 and 50") + } + if config.MaxPendingBatches < config.MaxInFlight { + // Flutter allocates in-flight work from the same bounded batch queue. A smaller queue cannot + // represent the advertised concurrency and would make the client silently rewrite policy. + return GiftComboConfig{}, fmt.Errorf("max_pending_batches must be greater than or equal to max_in_flight") + } + if config.RetryMaxAttempts < 1 || config.RetryMaxAttempts > 10 { + return GiftComboConfig{}, fmt.Errorf("retry_max_attempts must be between 1 and 10") + } + if config.RetryBaseDelayMS < 100 || config.RetryBaseDelayMS > 5_000 { + return GiftComboConfig{}, fmt.Errorf("retry_base_delay_ms must be between 100 and 5000") + } + if config.RolloutBasisPoints < 0 || config.RolloutBasisPoints > 10_000 { + return GiftComboConfig{}, fmt.Errorf("rollout_basis_points must be between 0 and 10000") + } + return config, nil +} + +var _ interface { + GiftComboConfig(context.Context, string) (GiftComboConfig, error) +} = (*MySQLReader)(nil) diff --git a/services/gateway-service/internal/appconfig/gift_combo_test.go b/services/gateway-service/internal/appconfig/gift_combo_test.go new file mode 100644 index 00000000..f484606f --- /dev/null +++ b/services/gateway-service/internal/appconfig/gift_combo_test.go @@ -0,0 +1,49 @@ +package appconfig + +import "testing" + +func TestParseGiftComboConfigOverlaysDefaultsAndPreservesKillSwitch(t *testing.T) { + config, err := ParseGiftComboConfig(`{"enabled":false,"rollout_basis_points":2500}`) + if err != nil { + t.Fatalf("ParseGiftComboConfig failed: %v", err) + } + if config.Enabled || config.RolloutBasisPoints != 2500 || config.APIVersion != 2 || config.WindowMS != 300 || config.MaxGiftCountPerBatch != 999 || config.MaxGiftUnitsPerBatch != 5_000 || config.MaxInFlight != 2 || config.RetryBaseDelayMS != 250 { + t.Fatalf("partial remote config did not inherit safe defaults: %+v", config) + } +} + +func TestParseGiftComboConfigPromotesStoredV1MarkerWithoutDowngradingClient(t *testing.T) { + config, err := ParseGiftComboConfig(`{"api_version":1,"enabled":false}`) + if err != nil { + t.Fatalf("legacy stored config must remain readable: %v", err) + } + if config.APIVersion != 2 || config.Enabled { + t.Fatalf("stored V1 marker must become a V2 micro-batch kill switch: %+v", config) + } +} + +func TestDefaultGiftComboConfigRequiresExplicitRollout(t *testing.T) { + config := DefaultGiftComboConfig() + if config.RolloutBasisPoints != 0 { + t.Fatalf("code deployment must not enable micro-batching rollout implicitly: %+v", config) + } +} + +func TestNormalizeGiftComboConfigRejectsUnboundedClientPolicy(t *testing.T) { + tests := []GiftComboConfig{ + {APIVersion: 1, WindowMS: 300, MaxGiftCountPerBatch: 999, MaxGiftUnitsPerBatch: 5_000, MaxInFlight: 2, MaxPendingBatches: 20, RetryMaxAttempts: 5, RetryBaseDelayMS: 250, RolloutBasisPoints: 10_000}, + {APIVersion: 3, WindowMS: 300, MaxGiftCountPerBatch: 999, MaxGiftUnitsPerBatch: 5_000, MaxInFlight: 2, MaxPendingBatches: 20, RetryMaxAttempts: 5, RetryBaseDelayMS: 250, RolloutBasisPoints: 10_000}, + {APIVersion: 2, WindowMS: 99, MaxGiftCountPerBatch: 999, MaxGiftUnitsPerBatch: 5_000, MaxInFlight: 2, MaxPendingBatches: 20, RetryMaxAttempts: 5, RetryBaseDelayMS: 250, RolloutBasisPoints: 10_000}, + {APIVersion: 2, WindowMS: 300, MaxGiftCountPerBatch: 1_000, MaxGiftUnitsPerBatch: 5_000, MaxInFlight: 2, MaxPendingBatches: 20, RetryMaxAttempts: 5, RetryBaseDelayMS: 250, RolloutBasisPoints: 10_000}, + {APIVersion: 2, WindowMS: 300, MaxGiftCountPerBatch: 999, MaxGiftUnitsPerBatch: 5_001, MaxInFlight: 2, MaxPendingBatches: 20, RetryMaxAttempts: 5, RetryBaseDelayMS: 250, RolloutBasisPoints: 10_000}, + {APIVersion: 2, WindowMS: 300, MaxGiftCountPerBatch: 999, MaxGiftUnitsPerBatch: 5_000, MaxInFlight: 3, MaxPendingBatches: 20, RetryMaxAttempts: 5, RetryBaseDelayMS: 250, RolloutBasisPoints: 10_000}, + {APIVersion: 2, WindowMS: 300, MaxGiftCountPerBatch: 999, MaxGiftUnitsPerBatch: 5_000, MaxInFlight: 2, MaxPendingBatches: 0, RetryMaxAttempts: 5, RetryBaseDelayMS: 250, RolloutBasisPoints: 10_000}, + {APIVersion: 2, WindowMS: 300, MaxGiftCountPerBatch: 999, MaxGiftUnitsPerBatch: 5_000, MaxInFlight: 2, MaxPendingBatches: 1, RetryMaxAttempts: 5, RetryBaseDelayMS: 250, RolloutBasisPoints: 10_000}, + {APIVersion: 2, WindowMS: 300, MaxGiftCountPerBatch: 999, MaxGiftUnitsPerBatch: 5_000, MaxInFlight: 2, MaxPendingBatches: 20, RetryMaxAttempts: 5, RetryBaseDelayMS: 99, RolloutBasisPoints: 10_000}, + } + for _, config := range tests { + if _, err := NormalizeGiftComboConfig(config); err == nil { + t.Fatalf("invalid policy was accepted: %+v", config) + } + } +} diff --git a/services/gateway-service/internal/appconfig/mysql.go b/services/gateway-service/internal/appconfig/mysql.go index 53213c1c..b398e7c6 100644 --- a/services/gateway-service/internal/appconfig/mysql.go +++ b/services/gateway-service/internal/appconfig/mysql.go @@ -17,8 +17,8 @@ import ( const h5LinkGroup = "h5-links" const roomRegionWhitelistGroup = "room-region-whitelist" -const listH5LinksSQL = "SELECT `key`, COALESCE(description, ''), COALESCE(value, ''), updated_at_ms FROM admin_app_configs WHERE `group` = ? ORDER BY `key` ASC" -const getRoomRegionWhitelistSQL = "SELECT COALESCE(value, '') FROM admin_app_configs WHERE `group` = ? AND `key` = ? LIMIT 1" +const listH5LinksSQL = "SELECT `key`, COALESCE(description, ''), COALESCE(value, ''), updated_at_ms FROM admin_app_configs WHERE app_code = ? AND `group` = ? AND is_deleted = FALSE ORDER BY `key` ASC" +const getRoomRegionWhitelistSQL = "SELECT COALESCE(value, '') FROM admin_app_configs WHERE app_code = '' AND `group` = ? AND `key` = ? LIMIT 1" const bdLeaderPositionAliasSQL = ` SELECT position_alias FROM admin_bd_leader_position_aliases @@ -262,7 +262,9 @@ func (r *MySQLReader) ListH5Links(ctx context.Context, query H5LinkQuery) ([]H5L return nil, errors.New("app config reader is not configured") } - rows, err := r.db.QueryContext(ctx, listH5LinksSQL, h5LinkGroup) + appCode := normalizeAppCode(query.AppCode) + // H5 链接是 App 专属运营内容;未配置的 App 返回空列表,不能回退到空 app_code 历史基线。 + rows, err := r.db.QueryContext(ctx, listH5LinksSQL, appCode, h5LinkGroup) if err != nil { return nil, err } diff --git a/services/gateway-service/internal/appconfig/mysql_test.go b/services/gateway-service/internal/appconfig/mysql_test.go index 1b430ea7..23b6c26f 100644 --- a/services/gateway-service/internal/appconfig/mysql_test.go +++ b/services/gateway-service/internal/appconfig/mysql_test.go @@ -1,6 +1,44 @@ package appconfig -import "testing" +import ( + "context" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestListH5LinksReadsOnlyRequestedApp(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("new sql mock failed: %v", err) + } + defer db.Close() + + mock.ExpectQuery(regexp.QuoteMeta(listH5LinksSQL)). + WithArgs("yumi", h5LinkGroup). + WillReturnRows(sqlmock.NewRows([]string{"key", "description", "value", "updated_at_ms"}). + AddRow("admin", "Yumi Admin", "https://yumi.example.com/admin", int64(200)). + AddRow("public", "", "https://yumi.example.com/public", int64(300))) + + reader := &MySQLReader{db: db} + items, err := reader.ListH5Links(context.Background(), H5LinkQuery{AppCode: " YUMI "}) + if err != nil { + t.Fatalf("list scoped h5 links failed: %v", err) + } + if len(items) != 2 { + t.Fatalf("unexpected merged h5 links: %+v", items) + } + if items[0].Key != "admin" || items[0].Label != "Yumi Admin" || items[0].URL != "https://yumi.example.com/admin" { + t.Fatalf("scoped override did not replace baseline: %+v", items[0]) + } + if items[1].Key != "public" || items[1].Label != "public" || items[1].URL != "https://yumi.example.com/public" { + t.Fatalf("requested app config mismatch: %+v", items[1]) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("sql expectations mismatch: %v", err) + } +} func TestNormalizeBannerDisplayScopeAcceptsChineseLabels(t *testing.T) { cases := map[string]string{ diff --git a/services/gateway-service/internal/client/broadcast_client.go b/services/gateway-service/internal/client/broadcast_client.go index f652487e..4be21cc8 100644 --- a/services/gateway-service/internal/client/broadcast_client.go +++ b/services/gateway-service/internal/client/broadcast_client.go @@ -10,6 +10,7 @@ import ( // BroadcastClient abstracts gateway access to activity-service IM broadcast membership operations. type BroadcastClient interface { + PublishGlobalBroadcast(ctx context.Context, req *activityv1.PublishGlobalBroadcastRequest) (*activityv1.PublishBroadcastResponse, error) RemoveRegionBroadcastMember(ctx context.Context, req *activityv1.RemoveRegionBroadcastMemberRequest) (*activityv1.RemoveRegionBroadcastMemberResponse, error) } @@ -22,6 +23,10 @@ func NewGRPCBroadcastClient(conn *grpc.ClientConn) BroadcastClient { return &grpcBroadcastClient{client: activityv1.NewBroadcastServiceClient(conn)} } +func (c *grpcBroadcastClient) PublishGlobalBroadcast(ctx context.Context, req *activityv1.PublishGlobalBroadcastRequest) (*activityv1.PublishBroadcastResponse, error) { + return c.client.PublishGlobalBroadcast(ctx, req) +} + func (c *grpcBroadcastClient) RemoveRegionBroadcastMember(ctx context.Context, req *activityv1.RemoveRegionBroadcastMemberRequest) (*activityv1.RemoveRegionBroadcastMemberResponse, error) { return c.client.RemoveRegionBroadcastMember(ctx, req) } diff --git a/services/gateway-service/internal/client/room_client.go b/services/gateway-service/internal/client/room_client.go index e3cfa2ac..5842670a 100644 --- a/services/gateway-service/internal/client/room_client.go +++ b/services/gateway-service/internal/client/room_client.go @@ -40,6 +40,7 @@ type RoomClient interface { type RoomGuardClient interface { CheckSpeakPermission(ctx context.Context, req *roomv1.CheckSpeakPermissionRequest) (*roomv1.CheckSpeakPermissionResponse, error) VerifyRoomPresence(ctx context.Context, req *roomv1.VerifyRoomPresenceRequest) (*roomv1.VerifyRoomPresenceResponse, error) + ResolveRoomAppCode(ctx context.Context, req *roomv1.ResolveRoomAppCodeRequest) (*roomv1.ResolveRoomAppCodeResponse, error) } // RoomQueryClient 抽象 gateway 对 room-service 读模型查询的依赖。 @@ -195,6 +196,10 @@ func (c *grpcRoomGuardClient) VerifyRoomPresence(ctx context.Context, req *roomv return c.client.VerifyRoomPresence(ctx, req) } +func (c *grpcRoomGuardClient) ResolveRoomAppCode(ctx context.Context, req *roomv1.ResolveRoomAppCodeRequest) (*roomv1.ResolveRoomAppCodeResponse, error) { + return c.client.ResolveRoomAppCode(ctx, req) +} + func (c *grpcRoomQueryClient) ListRooms(ctx context.Context, req *roomv1.ListRoomsRequest) (*roomv1.ListRoomsResponse, error) { return c.client.ListRooms(ctx, req) } diff --git a/services/gateway-service/internal/client/user_client.go b/services/gateway-service/internal/client/user_client.go index 966fb0d6..8b470c9c 100644 --- a/services/gateway-service/internal/client/user_client.go +++ b/services/gateway-service/internal/client/user_client.go @@ -98,8 +98,9 @@ type UserCountryQueryClient interface { ListLoginRiskBlockedCountries(ctx context.Context, req *userv1.ListLoginRiskBlockedCountriesRequest) (*userv1.ListLoginRiskBlockedCountriesResponse, error) } -// UserRegionClient 抽象 gateway 对 user-service 区域国家归属查询的依赖。 +// UserRegionClient 抽象 gateway 对 user-service 区域目录和国家归属查询的依赖。 type UserRegionClient interface { + ListRegions(ctx context.Context, req *userv1.ListRegionsRequest) (*userv1.ListRegionsResponse, error) GetRegion(ctx context.Context, req *userv1.GetRegionRequest) (*userv1.RegionResponse, error) } @@ -129,6 +130,7 @@ type UserHostClient interface { GetAgency(ctx context.Context, req *userv1.GetAgencyRequest) (*userv1.GetAgencyResponse, error) UpdateAgencyProfile(ctx context.Context, req *userv1.UpdateAgencyProfileRequest) (*userv1.UpdateAgencyProfileResponse, error) CheckBusinessCapability(ctx context.Context, req *userv1.CheckBusinessCapabilityRequest) (*userv1.CheckBusinessCapabilityResponse, error) + GetRoleScopePolicy(ctx context.Context, req *userv1.GetRoleScopePolicyRequest) (*userv1.GetRoleScopePolicyResponse, error) GetAgencyMembers(ctx context.Context, req *userv1.GetAgencyMembersRequest) (*userv1.GetAgencyMembersResponse, error) GetAgencyApplications(ctx context.Context, req *userv1.GetAgencyApplicationsRequest) (*userv1.GetAgencyApplicationsResponse, error) ReviewAgencyApplication(ctx context.Context, req *userv1.ReviewAgencyApplicationRequest) (*userv1.ReviewAgencyApplicationResponse, error) @@ -509,6 +511,10 @@ func (c *grpcUserCountryQueryClient) ListLoginRiskBlockedCountries(ctx context.C return c.client.ListLoginRiskBlockedCountries(ctx, req) } +func (c *grpcUserRegionClient) ListRegions(ctx context.Context, req *userv1.ListRegionsRequest) (*userv1.ListRegionsResponse, error) { + return c.client.ListRegions(ctx, req) +} + func (c *grpcUserRegionClient) GetRegion(ctx context.Context, req *userv1.GetRegionRequest) (*userv1.RegionResponse, error) { return c.client.GetRegion(ctx, req) } @@ -609,6 +615,10 @@ func (c *grpcUserHostClient) CheckBusinessCapability(ctx context.Context, req *u return c.client.CheckBusinessCapability(ctx, req) } +func (c *grpcUserHostClient) GetRoleScopePolicy(ctx context.Context, req *userv1.GetRoleScopePolicyRequest) (*userv1.GetRoleScopePolicyResponse, error) { + return c.client.GetRoleScopePolicy(ctx, req) +} + func (c *grpcUserHostClient) GetAgencyMembers(ctx context.Context, req *userv1.GetAgencyMembersRequest) (*userv1.GetAgencyMembersResponse, error) { return c.client.GetAgencyMembers(ctx, req) } @@ -625,6 +635,14 @@ func (c *grpcUserHostClient) KickAgencyHost(ctx context.Context, req *userv1.Kic return c.client.KickAgencyHost(ctx, req) } +func (c *grpcUserHostClient) GetHostEngagementStats(ctx context.Context, req *userv1.GetHostEngagementStatsRequest) (*userv1.GetHostEngagementStatsResponse, error) { + return c.client.GetHostEngagementStats(ctx, req) +} + +func (c *grpcUserHostClient) RecordPrivateMessageEvent(ctx context.Context, req *userv1.RecordPrivateMessageEventRequest) (*userv1.RecordPrivateMessageEventResponse, error) { + return c.client.RecordPrivateMessageEvent(ctx, req) +} + func (c *grpcUserHostAdminClient) CreateBDLeader(ctx context.Context, req *userv1.CreateBDLeaderRequest) (*userv1.CreateBDLeaderResponse, error) { return c.client.CreateBDLeader(ctx, req) } diff --git a/services/gateway-service/internal/client/wallet_client.go b/services/gateway-service/internal/client/wallet_client.go index 78064b5c..f9dfd256 100644 --- a/services/gateway-service/internal/client/wallet_client.go +++ b/services/gateway-service/internal/client/wallet_client.go @@ -34,7 +34,16 @@ type WalletClient interface { ListWalletTransactions(ctx context.Context, req *walletv1.ListWalletTransactionsRequest) (*walletv1.ListWalletTransactionsResponse, error) ListVipPackages(ctx context.Context, req *walletv1.ListVipPackagesRequest) (*walletv1.ListVipPackagesResponse, error) GetMyVip(ctx context.Context, req *walletv1.GetMyVipRequest) (*walletv1.GetMyVipResponse, error) + GetVipProgramConfig(ctx context.Context, req *walletv1.GetVipProgramConfigRequest) (*walletv1.GetVipProgramConfigResponse, error) + CheckVipBenefit(ctx context.Context, req *walletv1.CheckVipBenefitRequest) (*walletv1.CheckVipBenefitResponse, error) + UpdateMyVipSettings(ctx context.Context, req *walletv1.UpdateMyVipSettingsRequest) (*walletv1.UpdateMyVipSettingsResponse, error) PurchaseVip(ctx context.Context, req *walletv1.PurchaseVipRequest) (*walletv1.PurchaseVipResponse, error) + GrantVipTrialCard(ctx context.Context, req *walletv1.GrantVipTrialCardRequest) (*walletv1.GrantVipTrialCardResponse, error) + EquipVipTrialCard(ctx context.Context, req *walletv1.EquipVipTrialCardRequest) (*walletv1.EquipVipTrialCardResponse, error) + UnequipVipTrialCard(ctx context.Context, req *walletv1.UnequipVipTrialCardRequest) (*walletv1.UnequipVipTrialCardResponse, error) + GetMyCurrentVipDailyCoinRebate(ctx context.Context, req *walletv1.GetMyCurrentVipDailyCoinRebateRequest) (*walletv1.GetMyCurrentVipDailyCoinRebateResponse, error) + ListMyVipDailyCoinRebateStatuses(ctx context.Context, req *walletv1.ListMyVipDailyCoinRebateStatusesRequest) (*walletv1.ListMyVipDailyCoinRebateStatusesResponse, error) + ClaimVipDailyCoinRebate(ctx context.Context, req *walletv1.ClaimVipDailyCoinRebateRequest) (*walletv1.ClaimVipDailyCoinRebateResponse, error) DebitCPBreakupFee(ctx context.Context, req *walletv1.DebitCPBreakupFeeRequest) (*walletv1.DebitCPBreakupFeeResponse, error) DebitWheelDraw(ctx context.Context, req *walletv1.DebitWheelDrawRequest) (*walletv1.DebitWheelDrawResponse, error) GrantVip(ctx context.Context, req *walletv1.GrantVipRequest) (*walletv1.GrantVipResponse, error) @@ -90,6 +99,18 @@ func (c *grpcWalletClient) GetHostSalaryProgress(ctx context.Context, req *walle return c.client.GetHostSalaryProgress(ctx, req) } +func (c *grpcWalletClient) GetHostRevenueStats(ctx context.Context, req *walletv1.GetHostRevenueStatsRequest) (*walletv1.GetHostRevenueStatsResponse, error) { + return c.client.GetHostRevenueStats(ctx, req) +} + +func (c *grpcWalletClient) GetAgencyPointShareStats(ctx context.Context, req *walletv1.GetAgencyPointShareStatsRequest) (*walletv1.GetAgencyPointShareStatsResponse, error) { + return c.client.GetAgencyPointShareStats(ctx, req) +} + +func (c *grpcWalletClient) GetAgencyHostGiftStats(ctx context.Context, req *walletv1.GetAgencyHostGiftStatsRequest) (*walletv1.GetAgencyHostGiftStatsResponse, error) { + return c.client.GetAgencyHostGiftStats(ctx, req) +} + func (c *grpcWalletClient) GetTeamHostSalaryStats(ctx context.Context, req *walletv1.GetTeamHostSalaryStatsRequest) (*walletv1.GetTeamHostSalaryStatsResponse, error) { return c.client.GetTeamHostSalaryStats(ctx, req) } @@ -170,10 +191,46 @@ func (c *grpcWalletClient) GetMyVip(ctx context.Context, req *walletv1.GetMyVipR return c.client.GetMyVip(ctx, req) } +func (c *grpcWalletClient) GetVipProgramConfig(ctx context.Context, req *walletv1.GetVipProgramConfigRequest) (*walletv1.GetVipProgramConfigResponse, error) { + return c.client.GetVipProgramConfig(ctx, req) +} + +func (c *grpcWalletClient) CheckVipBenefit(ctx context.Context, req *walletv1.CheckVipBenefitRequest) (*walletv1.CheckVipBenefitResponse, error) { + return c.client.CheckVipBenefit(ctx, req) +} + +func (c *grpcWalletClient) UpdateMyVipSettings(ctx context.Context, req *walletv1.UpdateMyVipSettingsRequest) (*walletv1.UpdateMyVipSettingsResponse, error) { + return c.client.UpdateMyVipSettings(ctx, req) +} + func (c *grpcWalletClient) PurchaseVip(ctx context.Context, req *walletv1.PurchaseVipRequest) (*walletv1.PurchaseVipResponse, error) { return c.client.PurchaseVip(ctx, req) } +func (c *grpcWalletClient) GrantVipTrialCard(ctx context.Context, req *walletv1.GrantVipTrialCardRequest) (*walletv1.GrantVipTrialCardResponse, error) { + return c.client.GrantVipTrialCard(ctx, req) +} + +func (c *grpcWalletClient) EquipVipTrialCard(ctx context.Context, req *walletv1.EquipVipTrialCardRequest) (*walletv1.EquipVipTrialCardResponse, error) { + return c.client.EquipVipTrialCard(ctx, req) +} + +func (c *grpcWalletClient) UnequipVipTrialCard(ctx context.Context, req *walletv1.UnequipVipTrialCardRequest) (*walletv1.UnequipVipTrialCardResponse, error) { + return c.client.UnequipVipTrialCard(ctx, req) +} + +func (c *grpcWalletClient) GetMyCurrentVipDailyCoinRebate(ctx context.Context, req *walletv1.GetMyCurrentVipDailyCoinRebateRequest) (*walletv1.GetMyCurrentVipDailyCoinRebateResponse, error) { + return c.client.GetMyCurrentVipDailyCoinRebate(ctx, req) +} + +func (c *grpcWalletClient) ListMyVipDailyCoinRebateStatuses(ctx context.Context, req *walletv1.ListMyVipDailyCoinRebateStatusesRequest) (*walletv1.ListMyVipDailyCoinRebateStatusesResponse, error) { + return c.client.ListMyVipDailyCoinRebateStatuses(ctx, req) +} + +func (c *grpcWalletClient) ClaimVipDailyCoinRebate(ctx context.Context, req *walletv1.ClaimVipDailyCoinRebateRequest) (*walletv1.ClaimVipDailyCoinRebateResponse, error) { + return c.client.ClaimVipDailyCoinRebate(ctx, req) +} + func (c *grpcWalletClient) DebitCPBreakupFee(ctx context.Context, req *walletv1.DebitCPBreakupFeeRequest) (*walletv1.DebitCPBreakupFeeResponse, error) { return c.client.DebitCPBreakupFee(ctx, req) } @@ -206,6 +263,18 @@ func (c *grpcWalletClient) TransferSalaryToCoinSeller(ctx context.Context, req * return c.client.TransferSalaryToCoinSeller(ctx, req) } +func (c *grpcWalletClient) ListPointWithdrawalCoinSellers(ctx context.Context, req *walletv1.ListPointWithdrawalCoinSellersRequest) (*walletv1.ListPointWithdrawalCoinSellersResponse, error) { + return c.client.ListPointWithdrawalCoinSellers(ctx, req) +} + +func (c *grpcWalletClient) TransferPointToCoinSeller(ctx context.Context, req *walletv1.TransferPointToCoinSellerRequest) (*walletv1.TransferPointToCoinSellerResponse, error) { + return c.client.TransferPointToCoinSeller(ctx, req) +} + +func (c *grpcWalletClient) GetPointWithdrawalConfig(ctx context.Context, req *walletv1.GetPointWithdrawalConfigRequest) (*walletv1.GetPointWithdrawalConfigResponse, error) { + return c.client.GetPointWithdrawalConfig(ctx, req) +} + func (c *grpcWalletClient) FreezeSalaryWithdrawal(ctx context.Context, req *walletv1.FreezeSalaryWithdrawalRequest) (*walletv1.FreezeSalaryWithdrawalResponse, error) { return c.client.FreezeSalaryWithdrawal(ctx, req) } diff --git a/services/gateway-service/internal/config/config.go b/services/gateway-service/internal/config/config.go index 5a6f49c7..a83b5c33 100644 --- a/services/gateway-service/internal/config/config.go +++ b/services/gateway-service/internal/config/config.go @@ -8,6 +8,7 @@ import ( "time" "hyapp/pkg/configx" + "hyapp/pkg/giftlimits" "hyapp/pkg/grpcclient" "hyapp/pkg/logx" "hyapp/pkg/tencentim" @@ -31,6 +32,7 @@ type Config struct { Statistics StatisticsConfig `yaml:"statistics"` GRPCClient GRPCClientConfig `yaml:"grpc_client"` AuthRateLimit AuthRateLimitConfig `yaml:"auth_rate_limit"` + GiftCapacityLimit GiftCapacityLimitConfig `yaml:"gift_capacity_limit"` LoginRisk LoginRiskConfig `yaml:"login_risk"` AppConfig AppConfigConfig `yaml:"app_config"` Leaderboard LeaderboardConfig `yaml:"leaderboard"` @@ -191,6 +193,42 @@ type AuthRateLimitConfig struct { SessionIDIPLimit int `yaml:"session_id_ip_limit"` } +// GiftCapacityLimitConfig 控制送礼入口按真实目标成本执行的 Redis 容量保护。 +type GiftCapacityLimitConfig struct { + // Enabled 为 false 时显式关闭保护;本地、Docker 和生产模板均默认开启。 + Enabled bool `yaml:"enabled"` + // RedisAddr/Password/DB 指向所有 gateway 节点共享的 Redis;地址为空时复用 auth_rate_limit。 + RedisAddr string `yaml:"redis_addr"` + RedisPassword string `yaml:"redis_password"` + RedisDB int `yaml:"redis_db"` + // KeyPrefix 隔离送礼令牌桶和在途租约,避免与登录频控共享计数空间。 + KeyPrefix string `yaml:"key_prefix"` + // User/Room/App 三层都按 gift_count*去重 target_count 计费;用户层保护单账号, + // 房间层收敛多人叠加,App 层兜底总抽奖/账务工作量。 + UserBucketCapacity int `yaml:"user_bucket_capacity"` + UserRefillTokensPerSecond float64 `yaml:"user_refill_tokens_per_second"` + RoomBucketCapacity int `yaml:"room_bucket_capacity"` + RoomRefillTokensPerSecond float64 `yaml:"room_refill_tokens_per_second"` + AppBucketCapacity int `yaml:"app_bucket_capacity"` + AppRefillTokensPerSecond float64 `yaml:"app_refill_tokens_per_second"` + // MaxTargetsPerRequest 是协议入口硬上限,超过后直接 4xx,不进入永远无法补足的大权重令牌重试。 + MaxTargetsPerRequest int `yaml:"max_targets_per_request"` + // MaxGiftCountPerRequest 限制单目标礼物数量;MaxGiftUnitsPerRequest 进一步限制 + // gift_count*去重目标数,避免 999*全麦把幸运礼物随机/落库成本放大到不可控。 + MaxGiftCountPerRequest int `yaml:"max_gift_count_per_request"` + MaxGiftUnitsPerRequest int `yaml:"max_gift_units_per_request"` + // MaxInFlight 是单个 app:user 在所有房间合计允许的并发送礼请求数。 + MaxInFlight int `yaml:"max_in_flight"` + // InFlightLease 是 gateway 异常退出后并发槽自动释放的上限。 + InFlightLease time.Duration `yaml:"in_flight_lease"` + // RedisOperationTimeout 是三层 Lua 的共同总预算,不是每层各自预算。 + RedisOperationTimeout time.Duration `yaml:"redis_operation_timeout"` + // RequestTimeout 覆盖 profile、host 和 room gRPC 的完整编排;lease 必须严格大于它并留安全余量。 + RequestTimeout time.Duration `yaml:"request_timeout"` +} + +const giftCapacityLeaseSafetyMargin = 2 * time.Second + // TencentIMConfig 描述 gateway 给客户端签发腾讯云 IM UserSig 的配置。 type TencentIMConfig struct { // SDKAppID 是腾讯云 IM 控制台分配的应用 ID。 @@ -303,12 +341,22 @@ func Default() Config { "X-Bundle-ID", "X-App-Platform", "X-Platform", + "X-App-Version", + "X-App-Build-Number", + "X-Device-Model", + "X-Device-Manufacturer", + "X-OS-Version", + "X-Device-IMEI", }, ExposeHeaders: []string{"X-Request-ID"}, AllowCredentials: true, MaxAgeSec: 600, }, - JWTSecret: "gateway-dev-secret", + // `make run` deliberately uses in-code defaults when MQ is disabled, so + // this must remain identical to user-service's local JWT signing secret. + // A divergent default makes every freshly refreshed token fail gateway + // verification even though login itself appears successful. + JWTSecret: "dev-shared-secret", RoomServiceAddr: "127.0.0.1:13001", UserServiceAddr: "127.0.0.1:13005", WalletServiceAddr: "127.0.0.1:13004", @@ -352,6 +400,24 @@ func Default() Config { DisplayUserIDLimit: 50, SessionIDIPLimit: 30, }, + GiftCapacityLimit: GiftCapacityLimitConfig{ + Enabled: true, + // RedisAddr 故意留空,由 Normalize 复用 auth_rate_limit Redis;旧 YAML 没有本 section 也能安全启动。 + KeyPrefix: "gateway:gift:capacity:", + UserBucketCapacity: 5_000, + UserRefillTokensPerSecond: 500, + RoomBucketCapacity: 20_000, + RoomRefillTokensPerSecond: 5_000, + AppBucketCapacity: 100_000, + AppRefillTokensPerSecond: 25_000, + MaxTargetsPerRequest: 30, + MaxGiftCountPerRequest: int(giftlimits.MaxGiftCount), + MaxGiftUnitsPerRequest: int(giftlimits.MaxTotalGiftUnits), + MaxInFlight: 2, + InFlightLease: 10 * time.Second, + RedisOperationTimeout: 500 * time.Millisecond, + RequestTimeout: 5 * time.Second, + }, LoginRisk: LoginRiskConfig{ Enabled: true, RedisAddr: "127.0.0.1:13379", @@ -427,7 +493,8 @@ func (cfg *Config) Normalize() error { cfg.CORS.AllowedMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"} } if len(cfg.CORS.AllowedHeaders) == 0 { - cfg.CORS.AllowedHeaders = []string{"Authorization", "Content-Type", "X-Request-ID", "X-App-Code", "X-HY-App-Code", "X-App-Package", "X-Package-Name", "X-App-Bundle-ID", "X-Bundle-ID", "X-App-Platform", "X-Platform"} + // 设备资料头需要进入 CORS 允许集,H5 客户端才能与原生保持同一套设备上报协议。 + cfg.CORS.AllowedHeaders = []string{"Authorization", "Content-Type", "X-Request-ID", "X-App-Code", "X-HY-App-Code", "X-App-Package", "X-Package-Name", "X-App-Bundle-ID", "X-Bundle-ID", "X-App-Platform", "X-Platform", "X-App-Version", "X-App-Build-Number", "X-Device-Model", "X-Device-Manufacturer", "X-OS-Version", "X-Device-IMEI"} } if len(cfg.CORS.ExposeHeaders) == 0 { cfg.CORS.ExposeHeaders = []string{"X-Request-ID"} @@ -519,6 +586,67 @@ func (cfg *Config) Normalize() error { if cfg.LoginRisk.KeyPrefix == "" { cfg.LoginRisk.KeyPrefix = "login_risk:ip:" } + cfg.GiftCapacityLimit.RedisAddr = strings.TrimSpace(cfg.GiftCapacityLimit.RedisAddr) + cfg.GiftCapacityLimit.KeyPrefix = strings.TrimSpace(cfg.GiftCapacityLimit.KeyPrefix) + if cfg.GiftCapacityLimit.Enabled && cfg.GiftCapacityLimit.RedisAddr == "" { + // 送礼保护与 auth 频控具有同样的强一致入口语义,默认复用已部署的共享 Redis, + // 但使用独立 key_prefix,避免引入新的基础设施依赖。 + cfg.GiftCapacityLimit.RedisAddr = strings.TrimSpace(cfg.AuthRateLimit.RedisAddr) + cfg.GiftCapacityLimit.RedisPassword = cfg.AuthRateLimit.RedisPassword + cfg.GiftCapacityLimit.RedisDB = cfg.AuthRateLimit.RedisDB + } + if cfg.GiftCapacityLimit.KeyPrefix == "" { + cfg.GiftCapacityLimit.KeyPrefix = "gateway:gift:capacity:" + } + if cfg.GiftCapacityLimit.UserBucketCapacity <= 0 { + cfg.GiftCapacityLimit.UserBucketCapacity = 5_000 + } + if cfg.GiftCapacityLimit.UserRefillTokensPerSecond <= 0 { + cfg.GiftCapacityLimit.UserRefillTokensPerSecond = 500 + } + if cfg.GiftCapacityLimit.RoomBucketCapacity <= 0 { + cfg.GiftCapacityLimit.RoomBucketCapacity = 20_000 + } + if cfg.GiftCapacityLimit.RoomRefillTokensPerSecond <= 0 { + cfg.GiftCapacityLimit.RoomRefillTokensPerSecond = 5_000 + } + if cfg.GiftCapacityLimit.AppBucketCapacity <= 0 { + cfg.GiftCapacityLimit.AppBucketCapacity = 100_000 + } + if cfg.GiftCapacityLimit.AppRefillTokensPerSecond <= 0 { + cfg.GiftCapacityLimit.AppRefillTokensPerSecond = 25_000 + } + if cfg.GiftCapacityLimit.MaxTargetsPerRequest <= 0 { + cfg.GiftCapacityLimit.MaxTargetsPerRequest = 30 + } + if cfg.GiftCapacityLimit.MaxGiftCountPerRequest <= 0 { + cfg.GiftCapacityLimit.MaxGiftCountPerRequest = int(giftlimits.MaxGiftCount) + } + if cfg.GiftCapacityLimit.MaxGiftUnitsPerRequest <= 0 { + cfg.GiftCapacityLimit.MaxGiftUnitsPerRequest = int(giftlimits.MaxTotalGiftUnits) + } + if int64(cfg.GiftCapacityLimit.MaxGiftCountPerRequest) > giftlimits.MaxGiftCount || int64(cfg.GiftCapacityLimit.MaxGiftUnitsPerRequest) > giftlimits.MaxTotalGiftUnits { + return fmt.Errorf("gift_capacity_limit request limits exceed cross-service hard ceiling") + } + if cfg.GiftCapacityLimit.MaxInFlight <= 0 { + cfg.GiftCapacityLimit.MaxInFlight = 2 + } + if cfg.GiftCapacityLimit.InFlightLease <= 0 { + cfg.GiftCapacityLimit.InFlightLease = 10 * time.Second + } + if cfg.GiftCapacityLimit.RedisOperationTimeout <= 0 { + cfg.GiftCapacityLimit.RedisOperationTimeout = 500 * time.Millisecond + } + if cfg.GiftCapacityLimit.RequestTimeout <= 0 { + cfg.GiftCapacityLimit.RequestTimeout = 5 * time.Second + } + if cfg.GiftCapacityLimit.UserBucketCapacity < cfg.GiftCapacityLimit.MaxGiftUnitsPerRequest || cfg.GiftCapacityLimit.RoomBucketCapacity < cfg.GiftCapacityLimit.MaxGiftUnitsPerRequest || cfg.GiftCapacityLimit.AppBucketCapacity < cfg.GiftCapacityLimit.MaxGiftUnitsPerRequest { + return fmt.Errorf("gift_capacity_limit bucket capacities must be >= max_gift_units_per_request") + } + if cfg.GiftCapacityLimit.InFlightLease <= cfg.GiftCapacityLimit.RequestTimeout+giftCapacityLeaseSafetyMargin { + // lease 从用户层获准时开始计时;严格大于 RPC 总预算和 2s 收尾余量,确保正常请求期间槽位不会提前复用。 + return fmt.Errorf("gift_capacity_limit.in_flight_lease must exceed request_timeout by more than %s", giftCapacityLeaseSafetyMargin) + } cfg.TencentCOS.SecretID = strings.TrimSpace(cfg.TencentCOS.SecretID) cfg.TencentCOS.SecretKey = strings.TrimSpace(cfg.TencentCOS.SecretKey) cfg.TencentCOS.BucketName = strings.TrimSpace(cfg.TencentCOS.BucketName) diff --git a/services/gateway-service/internal/config/config_test.go b/services/gateway-service/internal/config/config_test.go index d034e7cc..ec193820 100644 --- a/services/gateway-service/internal/config/config_test.go +++ b/services/gateway-service/internal/config/config_test.go @@ -22,7 +22,7 @@ func TestLoad(t *testing.T) { if cfg.RoomServiceAddr != "127.0.0.1:13001" { t.Fatalf("unexpected room addr: %s", cfg.RoomServiceAddr) } - if !cfg.CORS.Enabled || !containsString(cfg.CORS.AllowedOrigins, "*") || !containsString(cfg.CORS.AllowedHeaders, "X-App-Code") { + if !cfg.CORS.Enabled || !containsString(cfg.CORS.AllowedOrigins, "*") || !containsString(cfg.CORS.AllowedHeaders, "X-App-Code") || !containsString(cfg.CORS.AllowedHeaders, "X-App-Version") || !containsString(cfg.CORS.AllowedHeaders, "X-App-Build-Number") { t.Fatalf("unexpected cors config: %+v", cfg.CORS) } if cfg.GRPCClient.DefaultTimeout != 5*time.Second || cfg.GRPCClient.RetryMaxAttempts != 2 || len(cfg.GRPCClient.RetryableStatusCodes) != 1 || cfg.GRPCClient.RetryableStatusCodes[0] != "UNAVAILABLE" { @@ -40,6 +40,15 @@ func TestLoad(t *testing.T) { if cfg.AuthRateLimit.KeyPrefix != "gateway:auth:rate:" { t.Fatalf("unexpected auth rate key prefix: %q", cfg.AuthRateLimit.KeyPrefix) } + if !cfg.GiftCapacityLimit.Enabled || cfg.GiftCapacityLimit.RedisAddr != "127.0.0.1:13379" || cfg.GiftCapacityLimit.KeyPrefix != "gateway:gift:capacity:" { + t.Fatalf("local gift capacity config mismatch: %+v", cfg.GiftCapacityLimit) + } + if cfg.GiftCapacityLimit.UserBucketCapacity != 5_000 || cfg.GiftCapacityLimit.UserRefillTokensPerSecond != 500 || cfg.GiftCapacityLimit.RoomBucketCapacity != 20_000 || cfg.GiftCapacityLimit.RoomRefillTokensPerSecond != 5_000 || cfg.GiftCapacityLimit.AppBucketCapacity != 100_000 || cfg.GiftCapacityLimit.AppRefillTokensPerSecond != 25_000 { + t.Fatalf("unexpected local gift capacity buckets: %+v", cfg.GiftCapacityLimit) + } + if cfg.GiftCapacityLimit.MaxTargetsPerRequest != 30 || cfg.GiftCapacityLimit.MaxGiftCountPerRequest != 999 || cfg.GiftCapacityLimit.MaxGiftUnitsPerRequest != 5_000 || cfg.GiftCapacityLimit.MaxInFlight != 2 || cfg.GiftCapacityLimit.InFlightLease != 10*time.Second || cfg.GiftCapacityLimit.RedisOperationTimeout != 500*time.Millisecond || cfg.GiftCapacityLimit.RequestTimeout != 5*time.Second { + t.Fatalf("unexpected local gift capacity policy: %+v", cfg.GiftCapacityLimit) + } if !cfg.TencentRTC.Enabled || cfg.TencentRTC.SDKAppID == 0 || cfg.TencentRTC.SecretKey == "" { t.Fatalf("local tencent rtc config must be explicit: %+v", cfg.TencentRTC) } @@ -73,7 +82,7 @@ func TestLoadTencentExample(t *testing.T) { if cfg.TencentIM.CallbackURL == "" { t.Fatalf("callback_url template must be present") } - if !cfg.CORS.Enabled || !containsString(cfg.CORS.AllowedOrigins, "https://h5.global-interaction.com") || cfg.CORS.MaxAgeSec != 600 { + if !cfg.CORS.Enabled || !containsString(cfg.CORS.AllowedOrigins, "https://h5.global-interaction.com") || !containsString(cfg.CORS.AllowedHeaders, "X-App-Version") || !containsString(cfg.CORS.AllowedHeaders, "X-App-Build-Number") || cfg.CORS.MaxAgeSec != 600 { t.Fatalf("tencent cors template must be explicit: %+v", cfg.CORS) } if cfg.AppConfig.MySQLDSN == "" || !strings.Contains(cfg.AppConfig.MySQLDSN, "hyapp_admin") { @@ -88,6 +97,9 @@ func TestLoadTencentExample(t *testing.T) { if !cfg.AuthRateLimit.Enabled || cfg.AuthRateLimit.RedisAddr == "" || cfg.AuthRateLimit.RedisPassword == "" { t.Fatalf("tencent auth rate redis template must be explicit: %+v", cfg.AuthRateLimit) } + if !cfg.GiftCapacityLimit.Enabled || cfg.GiftCapacityLimit.RedisAddr == "" || cfg.GiftCapacityLimit.RedisPassword == "" || cfg.GiftCapacityLimit.InFlightLease != 10*time.Second || cfg.GiftCapacityLimit.RequestTimeout != 5*time.Second || cfg.GiftCapacityLimit.MaxTargetsPerRequest != 30 { + t.Fatalf("tencent gift capacity template must be explicit: %+v", cfg.GiftCapacityLimit) + } if !cfg.TencentRTC.Enabled || cfg.TencentRTC.SDKAppID == 0 || cfg.TencentRTC.SecretKey == "" { t.Fatalf("tencent rtc template must be explicit: %+v", cfg.TencentRTC) } @@ -102,6 +114,62 @@ func TestLoadTencentExample(t *testing.T) { } } +func TestLoadDockerGiftCapacityLimit(t *testing.T) { + cfg, err := Load("../../configs/config.docker.yaml") + if err != nil { + t.Fatalf("Load Docker config failed: %v", err) + } + if !cfg.GiftCapacityLimit.Enabled || cfg.GiftCapacityLimit.RedisAddr != "redis:6379" || cfg.GiftCapacityLimit.UserBucketCapacity != 5_000 || cfg.GiftCapacityLimit.RoomBucketCapacity != 20_000 || cfg.GiftCapacityLimit.AppBucketCapacity != 100_000 || cfg.GiftCapacityLimit.MaxInFlight != 2 { + t.Fatalf("Docker gift capacity config mismatch: %+v", cfg.GiftCapacityLimit) + } + if !containsString(cfg.CORS.AllowedHeaders, "X-App-Version") || !containsString(cfg.CORS.AllowedHeaders, "X-App-Build-Number") { + t.Fatalf("Docker CORS must allow login client version headers: %+v", cfg.CORS) + } +} + +func TestLoadLegacyYAMLWithoutGiftSectionReusesAuthRedis(t *testing.T) { + if got := Default().GiftCapacityLimit.RedisAddr; got != "" { + t.Fatalf("Default gift RedisAddr must stay empty for auth Redis inheritance, got %q", got) + } + path := filepath.Join(t.TempDir(), "config.yaml") + if err := os.WriteFile(path, []byte(`auth_rate_limit: + enabled: true + redis_addr: "legacy-redis.internal:6379" + redis_password: "legacy-secret" + redis_db: 4 +`), 0o600); err != nil { + t.Fatalf("write legacy config failed: %v", err) + } + cfg, err := Load(path) + if err != nil { + t.Fatalf("legacy YAML without gift_capacity_limit must still load: %v", err) + } + if !cfg.GiftCapacityLimit.Enabled || cfg.GiftCapacityLimit.RedisAddr != "legacy-redis.internal:6379" || cfg.GiftCapacityLimit.RedisPassword != "legacy-secret" || cfg.GiftCapacityLimit.RedisDB != 4 { + t.Fatalf("gift capacity must reuse auth Redis for old YAML: %+v", cfg.GiftCapacityLimit) + } + if cfg.GiftCapacityLimit.MaxTargetsPerRequest != 30 || cfg.GiftCapacityLimit.MaxGiftCountPerRequest != 999 || cfg.GiftCapacityLimit.MaxGiftUnitsPerRequest != 5_000 || cfg.GiftCapacityLimit.UserRefillTokensPerSecond != 500 || cfg.GiftCapacityLimit.RoomRefillTokensPerSecond != 5_000 || cfg.GiftCapacityLimit.AppRefillTokensPerSecond != 25_000 { + t.Fatalf("old YAML must receive safe capacity defaults: %+v", cfg.GiftCapacityLimit) + } +} + +func TestNormalizeRejectsGiftLeaseWithoutRequestSafetyMargin(t *testing.T) { + cfg := Default() + cfg.GiftCapacityLimit.RequestTimeout = 8 * time.Second + cfg.GiftCapacityLimit.InFlightLease = 10 * time.Second + if err := cfg.Normalize(); err == nil || !strings.Contains(err.Error(), "in_flight_lease") { + t.Fatalf("lease equal to request timeout plus safety margin must be rejected: %v", err) + } +} + +func TestNormalizeRejectsGiftBucketSmallerThanRequestMaximum(t *testing.T) { + cfg := Default() + cfg.GiftCapacityLimit.MaxGiftUnitsPerRequest = 5_000 + cfg.GiftCapacityLimit.UserBucketCapacity = 4_999 + if err := cfg.Normalize(); err == nil || !strings.Contains(err.Error(), "bucket capacities") { + t.Fatalf("bucket below max request weight must be rejected: %v", err) + } +} + func containsString(values []string, want string) bool { for _, value := range values { if value == want { diff --git a/services/gateway-service/internal/financewithdrawal/writer.go b/services/gateway-service/internal/financewithdrawal/writer.go index a4ff6dd5..be439750 100644 --- a/services/gateway-service/internal/financewithdrawal/writer.go +++ b/services/gateway-service/internal/financewithdrawal/writer.go @@ -25,6 +25,11 @@ type CreateApplicationCommand struct { SalaryAssetType string WithdrawAmount string WithdrawAmountMinor int64 + PointFeeAmount int64 + PointNetAmount int64 + PointsPerUSD int64 + PointFeeBPS int32 + PointPolicyInstance string WithdrawMethod string WithdrawAddress string FreezeCommandID string @@ -40,6 +45,11 @@ type Application struct { SalaryAssetType string WithdrawAmount string WithdrawAmountMinor int64 + PointFeeAmount int64 + PointNetAmount int64 + PointsPerUSD int64 + PointFeeBPS int32 + PointPolicyInstance string WithdrawMethod string WithdrawAddress string FreezeCommandID string @@ -106,14 +116,21 @@ func (w *MySQLWriter) CreateApplication(ctx context.Context, command CreateAppli result, err := w.db.ExecContext(ctx, ` INSERT INTO admin_user_withdrawal_applications - (app_code, user_id, salary_asset_type, withdraw_amount, withdraw_amount_minor, withdraw_method, withdraw_address, freeze_command_id, freeze_transaction_id, status, created_at_ms, updated_at_ms) + (app_code, user_id, salary_asset_type, withdraw_amount, withdraw_amount_minor, + point_fee_amount, point_net_amount, points_per_usd, point_fee_bps, point_policy_instance_code, + withdraw_method, withdraw_address, freeze_command_id, freeze_transaction_id, status, created_at_ms, updated_at_ms) VALUES - (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, command.AppCode, formatUserID(command.UserID), command.SalaryAssetType, command.WithdrawAmount, command.WithdrawAmountMinor, + command.PointFeeAmount, + command.PointNetAmount, + command.PointsPerUSD, + command.PointFeeBPS, + command.PointPolicyInstance, command.WithdrawMethod, command.WithdrawAddress, command.FreezeCommandID, @@ -144,6 +161,11 @@ VALUES SalaryAssetType: command.SalaryAssetType, WithdrawAmount: command.WithdrawAmount, WithdrawAmountMinor: command.WithdrawAmountMinor, + PointFeeAmount: command.PointFeeAmount, + PointNetAmount: command.PointNetAmount, + PointsPerUSD: command.PointsPerUSD, + PointFeeBPS: command.PointFeeBPS, + PointPolicyInstance: command.PointPolicyInstance, WithdrawMethod: command.WithdrawMethod, WithdrawAddress: command.WithdrawAddress, FreezeCommandID: command.FreezeCommandID, @@ -157,6 +179,7 @@ VALUES func (w *MySQLWriter) getApplicationByFreezeCommand(ctx context.Context, appCode string, freezeCommandID string) (Application, bool, error) { row := w.db.QueryRowContext(ctx, ` SELECT id, app_code, user_id, salary_asset_type, withdraw_amount, withdraw_amount_minor, + point_fee_amount, point_net_amount, points_per_usd, point_fee_bps, point_policy_instance_code, withdraw_method, withdraw_address, freeze_command_id, freeze_transaction_id, status, created_at_ms, updated_at_ms FROM admin_user_withdrawal_applications @@ -171,6 +194,11 @@ LIMIT 1`, appCode, freezeCommandID) &item.SalaryAssetType, &item.WithdrawAmount, &item.WithdrawAmountMinor, + &item.PointFeeAmount, + &item.PointNetAmount, + &item.PointsPerUSD, + &item.PointFeeBPS, + &item.PointPolicyInstance, &item.WithdrawMethod, &item.WithdrawAddress, &item.FreezeCommandID, @@ -205,6 +233,7 @@ func normalizeCreateApplicationCommand(command CreateApplicationCommand) CreateA command.WithdrawAddress = strings.TrimSpace(command.WithdrawAddress) command.FreezeCommandID = strings.TrimSpace(command.FreezeCommandID) command.FreezeTransactionID = strings.TrimSpace(command.FreezeTransactionID) + command.PointPolicyInstance = strings.TrimSpace(command.PointPolicyInstance) if command.WithdrawMethod == "" { command.WithdrawMethod = MethodUSDTTRC20 } @@ -234,6 +263,12 @@ func validateCreateApplicationCommand(command CreateApplicationCommand) error { case command.CreatedAtMS <= 0: return errors.New("created_at_ms is required") default: + if command.SalaryAssetType == "POINT" || command.SalaryAssetType == "COIN_SELLER_POINT" { + // POINT 的政策快照会被财务审核原样复用;写入前先验证内部一致性,避免冻结成功但审核无法结算。 + if command.PointsPerUSD <= 0 || command.PointFeeBPS < 0 || command.PointFeeBPS > 10000 || command.PointFeeAmount < 0 || command.PointNetAmount < 0 || command.PointFeeAmount+command.PointNetAmount != command.WithdrawAmountMinor || command.PointFeeAmount != command.WithdrawAmountMinor*int64(command.PointFeeBPS)/10000 { + return errors.New("point withdrawal policy snapshot is invalid") + } + } return nil } } diff --git a/services/gateway-service/internal/financewithdrawal/writer_test.go b/services/gateway-service/internal/financewithdrawal/writer_test.go index 84a7c103..eaf05ce7 100644 --- a/services/gateway-service/internal/financewithdrawal/writer_test.go +++ b/services/gateway-service/internal/financewithdrawal/writer_test.go @@ -39,6 +39,11 @@ func TestCreateApplicationReturnsExistingByAppAndFreezeCommand(t *testing.T) { "POINT", "9.50", int64(1000000), + int64(50000), + int64(950000), + int64(100000), + int32(500), + "huwaa-policy-v1", MethodUSDTTRC20, "TQY9pFbZHuR4fUN9WgXPYj5nmB6nQiLQfF", "cmd-point-freeze", @@ -54,6 +59,11 @@ func TestCreateApplicationReturnsExistingByAppAndFreezeCommand(t *testing.T) { SalaryAssetType: "POINT", WithdrawAmount: "9.50", WithdrawAmountMinor: 1_000_000, + PointFeeAmount: 50_000, + PointNetAmount: 950_000, + PointsPerUSD: 100_000, + PointFeeBPS: 500, + PointPolicyInstance: "huwaa-policy-v1", WithdrawMethod: MethodUSDTTRC20, WithdrawAddress: "TQY9pFbZHuR4fUN9WgXPYj5nmB6nQiLQfF", FreezeCommandID: "cmd-point-freeze", @@ -86,6 +96,11 @@ func TestCreateApplicationInsertsAfterAppScopedMiss(t *testing.T) { "POINT", "9.50", int64(1000000), + int64(50000), + int64(950000), + int64(100000), + int32(500), + "huwaa-policy-v1", MethodUSDTTRC20, "TQY9pFbZHuR4fUN9WgXPYj5nmB6nQiLQfF", "cmd-point-freeze", @@ -102,6 +117,11 @@ func TestCreateApplicationInsertsAfterAppScopedMiss(t *testing.T) { SalaryAssetType: "point", WithdrawAmount: "9.50", WithdrawAmountMinor: 1_000_000, + PointFeeAmount: 50_000, + PointNetAmount: 950_000, + PointsPerUSD: 100_000, + PointFeeBPS: 500, + PointPolicyInstance: "huwaa-policy-v1", WithdrawAddress: "TQY9pFbZHuR4fUN9WgXPYj5nmB6nQiLQfF", FreezeCommandID: "cmd-point-freeze", FreezeTransactionID: "tx-freeze", @@ -126,6 +146,11 @@ func withdrawalApplicationRows() *sqlmock.Rows { "salary_asset_type", "withdraw_amount", "withdraw_amount_minor", + "point_fee_amount", + "point_net_amount", + "points_per_usd", + "point_fee_bps", + "point_policy_instance_code", "withdraw_method", "withdraw_address", "freeze_command_id", diff --git a/services/gateway-service/internal/transport/http/appapi/app_event_handler.go b/services/gateway-service/internal/transport/http/appapi/app_event_handler.go index 3c7ba23b..b4e9b65a 100644 --- a/services/gateway-service/internal/transport/http/appapi/app_event_handler.go +++ b/services/gateway-service/internal/transport/http/appapi/app_event_handler.go @@ -1,7 +1,6 @@ package appapi import ( - "bytes" "encoding/json" "net/http" "strings" @@ -9,16 +8,12 @@ import ( userv1 "hyapp.local/api/proto/user/v1" "hyapp/pkg/appcode" + "hyapp/pkg/apptracking" "hyapp/services/gateway-service/internal/auth" "hyapp/services/gateway-service/internal/client" "hyapp/services/gateway-service/internal/transport/http/httpkit" ) -const ( - appEventMaxBatchSize = 50 - appEventMaxPropertiesBytes = 4096 -) - type appEventsRequest struct { Events []appEventRequest `json:"events"` } @@ -44,26 +39,17 @@ type appEventRequest struct { } func (h *Handler) reportAppEvents(writer http.ResponseWriter, request *http.Request) { - if h.statisticsClient == nil { - // App 埋点事实由 statistics-service 持久化;gateway 未注入写入边界时不能假成功,否则客户端会丢掉重试机会。 - httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") - return - } var body appEventsRequest if !httpkit.Decode(writer, request, &body) { return } - if len(body.Events) == 0 || len(body.Events) > appEventMaxBatchSize { + if !apptracking.ValidBatchSize(len(body.Events)) { httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } nowMS := time.Now().UTC().UnixMilli() userID := auth.UserIDFromContext(request.Context()) - countryID, regionID, ok := h.appEventUserDimensions(writer, request, userID) - if !ok { - return - } headerDeviceID := httpkit.FirstHeader(request, "X-Device-ID", "X-HY-Device-ID") headerPlatform := httpkit.FirstHeader(request, "X-App-Platform", "X-Platform") headerAppVersion := httpkit.FirstHeader(request, "X-App-Version", "X-Client-Version") @@ -75,8 +61,6 @@ func (h *Handler) reportAppEvents(writer http.ResponseWriter, request *http.Requ event, valid := appTrackingEventFromRequest(item, appTrackingEventDefaults{ appCode: appcode.FromContext(request.Context()), userID: userID, - countryID: countryID, - regionID: regionID, deviceID: headerDeviceID, sessionID: auth.SessionIDFromContext(request.Context()), platform: headerPlatform, @@ -91,6 +75,21 @@ func (h *Handler) reportAppEvents(writer http.ResponseWriter, request *http.Requ } events = append(events, event) } + if h.statisticsClient == nil { + // App 埋点事实由 statistics-service 持久化;gateway 未注入写入边界时不能假成功,否则客户端会丢掉重试机会。 + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + // 先完成客户端可控字段的全部长度校验,再读取 user-service。这样超限 + // 请求稳定返回 400,不会制造无意义的下游调用,更不会被包装成 502。 + countryID, regionID, ok := h.appEventUserDimensions(writer, request, userID) + if !ok { + return + } + for index := range events { + events[index].CountryID = countryID + events[index].RegionID = regionID + } result, err := h.statisticsClient.ReportAppTrackingEvents(request.Context(), events) if err != nil { @@ -128,8 +127,6 @@ func (h *Handler) appEventUserDimensions(writer http.ResponseWriter, request *ht type appTrackingEventDefaults struct { appCode string userID int64 - countryID int64 - regionID int64 deviceID string sessionID string platform string @@ -140,16 +137,7 @@ type appTrackingEventDefaults struct { } func appTrackingEventFromRequest(item appEventRequest, defaults appTrackingEventDefaults) (client.AppTrackingEvent, bool) { - item.EventID = strings.TrimSpace(item.EventID) - item.EventName = strings.TrimSpace(item.EventName) - if item.EventID == "" || item.EventName == "" { - return client.AppTrackingEvent{}, false - } deviceID := firstTrimmed(item.DeviceID, defaults.deviceID) - if defaults.userID <= 0 && deviceID == "" { - // 匿名埋点没有服务端 user_id,只能依赖稳定 device_id 做排障和后续粗粒度去重。 - return client.AppTrackingEvent{}, false - } properties, ok := normalizeAppEventProperties(item.Properties) if !ok { return client.AppTrackingEvent{}, false @@ -158,14 +146,14 @@ func appTrackingEventFromRequest(item appEventRequest, defaults appTrackingEvent if occurredAtMS <= 0 { occurredAtMS = defaults.receivedAtMS } - return client.AppTrackingEvent{ + event := client.AppTrackingEvent{ AppCode: defaults.appCode, EventID: item.EventID, EventName: item.EventName, - EventType: strings.TrimSpace(item.EventType), - Screen: strings.TrimSpace(item.Screen), - TargetType: strings.TrimSpace(item.TargetType), - TargetID: strings.TrimSpace(item.TargetID), + EventType: item.EventType, + Screen: item.Screen, + TargetType: item.TargetType, + TargetID: item.TargetID, UserID: defaults.userID, DeviceID: deviceID, SessionID: firstTrimmed(defaults.sessionID, item.SessionID), @@ -173,25 +161,60 @@ func appTrackingEventFromRequest(item appEventRequest, defaults appTrackingEvent AppVersion: firstTrimmed(item.AppVersion, defaults.appVersion), Language: firstTrimmed(item.Language, defaults.language), Timezone: firstTrimmed(item.Timezone, defaults.timezone), - CountryID: defaults.countryID, - RegionID: defaults.regionID, DurationMS: item.DurationMS, Success: item.Success, - ErrorCode: strings.TrimSpace(item.ErrorCode), + ErrorCode: item.ErrorCode, Properties: properties, OccurredAtMS: occurredAtMS, - }, true + } + if !normalizeAppTrackingEventText(&event) || event.EventID == "" || event.EventName == "" { + return client.AppTrackingEvent{}, false + } + if defaults.userID <= 0 && event.DeviceID == "" { + // 匿名埋点没有服务端 user_id,只能依赖稳定 device_id 做排障和后续粗粒度去重。 + return client.AppTrackingEvent{}, false + } + return event, true } func normalizeAppEventProperties(raw json.RawMessage) (json.RawMessage, bool) { - trimmed := bytes.TrimSpace(raw) - if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) { - return nil, true + return apptracking.NormalizeProperties(raw) +} + +func normalizeAppTrackingEventText(event *client.AppTrackingEvent) bool { + if event == nil { + return false } - if len(trimmed) > appEventMaxPropertiesBytes || !json.Valid(trimmed) { - return nil, false + // Every field uses the same byte limit as statistics persistence. Returning + // false is deliberate: identifiers and diagnostic error codes must be + // accepted intact or rejected, never silently truncated into another fact. + fields := []struct { + value *string + maxBytes int + }{ + {value: &event.AppCode, maxBytes: apptracking.MaxAppCodeBytes}, + {value: &event.EventID, maxBytes: apptracking.MaxEventIDBytes}, + {value: &event.EventName, maxBytes: apptracking.MaxEventNameBytes}, + {value: &event.EventType, maxBytes: apptracking.MaxEventTypeBytes}, + {value: &event.Screen, maxBytes: apptracking.MaxScreenBytes}, + {value: &event.TargetType, maxBytes: apptracking.MaxTargetTypeBytes}, + {value: &event.TargetID, maxBytes: apptracking.MaxTargetIDBytes}, + {value: &event.DeviceID, maxBytes: apptracking.MaxDeviceIDBytes}, + {value: &event.SessionID, maxBytes: apptracking.MaxSessionIDBytes}, + {value: &event.Platform, maxBytes: apptracking.MaxPlatformBytes}, + {value: &event.AppVersion, maxBytes: apptracking.MaxAppVersionBytes}, + {value: &event.Language, maxBytes: apptracking.MaxLanguageBytes}, + {value: &event.Timezone, maxBytes: apptracking.MaxTimezoneBytes}, + {value: &event.ErrorCode, maxBytes: apptracking.MaxErrorCodeBytes}, } - return json.RawMessage(trimmed), true + for _, field := range fields { + normalized, ok := apptracking.NormalizeText(*field.value, field.maxBytes) + if !ok { + return false + } + *field.value = normalized + } + return true } func appEventRequestLanguage(request *http.Request) string { diff --git a/services/gateway-service/internal/transport/http/auth_handler.go b/services/gateway-service/internal/transport/http/auth_handler.go index a87fc44f..c059c656 100644 --- a/services/gateway-service/internal/transport/http/auth_handler.go +++ b/services/gateway-service/internal/transport/http/auth_handler.go @@ -46,12 +46,14 @@ type quickCreateAccountData struct { // loginPassword 处理账号密码登录;账号就是当前有效短号。 func (h *Handler) loginPassword(writer http.ResponseWriter, request *http.Request) { var body struct { - Account string `json:"account"` - Password string `json:"password"` - DeviceID string `json:"device_id"` - Platform string `json:"platform"` - Language string `json:"language"` - Timezone string `json:"timezone"` + Account string `json:"account"` + Password string `json:"password"` + DeviceID string `json:"device_id"` + AppVersion string `json:"app_version"` + BuildNumber string `json:"build_number"` + Platform string `json:"platform"` + Language string `json:"language"` + Timezone string `json:"timezone"` } if !decode(writer, request, &body) { return @@ -77,8 +79,9 @@ func (h *Handler) loginPassword(writer http.ResponseWriter, request *http.Reques return } + meta := authRequestMetaWithLoginContext(request, body.DeviceID, body.Platform, body.Language, body.Timezone, body.AppVersion, body.BuildNumber) resp, err := h.userClient.LoginPassword(request.Context(), &userv1.LoginPasswordRequest{ - Meta: authRequestMetaWithLoginContext(request, body.DeviceID, body.Platform, body.Language, body.Timezone), + Meta: meta, DisplayUserId: account, Password: body.Password, }) @@ -173,8 +176,11 @@ func (h *Handler) quickCreateAccount(writer http.ResponseWriter, request *http.R return } + meta := authRequestMetaWithLoginContext(request, body.DeviceID, body.Platform, body.Language, body.Timezone, body.AppVersion, body.BuildNumber) + // 标准客户端不再发 body device/os_version;缺失时回退设备头,让 users.register_device/os_version 不再为空。 + body.Device, body.OSVersion = deviceFieldsWithHeaderFallback(body.Device, body.OSVersion, meta) resp, err := h.userClient.QuickCreateAccount(request.Context(), &userv1.QuickCreateAccountRequest{ - Meta: authRequestMetaWithLoginContext(request, body.DeviceID, body.Platform, body.Language, body.Timezone), + Meta: meta, Password: body.Password, Username: body.Username, Avatar: body.Avatar, @@ -185,8 +191,8 @@ func (h *Handler) quickCreateAccount(writer http.ResponseWriter, request *http.R Device: body.Device, OsVersion: body.OSVersion, Birth: body.Birth, - AppVersion: body.AppVersion, - BuildNumber: body.BuildNumber, + AppVersion: meta.GetAppVersion(), + BuildNumber: meta.GetBuildNumber(), Source: body.Source, InstallChannel: body.InstallChannel, Campaign: body.Campaign, @@ -256,8 +262,11 @@ func (h *Handler) loginThirdParty(writer http.ResponseWriter, request *http.Requ return } + meta := authRequestMetaWithLoginContext(request, body.DeviceID, body.Platform, body.Language, body.Timezone, body.AppVersion, body.BuildNumber) + // 三方登录即注册路径同样回退设备头,注册快照与设备档案共用同一来源。 + body.Device, body.OSVersion = deviceFieldsWithHeaderFallback(body.Device, body.OSVersion, meta) resp, err := h.userClient.LoginThirdParty(request.Context(), &userv1.LoginThirdPartyRequest{ - Meta: authRequestMetaWithLoginContext(request, body.DeviceID, body.Platform, body.Language, body.Timezone), + Meta: meta, Provider: body.Provider, Credential: body.Credential, Username: body.Username, @@ -269,8 +278,8 @@ func (h *Handler) loginThirdParty(writer http.ResponseWriter, request *http.Requ OsVersion: body.OSVersion, Avatar: body.Avatar, Birth: body.Birth, - AppVersion: body.AppVersion, - BuildNumber: body.BuildNumber, + AppVersion: meta.GetAppVersion(), + BuildNumber: meta.GetBuildNumber(), Source: body.Source, InstallChannel: body.InstallChannel, Campaign: body.Campaign, @@ -333,8 +342,11 @@ func (h *Handler) registerThirdParty(writer http.ResponseWriter, request *http.R return } + meta := authRequestMetaWithLoginContext(request, body.DeviceID, body.Platform, body.Language, body.Timezone, body.AppVersion, body.BuildNumber) + // 注册专用入口的 register_device/os_version 快照缺 body 时回退设备头。 + body.Device, body.OSVersion = deviceFieldsWithHeaderFallback(body.Device, body.OSVersion, meta) resp, err := h.userClient.RegisterThirdParty(request.Context(), &userv1.RegisterThirdPartyRequest{ - Meta: authRequestMetaWithLoginContext(request, body.DeviceID, body.Platform, body.Language, body.Timezone), + Meta: meta, Provider: body.Provider, Credential: body.Credential, DeviceId: body.DeviceID, @@ -346,8 +358,8 @@ func (h *Handler) registerThirdParty(writer http.ResponseWriter, request *http.R Device: body.Device, OsVersion: body.OSVersion, Birth: body.Birth, - AppVersion: body.AppVersion, - BuildNumber: body.BuildNumber, + AppVersion: meta.GetAppVersion(), + BuildNumber: meta.GetBuildNumber(), Source: body.Source, InstallChannel: body.InstallChannel, Campaign: body.Campaign, @@ -428,6 +440,8 @@ func (h *Handler) refreshToken(writer http.ResponseWriter, request *http.Request var body struct { RefreshToken string `json:"refresh_token"` DeviceID string `json:"device_id"` + AppVersion string `json:"app_version"` + BuildNumber string `json:"build_number"` } if !decode(writer, request, &body) { return @@ -444,7 +458,8 @@ func (h *Handler) refreshToken(writer http.ResponseWriter, request *http.Request } resp, err := h.userClient.RefreshToken(request.Context(), &userv1.RefreshTokenRequest{ - Meta: authRequestMeta(request, body.DeviceID), + // refresh 是登录态自动恢复的一部分,必须携带当前安装包版本;否则升级后画像会一直停留在旧显式登录版本。 + Meta: authRequestMetaWithLoginContext(request, body.DeviceID, "", "", "", body.AppVersion, body.BuildNumber), RefreshToken: body.RefreshToken, }) if err != nil { @@ -488,12 +503,46 @@ func (h *Handler) logout(writer http.ResponseWriter, request *http.Request) { httpkit.WriteOK(writer, request, map[string]bool{"revoked": resp.GetRevoked()}) } -// authRequestMeta 生成 user-service auth RPC 的统一追踪和审计元信息。 -func authRequestMeta(request *http.Request, deviceID string) *userv1.RequestMeta { - return authRequestMetaWithLoginContext(request, deviceID, "", "", "") +// deviceFieldsWithHeaderFallback 在 body 未携带设备资料时回退到全局设备头。 +// body 字段属于旧三方登录协议,标准 Flutter 客户端只发 X-Device-Model/X-OS-Version 头; +// 两个来源必须在 gateway 收敛一次,注册快照(users 表)与登录审计设备档案才不会互相矛盾。 +func deviceFieldsWithHeaderFallback(bodyDevice string, bodyOSVersion string, meta *userv1.RequestMeta) (string, string) { + device := strings.TrimSpace(bodyDevice) + if device == "" { + device = meta.GetDeviceModel() + } + osVersion := strings.TrimSpace(bodyOSVersion) + if osVersion == "" { + osVersion = meta.GetOsVersion() + } + return device, osVersion } -func authRequestMetaWithLoginContext(request *http.Request, deviceID string, platform string, language string, timezone string) *userv1.RequestMeta { +// authRequestMeta 生成 user-service auth RPC 的统一追踪和审计元信息。 +func authRequestMeta(request *http.Request, deviceID string) *userv1.RequestMeta { + return authRequestMetaWithLoginContext(request, deviceID, "", "", "", "", "") +} + +func authRequestMetaWithLoginContext( + request *http.Request, + deviceID string, + platform string, + language string, + timezone string, + bodyAppVersion string, + bodyBuildNumber string, +) *userv1.RequestMeta { + // 标准 App 客户端统一从 Header 上报版本;body 只为仍按旧三方登录协议接入的客户端兜底。 + // 两个值必须在 gateway 只解析一次并同时写入 Meta 和三方注册字段,避免审计版本与设备资料版本互相矛盾。 + appVersion := httpkit.FirstHeader(request, "X-App-Version") + if appVersion == "" { + appVersion = strings.TrimSpace(bodyAppVersion) + } + buildNumber := httpkit.FirstHeader(request, "X-App-Build-Number") + if buildNumber == "" { + buildNumber = strings.TrimSpace(bodyBuildNumber) + } + return &userv1.RequestMeta{ RequestId: httpkit.RequestIDFromContext(request.Context()), Caller: "gateway-service", @@ -508,6 +557,13 @@ func authRequestMetaWithLoginContext(request *http.Request, deviceID string, pla Platform: strings.ToLower(strings.TrimSpace(platform)), Language: strings.TrimSpace(language), Timezone: strings.TrimSpace(timezone), + AppVersion: appVersion, + BuildNumber: buildNumber, + // 设备资料头由 App 全局注入;认证成功后 user-service 按 device_id 聚合进 user_devices。 + DeviceModel: httpkit.FirstHeader(request, "X-Device-Model"), + DeviceManufacturer: httpkit.FirstHeader(request, "X-Device-Manufacturer"), + OsVersion: httpkit.FirstHeader(request, "X-OS-Version"), + Imei: httpkit.FirstHeader(request, "X-Device-IMEI"), } } diff --git a/services/gateway-service/internal/transport/http/callbackapi/handler.go b/services/gateway-service/internal/transport/http/callbackapi/handler.go index 0f7ec3f6..b61cfa2e 100644 --- a/services/gateway-service/internal/transport/http/callbackapi/handler.go +++ b/services/gateway-service/internal/transport/http/callbackapi/handler.go @@ -28,6 +28,7 @@ type Handler struct { roomClient client.RoomClient roomGuardClient client.RoomGuardClient userProfileClient client.UserProfileClient + userHostClient client.UserHostClient tencentIM TencentIMConfig tencentRTC TencentRTCConfig } @@ -36,6 +37,7 @@ type Config struct { RoomClient client.RoomClient RoomGuardClient client.RoomGuardClient UserProfileClient client.UserProfileClient + UserHostClient client.UserHostClient TencentIM TencentIMConfig TencentRTC TencentRTCConfig } @@ -45,6 +47,7 @@ func New(config Config) *Handler { roomClient: config.RoomClient, roomGuardClient: config.RoomGuardClient, userProfileClient: config.UserProfileClient, + userHostClient: config.UserHostClient, tencentIM: config.TencentIM, tencentRTC: config.TencentRTC, } diff --git a/services/gateway-service/internal/transport/http/callbackapi/tencent_im_callback.go b/services/gateway-service/internal/transport/http/callbackapi/tencent_im_callback.go index c7ac09bd..0f30fd9f 100644 --- a/services/gateway-service/internal/transport/http/callbackapi/tencent_im_callback.go +++ b/services/gateway-service/internal/transport/http/callbackapi/tencent_im_callback.go @@ -1,9 +1,12 @@ package callbackapi import ( + "context" "encoding/json" + "fmt" "hyapp/services/gateway-service/internal/transport/http/httpkit" "io" + "log/slog" "net/http" "strconv" "strings" @@ -18,6 +21,7 @@ import ( const ( tencentCallbackJoinGroup = "Group.CallbackBeforeApplyJoinGroup" tencentCallbackSendMsg = "Group.CallbackBeforeSendMsg" + tencentCallbackC2CSent = "C2C.CallbackAfterSendMsg" ) // tencentIMCallbackBody 覆盖本阶段需要处理的腾讯云 IM 群回调字段。 @@ -28,6 +32,11 @@ type tencentIMCallbackBody struct { Requestor string `json:"Requestor_Account"` FromAccount string `json:"From_Account"` OperatorAccount string `json:"Operator_Account"` + ToAccount string `json:"To_Account"` + MsgKey string `json:"MsgKey"` + MsgSeq int64 `json:"MsgSeq"` + MsgRandom int64 `json:"MsgRandom"` + MsgTime int64 `json:"MsgTime"` } // tencentIMCallbackResponse 是腾讯云 IM 回调要求的原生响应结构。 @@ -86,11 +95,60 @@ func (h *Handler) handleTencentIMCallback(writer http.ResponseWriter, request *h h.handleTencentIMJoinCallback(writer, request, body) case tencentCallbackSendMsg: h.handleTencentIMSendCallback(writer, request, body) + case tencentCallbackC2CSent: + h.handleTencentIMC2CSentCallback(writer, request, body) default: writeTencentCallback(writer, http.StatusOK, 0, "") } } +func (h *Handler) handleTencentIMC2CSentCallback(writer http.ResponseWriter, request *http.Request, body tencentIMCallbackBody) { + // 服务端系统通知不属于主播“收到私信的人数”,否则批量余额/VIP 通知会污染运营指标。 + if h.tencentIMCallbackSenderIsServer(body) { + writeTencentCallback(writer, http.StatusOK, 0, "") + return + } + senderUserID, senderOK := parseTencentIMUserID(body.FromAccount) + targetUserID, targetOK := parseTencentIMUserID(body.ToAccount) + callbackAppCode := appcode.Normalize(request.URL.Query().Get("app_code")) + if callbackAppCode == "" { + callbackAppCode = appcode.FromContext(request.Context()) + } + recorder, recorderOK := h.userHostClient.(interface { + RecordPrivateMessageEvent(context.Context, *userv1.RecordPrivateMessageEventRequest) (*userv1.RecordPrivateMessageEventResponse, error) + }) + if !senderOK || !targetOK || senderUserID == targetUserID || !recorderOK { + // 统计旁路不能阻断用户已经发送成功的私信;缺配置只记告警,腾讯仍收到成功响应。 + slog.Warn("tencent_im_private_message_stats_skipped", "app_code", callbackAppCode, "from", body.FromAccount, "to", body.ToAccount) + writeTencentCallback(writer, http.StatusOK, 0, "") + return + } + occurredAtMS := body.MsgTime * 1000 + if occurredAtMS <= 0 { + occurredAtMS = time.Now().UTC().UnixMilli() + } + eventID := strings.TrimSpace(body.MsgKey) + if eventID == "" { + eventID = fmt.Sprintf("c2c:%d:%d:%d:%d:%d", senderUserID, targetUserID, body.MsgSeq, body.MsgRandom, occurredAtMS) + } + _, err := recorder.RecordPrivateMessageEvent(appcode.WithContext(request.Context(), callbackAppCode), &userv1.RecordPrivateMessageEventRequest{ + Meta: &userv1.RequestMeta{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + Caller: "gateway-service", + SentAtMs: time.Now().UTC().UnixMilli(), + AppCode: callbackAppCode, + }, + EventId: eventID, + SenderUserId: senderUserID, + TargetUserId: targetUserID, + OccurredAtMs: occurredAtMS, + }) + if err != nil { + slog.Warn("tencent_im_private_message_stats_failed", "app_code", callbackAppCode, "event_id", eventID, "error", err) + } + writeTencentCallback(writer, http.StatusOK, 0, "") +} + func (h *Handler) handleTencentIMJoinCallback(writer http.ResponseWriter, request *http.Request, body tencentIMCallbackBody) { parsed := imgroup.ParseWithPrefix(body.GroupID, h.tencentIM.GroupIDPrefix) userID, ok := parseTencentIMUserID(body.Requestor) @@ -294,7 +352,7 @@ func resolveTencentIMCallbackCommand(queryCommand string, bodyCommand string) (s } func knownTencentIMCallback(command string) bool { - return command == tencentCallbackJoinGroup || command == tencentCallbackSendMsg + return command == tencentCallbackJoinGroup || command == tencentCallbackSendMsg || command == tencentCallbackC2CSent } func parseTencentIMUserID(value string) (int64, bool) { diff --git a/services/gateway-service/internal/transport/http/callbackapi/tencent_rtc_callback.go b/services/gateway-service/internal/transport/http/callbackapi/tencent_rtc_callback.go index abc20f9f..e6281167 100644 --- a/services/gateway-service/internal/transport/http/callbackapi/tencent_rtc_callback.go +++ b/services/gateway-service/internal/transport/http/callbackapi/tencent_rtc_callback.go @@ -88,8 +88,8 @@ func (h *Handler) handleTencentRTCCallback(writer http.ResponseWriter, request * roomID, ok := parseTencentRTCRoomID(body.EventInfo.RoomID) userID, userOK := parseTencentIMUserID(body.EventInfo.UserID) eventTimeMS := tencentRTCEventTimeMS(body) - logCtx := logx.With(request.Context(), slog.String("request_id", httpkit.RequestIDFromContext(request.Context())), slog.String("app_code", tencentRTCCallbackAppCode(request))) - if !ok || !userOK || eventTimeMS <= 0 || h.roomClient == nil { + logCtx := logx.With(request.Context(), slog.String("request_id", httpkit.RequestIDFromContext(request.Context()))) + if !ok || !userOK || eventTimeMS <= 0 || h.roomClient == nil || h.roomGuardClient == nil { logx.Warn(logCtx, "gateway_rtc_callback_ignored", slog.String("provider", "tencent"), slog.Int("event_group", body.EventGroupID), @@ -102,13 +102,37 @@ func (h *Handler) handleTencentRTCCallback(writer http.ResponseWriter, request * return } + // 腾讯回调只携带 SDKAppID 和 room_id;线上当前多个 App 共用腾讯应用,URL app_code 无法代表真实房间租户。 + // 房间归属必须回到 room-service 的持久事实解析,避免 Fami 回调被固定 lalu 参数写入错误 Room Cell。 + appResp, err := h.roomGuardClient.ResolveRoomAppCode(request.Context(), &roomv1.ResolveRoomAppCodeRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + RoomId: roomID, + }) + if err != nil { + if tencentRTCCallbackCanAckBusinessError(err) { + logx.Warn(logCtx, "gateway_rtc_callback_room_app_ignored", slog.String("room_id", roomID), slog.String("reason", string(xerr.ReasonFromGRPC(err)))) + writeTencentRTCCallback(writer, http.StatusOK, 0, "") + return + } + logx.Error(logCtx, "gateway_rtc_callback_room_app_error", err, slog.String("room_id", roomID)) + writeTencentRTCCallback(writer, http.StatusInternalServerError, 1, "room service error") + return + } + resolvedAppCode := appcode.Normalize(appResp.GetAppCode()) + if strings.TrimSpace(appResp.GetAppCode()) == "" { + logx.Warn(logCtx, "gateway_rtc_callback_room_app_ignored", slog.String("room_id", roomID), slog.String("reason", "app_code_empty")) + writeTencentRTCCallback(writer, http.StatusOK, 0, "") + return + } + logCtx = logx.With(logCtx, slog.String("app_code", resolvedAppCode)) + resp, err := h.roomClient.ApplyRTCEvent(request.Context(), &roomv1.ApplyRTCEventRequest{ Meta: &roomv1.RequestMeta{ RequestId: httpkit.RequestIDFromContext(request.Context()), CommandId: tencentRTCCallbackCommandID(body, roomID, userID, eventTimeMS), ActorUserId: userID, RoomId: roomID, - AppCode: tencentRTCCallbackAppCode(request), + AppCode: resolvedAppCode, GatewayNodeId: "gateway-local", SessionId: fmt.Sprintf("tencent-rtc-%d", body.EventType), SentAtMs: time.Now().UnixMilli(), @@ -242,14 +266,6 @@ func tencentRTCCallbackDigest(body tencentRTCCallbackBody, roomID string, userID return hex.EncodeToString(sum[:16]) } -func tencentRTCCallbackAppCode(request *http.Request) string { - if raw := strings.TrimSpace(request.URL.Query().Get("app_code")); raw != "" { - return appcode.Normalize(raw) - } - - return appcode.FromContext(request.Context()) -} - func tencentRTCCallbackCanAckBusinessError(err error) bool { reason := xerr.ReasonFromGRPC(err) return reason == xerr.NotFound || reason == xerr.InvalidArgument || reason == xerr.RoomClosed || reason == xerr.PermissionDenied diff --git a/services/gateway-service/internal/transport/http/cors.go b/services/gateway-service/internal/transport/http/cors.go index 32bc8fdd..5f02c6d4 100644 --- a/services/gateway-service/internal/transport/http/cors.go +++ b/services/gateway-service/internal/transport/http/cors.go @@ -101,7 +101,8 @@ func normalizeCORSConfig(config CORSConfig) CORSConfig { config.AllowedMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"} } if len(config.AllowedHeaders) == 0 { - config.AllowedHeaders = []string{"Authorization", "Content-Type", "X-Request-ID", "X-App-Code", "X-HY-App-Code", "X-App-Package", "X-Package-Name", "X-App-Bundle-ID", "X-Bundle-ID", "X-App-Platform", "X-Platform"} + // 设备资料头进入允许集,H5 客户端才能与原生共用同一套设备上报协议。 + config.AllowedHeaders = []string{"Authorization", "Content-Type", "X-Request-ID", "X-App-Code", "X-HY-App-Code", "X-App-Package", "X-Package-Name", "X-App-Bundle-ID", "X-Bundle-ID", "X-App-Platform", "X-Platform", "X-App-Version", "X-App-Build-Number", "X-Device-Model", "X-Device-Manufacturer", "X-OS-Version", "X-Device-IMEI"} } if len(config.ExposeHeaders) == 0 { config.ExposeHeaders = []string{"X-Request-ID", "X-Hyapp-Access-Token", "X-Hyapp-Token-Type", "X-Hyapp-Expires-In"} diff --git a/services/gateway-service/internal/transport/http/cors_test.go b/services/gateway-service/internal/transport/http/cors_test.go index ed223986..adea235c 100644 --- a/services/gateway-service/internal/transport/http/cors_test.go +++ b/services/gateway-service/internal/transport/http/cors_test.go @@ -13,7 +13,7 @@ func TestCORSPreflightShortCircuitsBusinessHandler(t *testing.T) { Enabled: true, AllowedOrigins: []string{"https://global-interaction.com"}, AllowedMethods: []string{"POST", "OPTIONS"}, - AllowedHeaders: []string{"Content-Type", "X-App-Code"}, + AllowedHeaders: []string{"Content-Type", "X-App-Code", "X-App-Version", "X-App-Build-Number"}, ExposeHeaders: []string{"X-Request-ID"}, AllowCredentials: true, MaxAgeSec: 300, @@ -24,7 +24,7 @@ func TestCORSPreflightShortCircuitsBusinessHandler(t *testing.T) { request := httptest.NewRequest(http.MethodOptions, "/api/v1/auth/third-party/login", nil) request.Header.Set("Origin", "https://global-interaction.com") request.Header.Set("Access-Control-Request-Method", "POST") - request.Header.Set("Access-Control-Request-Headers", "content-type,x-app-code") + request.Header.Set("Access-Control-Request-Headers", "content-type,x-app-code,x-app-version,x-app-build-number") recorder := httptest.NewRecorder() handler.ServeHTTP(recorder, request) @@ -41,11 +41,21 @@ func TestCORSPreflightShortCircuitsBusinessHandler(t *testing.T) { if got := recorder.Header().Get("Access-Control-Allow-Credentials"); got != "true" { t.Fatalf("unexpected allow credentials: %q", got) } - if got := recorder.Header().Get("Access-Control-Allow-Headers"); got != "Content-Type, X-App-Code" { + if got := recorder.Header().Get("Access-Control-Allow-Headers"); got != "Content-Type, X-App-Code, X-App-Version, X-App-Build-Number" { t.Fatalf("unexpected allow headers: %q", got) } } +func TestCORSDefaultHeadersAllowLoginClientVersion(t *testing.T) { + config := normalizeCORSConfig(CORSConfig{}) + allowedHeaders := "," + strings.ToLower(strings.Join(config.AllowedHeaders, ",")) + "," + for _, header := range []string{"X-App-Version", "X-App-Build-Number"} { + if !strings.Contains(allowedHeaders, ","+strings.ToLower(header)+",") { + t.Fatalf("default CORS headers must allow browser login version metadata %q: %+v", header, config.AllowedHeaders) + } + } +} + func TestCORSRejectsDisallowedPreflightOrigin(t *testing.T) { called := false handler := WithCORS(CORSConfig{ diff --git a/services/gateway-service/internal/transport/http/device_meta_test.go b/services/gateway-service/internal/transport/http/device_meta_test.go new file mode 100644 index 00000000..b6937d44 --- /dev/null +++ b/services/gateway-service/internal/transport/http/device_meta_test.go @@ -0,0 +1,68 @@ +package http + +import ( + "net/http/httptest" + "strings" + "testing" + + "hyapp/services/gateway-service/internal/transport/http/httpkit" +) + +// 设备资料头是 App 全局注入的,两个 meta builder(httpkit 通用版与 auth 专用版)都必须透传, +// 否则登录链路与业务链路的设备档案会出现来源分叉。 +func TestRequestMetaBuildersCarryDeviceProfileHeaders(t *testing.T) { + request := httptest.NewRequest("POST", "/api/v1/auth/login", nil) + request.Header.Set("X-Device-Model", "Redmi Note 8 Pro") + request.Header.Set("X-Device-Manufacturer", "Xiaomi") + request.Header.Set("X-OS-Version", "13") + request.Header.Set("X-Device-IMEI", "123456789012345") + + authMeta := authRequestMetaWithLoginContext(request, "dev-1", "android", "en", "Asia/Riyadh", "", "") + if authMeta.GetDeviceModel() != "Redmi Note 8 Pro" || authMeta.GetDeviceManufacturer() != "Xiaomi" || + authMeta.GetOsVersion() != "13" || authMeta.GetImei() != "123456789012345" { + t.Fatalf("auth meta must carry device profile headers: %+v", authMeta) + } + + userMeta := httpkit.UserMeta(request, "dev-1") + if userMeta.GetDeviceModel() != "Redmi Note 8 Pro" || userMeta.GetDeviceManufacturer() != "Xiaomi" || + userMeta.GetOsVersion() != "13" || userMeta.GetImei() != "123456789012345" { + t.Fatalf("httpkit user meta must carry device profile headers: %+v", userMeta) + } + + // 头缺失时保持空串:user-service 端“非空覆盖”依赖空值不参与合并。 + bare := httptest.NewRequest("POST", "/api/v1/auth/refresh", nil) + bareMeta := authRequestMetaWithLoginContext(bare, "dev-1", "", "", "", "", "") + if bareMeta.GetDeviceModel() != "" || bareMeta.GetImei() != "" { + t.Fatalf("missing headers must map to empty meta fields: %+v", bareMeta) + } +} + +// body device/os_version 属于旧协议;标准客户端只发设备头,回退逻辑保证 register 快照不再为空, +// 同时 body 显式值仍优先,旧客户端行为不变。 +func TestDeviceFieldsWithHeaderFallback(t *testing.T) { + request := httptest.NewRequest("POST", "/api/v1/auth/register", nil) + request.Header.Set("X-Device-Model", "iPhone17,2") + request.Header.Set("X-OS-Version", "18.1") + meta := authRequestMetaWithLoginContext(request, "dev-2", "ios", "", "", "", "") + + device, osVersion := deviceFieldsWithHeaderFallback("", " ", meta) + if device != "iPhone17,2" || osVersion != "18.1" { + t.Fatalf("empty body fields must fall back to headers: device=%q os=%q", device, osVersion) + } + + device, osVersion = deviceFieldsWithHeaderFallback("Pixel 8", "14", meta) + if device != "Pixel 8" || osVersion != "14" { + t.Fatalf("body fields must win over headers: device=%q os=%q", device, osVersion) + } +} + +// H5 与原生共用设备上报协议,新设备头必须落进默认 CORS 允许集,否则浏览器 preflight 直接拦掉。 +func TestCORSDefaultHeadersAllowDeviceProfile(t *testing.T) { + config := normalizeCORSConfig(CORSConfig{}) + allowedHeaders := "," + strings.ToLower(strings.Join(config.AllowedHeaders, ",")) + "," + for _, header := range []string{"X-Device-Model", "X-Device-Manufacturer", "X-OS-Version", "X-Device-IMEI"} { + if !strings.Contains(allowedHeaders, ","+strings.ToLower(header)+",") { + t.Fatalf("default CORS headers must allow device profile header %q: %+v", header, config.AllowedHeaders) + } + } +} diff --git a/services/gateway-service/internal/transport/http/gift_capacity_test.go b/services/gateway-service/internal/transport/http/gift_capacity_test.go new file mode 100644 index 00000000..c4d8af88 --- /dev/null +++ b/services/gateway-service/internal/transport/http/gift_capacity_test.go @@ -0,0 +1,486 @@ +package http + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "net" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/redis/go-redis/v9" + roomv1 "hyapp.local/api/proto/room/v1" + "hyapp/services/gateway-service/internal/auth" + "hyapp/services/gateway-service/internal/transport/http/giftlimit" +) + +type fakeGiftCapacityLimiter struct { + decision giftlimit.Decision + err error + inputs []giftlimit.Input + releases int +} + +func (f *fakeGiftCapacityLimiter) Acquire(_ context.Context, input giftlimit.Input) (giftlimit.Decision, error) { + f.inputs = append(f.inputs, input) + return f.decision, f.err +} + +func (f *fakeGiftCapacityLimiter) Release(context.Context, giftlimit.Lease) error { + f.releases++ + return nil +} + +func TestGiftCapacityLimitWeightsUniqueTargetsAndReleasesInFlightSlot(t *testing.T) { + roomClient := &fakeRoomClient{} + limiter := &fakeGiftCapacityLimiter{decision: giftlimit.Decision{Allowed: true}} + handler := NewHandlerWithClients(roomClient, nil, nil, &fakeUserProfileClient{regionID: 1001}) + handler.SetGiftCapacityProtection(true, 5*time.Second, 30, limiter) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/gift/batch-send", bytes.NewReader([]byte(`{"room_id":"room-1","command_id":"cmd-weighted","target_user_ids":[43,44,43,0],"gift_id":"rose","gift_count":1}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "huwaa")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if len(limiter.inputs) != 1 { + t.Fatalf("limiter acquire count mismatch: %d", len(limiter.inputs)) + } + input := limiter.inputs[0] + if input.AppCode != "huwaa" || input.UserID != 42 || input.RoomID != "room-1" || input.Weight != 2 || input.RequestID == "" { + t.Fatalf("weighted limiter input mismatch: %+v", input) + } + if limiter.releases != 1 { + t.Fatalf("successful acquire must release in-flight slot exactly once: %d", limiter.releases) + } + if roomClient.lastGift == nil || len(roomClient.lastGift.GetTargetUserIds()) != 2 { + t.Fatalf("normal batch-send behavior changed: %+v", roomClient.lastGift) + } +} + +func TestGiftCapacityLimitPreservesSingleTargetWeight(t *testing.T) { + limiter := &fakeGiftCapacityLimiter{decision: giftlimit.Decision{Allowed: true}} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{regionID: 1001}) + handler.SetGiftCapacityProtection(true, 5*time.Second, 30, limiter) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/gift/send", bytes.NewReader([]byte(`{"room_id":"room-1","command_id":"cmd-single","target_user_id":43,"gift_id":"rose","gift_count":1}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if len(limiter.inputs) != 1 || limiter.inputs[0].Weight != 1 { + t.Fatalf("legacy single-target request must consume one token: %+v", limiter.inputs) + } +} + +func TestGiftCapacityLimitWeightsGiftCountTimesUniqueTargets(t *testing.T) { + limiter := &fakeGiftCapacityLimiter{decision: giftlimit.Decision{Allowed: true}} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{regionID: 1001}) + handler.SetGiftCapacityProtection(true, 5*time.Second, 30, limiter) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/gift/batch-send", bytes.NewReader([]byte(`{"room_id":"room-1","command_id":"cmd-units","target_user_ids":[43,44,43],"gift_id":"rose","gift_count":99}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK || len(limiter.inputs) != 1 || limiter.inputs[0].Weight != 198 { + t.Fatalf("gift work units must be count*deduplicated targets: status=%d inputs=%+v body=%s", recorder.Code, limiter.inputs, recorder.Body.String()) + } +} + +func TestGiftCapacityLimitRejectsExplosiveGiftUnitsBeforeRedis(t *testing.T) { + targets := make([]int64, 0, 6) + for userID := int64(43); userID < 49; userID++ { + targets = append(targets, userID) + } + body, err := json.Marshal(map[string]any{ + "room_id": "room-1", + "command_id": "cmd-too-many-units", + "target_user_ids": targets, + "gift_id": "rose", + "gift_count": 999, + }) + if err != nil { + t.Fatalf("marshal body: %v", err) + } + roomClient := &fakeRoomClient{} + limiter := &fakeGiftCapacityLimiter{decision: giftlimit.Decision{Allowed: true}} + handler := NewHandlerWithClients(roomClient, nil, nil, &fakeUserProfileClient{regionID: 1001}) + handler.SetGiftCapacityProtection(true, 5*time.Second, 30, limiter) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/gift/batch-send", bytes.NewReader(body)) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusBadRequest || len(limiter.inputs) != 0 || roomClient.lastGift != nil { + t.Fatalf("gift_count*targets above hard cap reached Redis/downstream: status=%d inputs=%+v gift=%+v", recorder.Code, limiter.inputs, roomClient.lastGift) + } +} + +func TestGiftCapacityLimitRejectsGiftCountAboveV2Contract(t *testing.T) { + roomClient := &fakeRoomClient{} + limiter := &fakeGiftCapacityLimiter{decision: giftlimit.Decision{Allowed: true}} + handler := NewHandlerWithClients(roomClient, nil, nil, &fakeUserProfileClient{regionID: 1001}) + handler.SetGiftCapacityProtection(true, 5*time.Second, 30, limiter) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/gift/send", bytes.NewReader([]byte(`{"room_id":"room-1","command_id":"cmd-count-overflow","target_user_id":43,"gift_id":"rose","gift_count":1000}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusBadRequest || len(limiter.inputs) != 0 || roomClient.lastGift != nil { + t.Fatalf("gift_count above 999 reached Redis/downstream: status=%d inputs=%+v gift=%+v", recorder.Code, limiter.inputs, roomClient.lastGift) + } +} + +func TestSendGiftV2PreservesV1FieldsAndForwardsStableBatchIdentity(t *testing.T) { + roomClient := &fakeRoomClient{sendGiftResp: &roomv1.SendGiftResponse{ + Result: &roomv1.CommandResult{Applied: true, RoomVersion: 14}, + BillingReceiptId: "receipt-v2", + CoinBalanceAfter: 900, + V2: &roomv1.SendGiftResultV2{ + ApiVersion: 2, CommandId: "combo-1_b1", ComboSessionId: "combo-1", BatchSeq: 1, + CommittedRoomVersion: 14, BillingReceiptId: "receipt-v2", CoinBalanceAfter: 900, + SettledGiftCount: 9, SettledTargetUserIds: []int64{43}, OperationStatus: "committed", + }, + }} + handler := NewHandlerWithClients(roomClient, nil, nil, &fakeUserProfileClient{regionID: 1001}) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v2/rooms/gift/send", bytes.NewReader([]byte(`{"room_id":"room-1","command_id":"combo-1_b1","combo_session_id":"combo-1","batch_seq":1,"target_user_id":43,"gift_id":"rose","gift_count":9}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("V2 status mismatch: got=%d body=%s", recorder.Code, recorder.Body.String()) + } + if roomClient.lastGift == nil || roomClient.lastGift.GetComboSessionId() != "combo-1" || roomClient.lastGift.GetBatchSeq() != 1 || roomClient.lastGift.GetMeta().GetCommandId() != "combo-1_b1" || roomClient.lastGift.GetDisplayMode() != "" { + t.Fatalf("V2 stable batch identity was not forwarded: %+v", roomClient.lastGift) + } + var envelope struct { + Data struct { + BillingReceiptID string `json:"billing_receipt_id"` + CoinBalanceAfter int64 `json:"coin_balance_after"` + V2 struct { + CommandID string `json:"command_id"` + CommittedRoomVersion int64 `json:"committed_room_version"` + } `json:"v2"` + } `json:"data"` + } + if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil { + t.Fatalf("decode V2 response: %v", err) + } + if envelope.Data.BillingReceiptID != "receipt-v2" || envelope.Data.CoinBalanceAfter != 900 || envelope.Data.V2.CommandID != "combo-1_b1" || envelope.Data.V2.CommittedRoomVersion != 14 { + t.Fatalf("V1 compatibility or nested V2 result missing: %+v body=%s", envelope, recorder.Body.String()) + } +} + +func TestSendGiftV1RoutesPreserveTopLevelFieldsAndStripNestedV2(t *testing.T) { + tests := []struct { + name string + path string + body string + displayMode string + }{ + { + name: "single", + path: "/api/v1/rooms/gift/send", + body: `{"room_id":"room-1","command_id":"legacy-single","target_user_id":43,"gift_id":"rose","gift_count":1}`, + }, + { + name: "batch", + path: "/api/v1/rooms/gift/batch-send", + body: `{"room_id":"room-1","command_id":"legacy-batch","target_user_ids":[43,44],"gift_id":"rose","gift_count":2}`, + displayMode: "batch", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + roomClient := &fakeRoomClient{sendGiftResp: &roomv1.SendGiftResponse{ + Result: &roomv1.CommandResult{Applied: true, RoomVersion: 14}, + BillingReceiptId: "legacy-receipt", + RoomHeat: 81, + CoinBalanceAfter: 900, + GiftIncomeBalanceAfter: 700, + V2: &roomv1.SendGiftResultV2{ + ApiVersion: 2, CommandId: "internal-result", OperationStatus: "committed", + }, + }} + router := NewHandlerWithClients(roomClient, nil, nil, &fakeUserProfileClient{regionID: 1001}).Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, test.path, bytes.NewReader([]byte(test.body))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("V1 status mismatch: got=%d body=%s", recorder.Code, recorder.Body.String()) + } + if roomClient.lastGift == nil || roomClient.lastGift.GetDisplayMode() != test.displayMode { + t.Fatalf("V1 request behavior changed: %+v", roomClient.lastGift) + } + var envelope struct { + Data map[string]json.RawMessage `json:"data"` + } + if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil { + t.Fatalf("decode V1 response: %v", err) + } + if _, exists := envelope.Data["v2"]; exists { + t.Fatalf("legacy V1 response exposed nested v2: %s", recorder.Body.String()) + } + var receipt string + var balance int64 + var roomHeat int64 + if err := json.Unmarshal(envelope.Data["billing_receipt_id"], &receipt); err != nil { + t.Fatalf("decode billing_receipt_id: %v", err) + } + if err := json.Unmarshal(envelope.Data["coin_balance_after"], &balance); err != nil { + t.Fatalf("decode coin_balance_after: %v", err) + } + if err := json.Unmarshal(envelope.Data["room_heat"], &roomHeat); err != nil { + t.Fatalf("decode room_heat: %v", err) + } + if receipt != "legacy-receipt" || balance != 900 || roomHeat != 81 { + t.Fatalf("V1 top-level fields changed: receipt=%q balance=%d heat=%d body=%s", receipt, balance, roomHeat, recorder.Body.String()) + } + }) + } +} + +func TestSendGiftV2RequiresComboIdentityBeforeDownstream(t *testing.T) { + roomClient := &fakeRoomClient{} + handler := NewHandlerWithClients(roomClient, nil, nil, &fakeUserProfileClient{regionID: 1001}) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v2/rooms/gift/send", bytes.NewReader([]byte(`{"room_id":"room-1","command_id":"cmd-no-combo","target_user_id":43,"gift_id":"rose","gift_count":1}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusBadRequest || roomClient.lastGift != nil { + t.Fatalf("V2 without combo identity reached downstream: status=%d gift=%+v body=%s", recorder.Code, roomClient.lastGift, recorder.Body.String()) + } +} + +func TestGiftCapacityLimitReturns429EnvelopeBeforeDownstreamCalls(t *testing.T) { + for _, endpoint := range []struct { + name string + path string + body string + }{ + {name: "single", path: "/api/v1/rooms/gift/send", body: `{"room_id":"room-1","command_id":"cmd-denied-single","target_user_id":43,"gift_id":"rose","gift_count":1}`}, + {name: "batch", path: "/api/v1/rooms/gift/batch-send", body: `{"room_id":"room-1","command_id":"cmd-denied-batch","target_user_ids":[43,44],"gift_id":"rose","gift_count":1}`}, + } { + t.Run(endpoint.name, func(t *testing.T) { + roomClient := &fakeRoomClient{} + profileClient := &fakeUserProfileClient{regionID: 1001} + limiter := &fakeGiftCapacityLimiter{decision: giftlimit.Decision{RetryAfter: 275 * time.Millisecond}} + handler := NewHandlerWithClients(roomClient, nil, nil, profileClient) + handler.SetGiftCapacityProtection(true, 5*time.Second, 30, limiter) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, endpoint.path, bytes.NewReader([]byte(endpoint.body))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusTooManyRequests { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if recorder.Header().Get("Retry-After") != "1" { + t.Fatalf("Retry-After mismatch: %q", recorder.Header().Get("Retry-After")) + } + var envelope struct { + Code string `json:"code"` + Message string `json:"message"` + RequestID string `json:"request_id"` + Data struct { + RetryAfterMS int64 `json:"retry_after_ms"` + } `json:"data"` + } + if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil { + t.Fatalf("decode response failed: %v body=%s", err, recorder.Body.String()) + } + if envelope.Code != "RATE_LIMITED" || envelope.Message != "rate limited" || envelope.RequestID == "" || envelope.Data.RetryAfterMS != 275 { + t.Fatalf("rate-limit envelope mismatch: %+v", envelope) + } + if roomClient.lastGift != nil || profileClient.lastGet != nil || len(profileClient.batchRequests) != 0 { + t.Fatalf("denied request reached downstream: gift=%+v profile_get=%+v profile_batches=%d", roomClient.lastGift, profileClient.lastGet, len(profileClient.batchRequests)) + } + if limiter.releases != 0 { + t.Fatalf("denied request must not release an unowned slot: %d", limiter.releases) + } + }) + } +} + +func TestGiftCapacityLimitFailsClosedWhenRedisUnavailable(t *testing.T) { + roomClient := &fakeRoomClient{} + limiter := &fakeGiftCapacityLimiter{err: errors.New("redis unavailable")} + handler := NewHandlerWithClients(roomClient, nil, nil, &fakeUserProfileClient{regionID: 1001}) + handler.SetGiftCapacityProtection(true, 5*time.Second, 30, limiter) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/gift/send", bytes.NewReader([]byte(`{"room_id":"room-1","command_id":"cmd-error","target_user_id":43,"gift_id":"rose","gift_count":1}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusBadGateway || roomClient.lastGift != nil { + t.Fatalf("limiter backend failure must fail closed: status=%d body=%s gift=%+v", recorder.Code, recorder.Body.String(), roomClient.lastGift) + } +} + +func TestGiftCapacityLimitBlockedRedisFailsClosedQuickly(t *testing.T) { + // net.Pipe 对 Redis 写入只读不回,模拟连接存在但服务端永久不返回 Lua 结果;真正的 + // RedisLimiter 必须靠自己的 operation deadline 结束,而不是依赖测试 fake 立即报错。 + redisClient := redis.NewClient(&redis.Options{ + Addr: "blocked-redis", + MaxRetries: -1, + ReadTimeout: time.Second, + WriteTimeout: time.Second, + ContextTimeoutEnabled: true, + Dialer: func(context.Context, string, string) (net.Conn, error) { + clientConn, serverConn := net.Pipe() + go func() { + defer serverConn.Close() + _, _ = io.Copy(io.Discard, serverConn) + }() + return clientConn, nil + }, + }) + defer redisClient.Close() + limiter := giftlimit.NewRedisLimiter(redisClient, giftlimit.Config{RedisOperationTimeout: 50 * time.Millisecond}) + roomClient := &fakeRoomClient{} + handler := NewHandlerWithClients(roomClient, nil, nil, &fakeUserProfileClient{regionID: 1001}) + handler.SetGiftCapacityProtection(true, 5*time.Second, 30, limiter) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/gift/send", bytes.NewReader([]byte(`{"room_id":"room-1","command_id":"cmd-blocked-redis","target_user_id":43,"gift_id":"rose","gift_count":1}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + recorder := httptest.NewRecorder() + startedAt := time.Now() + + router.ServeHTTP(recorder, request) + elapsed := time.Since(startedAt) + + if recorder.Code != http.StatusBadGateway || roomClient.lastGift != nil || elapsed < 35*time.Millisecond || elapsed > 500*time.Millisecond { + t.Fatalf("blocked Redis must fail closed inside short deadline: status=%d elapsed=%s body=%s gift=%+v", recorder.Code, elapsed, recorder.Body.String(), roomClient.lastGift) + } +} + +func TestGiftCapacityLimitRejectsTooManyUniqueTargetsBeforeRedis(t *testing.T) { + uniqueTargets := make([]int64, 0, 31) + for userID := int64(1); userID <= 31; userID++ { + uniqueTargets = append(uniqueTargets, userID) + } + body, err := json.Marshal(map[string]any{ + "room_id": "room-1", + "command_id": "cmd-too-many", + "target_user_ids": uniqueTargets, + "gift_id": "rose", + "gift_count": 1, + }) + if err != nil { + t.Fatalf("marshal body: %v", err) + } + roomClient := &fakeRoomClient{} + limiter := &fakeGiftCapacityLimiter{decision: giftlimit.Decision{Allowed: true}} + handler := NewHandlerWithClients(roomClient, nil, nil, &fakeUserProfileClient{regionID: 1001}) + handler.SetGiftCapacityProtection(true, 5*time.Second, 30, limiter) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/gift/batch-send", bytes.NewReader(body)) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusBadRequest || len(limiter.inputs) != 0 || roomClient.lastGift != nil { + t.Fatalf("31 unique targets must fail before Redis/downstream: status=%d body=%s inputs=%+v gift=%+v", recorder.Code, recorder.Body.String(), limiter.inputs, roomClient.lastGift) + } +} + +func TestGiftCapacityLimitRejectsUnsupportedTargetTypeBeforeRedis(t *testing.T) { + roomClient := &fakeRoomClient{} + limiter := &fakeGiftCapacityLimiter{decision: giftlimit.Decision{Allowed: true}} + handler := NewHandlerWithClients(roomClient, nil, nil, &fakeUserProfileClient{regionID: 1001}) + handler.SetGiftCapacityProtection(true, 5*time.Second, 30, limiter) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/gift/send", bytes.NewReader([]byte(`{"room_id":"room-1","command_id":"cmd-unsupported","target_type":"all_mic","gift_id":"rose","gift_count":1}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusBadRequest || len(limiter.inputs) != 0 || roomClient.lastGift != nil { + t.Fatalf("unsupported target_type must fail before Redis/downstream: status=%d body=%s inputs=%+v gift=%+v", recorder.Code, recorder.Body.String(), limiter.inputs, roomClient.lastGift) + } +} + +func TestGiftCapacityLimitAppliesMaximumAfterTargetDeduplication(t *testing.T) { + targets := make([]int64, 0, 31) + for userID := int64(1); userID <= 30; userID++ { + targets = append(targets, userID) + } + targets = append(targets, 30) + body, err := json.Marshal(map[string]any{ + "room_id": "room-1", + "command_id": "cmd-dedup-maximum", + "target_user_ids": targets, + "gift_id": "rose", + "gift_count": 1, + }) + if err != nil { + t.Fatalf("marshal body: %v", err) + } + limiter := &fakeGiftCapacityLimiter{decision: giftlimit.Decision{RetryAfter: 10 * time.Millisecond}} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{regionID: 1001}) + handler.SetGiftCapacityProtection(true, 5*time.Second, 30, limiter) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/gift/batch-send", bytes.NewReader(body)) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusTooManyRequests || len(limiter.inputs) != 1 || limiter.inputs[0].Weight != 30 { + t.Fatalf("duplicate target must not trip max_targets_per_request: status=%d body=%s inputs=%+v", recorder.Code, recorder.Body.String(), limiter.inputs) + } +} + +func TestGiftRequestTotalDeadlineReleasesInFlightLease(t *testing.T) { + roomClient := &fakeRoomClient{sendGiftWaitForContext: true} + limiter := &fakeGiftCapacityLimiter{decision: giftlimit.Decision{Allowed: true}} + handler := NewHandlerWithClients(roomClient, nil, nil, &fakeUserProfileClient{regionID: 1001}) + handler.SetGiftCapacityProtection(true, 40*time.Millisecond, 30, limiter) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/gift/send", bytes.NewReader([]byte(`{"room_id":"room-1","command_id":"cmd-timeout","target_user_id":43,"gift_id":"rose","gift_count":1}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + recorder := httptest.NewRecorder() + startedAt := time.Now() + + router.ServeHTTP(recorder, request) + elapsed := time.Since(startedAt) + + if recorder.Code == http.StatusOK || elapsed < 30*time.Millisecond || elapsed > 300*time.Millisecond { + t.Fatalf("gift orchestration must honor one total deadline: status=%d elapsed=%s body=%s", recorder.Code, elapsed, recorder.Body.String()) + } + if roomClient.lastGift == nil || limiter.releases != 1 { + t.Fatalf("timed-out request must reach room once and release lease: gift=%+v releases=%d", roomClient.lastGift, limiter.releases) + } +} diff --git a/services/gateway-service/internal/transport/http/giftlimit/limiter.go b/services/gateway-service/internal/transport/http/giftlimit/limiter.go new file mode 100644 index 00000000..2a9d9fdd --- /dev/null +++ b/services/gateway-service/internal/transport/http/giftlimit/limiter.go @@ -0,0 +1,466 @@ +// Package giftlimit 为 gateway 送礼入口提供跨节点容量保护。 +// +// 送礼请求的最坏下游成本与 gift_count*去重 target_count 成正比:幸运礼物需要逐份开奖, +// 每个目标也会形成独立账务和事实。因此本包不按 HTTP 次数计费,而按服务端已经校验过 +// 的 total_gift_units 在 App、房间、用户三层扣令牌,并额外约束单用户在途请求数。 +package giftlimit + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "math" + "strconv" + "strings" + "time" + + "github.com/redis/go-redis/v9" +) + +const ( + defaultKeyPrefix = "gateway:gift:capacity:" + + // 一个合法请求最多 5000 gift units,因此用户桶必须至少容纳一个完整请求;500/s + // 让极端 999 数量请求形成自然冷却,同时普通 300ms 连击批次不会感到逐击等待。 + defaultUserBucketCapacity = 5_000 + defaultUserRefillTokensPerSecond = 500 + // 房间和 App 层允许多个真实用户同时连击,但把 5 人叠加造成的无界增长转换成 429 + // 背压。数值均为可灰度配置的保护起点,不冒充未经压测的 Broker 容量结论。 + defaultRoomBucketCapacity = 20_000 + defaultRoomRefillTokensPerSecond = 5_000 + defaultAppBucketCapacity = 100_000 + defaultAppRefillTokensPerSecond = 25_000 + + defaultMaxInFlight = 2 + defaultInFlightLease = 10 * time.Second + defaultRedisOperationTimeout = 500 * time.Millisecond + redisReleaseTimeout = time.Second + + redisDialTimeout = time.Second + redisReadTimeout = 300 * time.Millisecond + redisWriteTimeout = 300 * time.Millisecond + redisPoolTimeout = 300 * time.Millisecond + redisMinRetryBackoff = 20 * time.Millisecond + redisMaxRetryBackoff = 50 * time.Millisecond +) + +// Config 描述三层令牌桶、单用户在途租约和 Redis 操作预算。 +type Config struct { + Enabled bool + KeyPrefix string + + UserBucketCapacity int + UserRefillTokensPerSecond float64 + RoomBucketCapacity int + RoomRefillTokensPerSecond float64 + AppBucketCapacity int + AppRefillTokensPerSecond float64 + + MaxInFlight int + InFlightLease time.Duration + RedisOperationTimeout time.Duration +} + +// Input 是一次送礼请求在容量维度上的稳定身份。 +type Input struct { + AppCode string + UserID int64 + RoomID string + RequestID string + Weight int +} + +// Lease 标识已占用的分布式在途槽。字段只供 Limiter.Release 使用。 +type Lease struct { + inFlightKey string + member string +} + +// Decision 返回是否获准以及客户端下一次可重试的最短等待时间。 +type Decision struct { + Allowed bool + RetryAfter time.Duration + Lease Lease +} + +// Limiter 是 room HTTP adapter 所需的最小容量保护边界。 +type Limiter interface { + Acquire(ctx context.Context, input Input) (Decision, error) + Release(ctx context.Context, lease Lease) error +} + +type redisScriptRunner interface { + evalSlice(ctx context.Context, script string, keys []string, args ...any) ([]any, error) +} + +type goRedisScriptRunner struct { + client *redis.Client +} + +type bucketPolicy struct { + capacity int + refillPerSecond float64 +} + +type redisScopeKeys struct { + appBucket string + roomBucket string + userBucket string + inFlight string + leaseMember string +} + +// RedisLimiter 按 user→room→app 顺序执行三个独立 Lua。 +// +// Redis Cluster 只允许一次脚本访问同一 hash slot。App、房间、用户是不同聚合域,不能 +// 伪装成一个跨 slot 原子事务。用户层先检查 in-flight,可防止已占满并发槽的账号用必拒 +// 请求持续消耗房间/App 额度;后层拒绝时释放用户 lease,但保留用户令牌作为请求方成本。 +// App 拒绝时不回滚房间令牌,少量保守损耗比并发回滚造成超发或绕过容量上限更安全。 +type RedisLimiter struct { + runner redisScriptRunner + config Config +} + +// redisTokenBucketScript 在一个聚合维度内原子补充并扣减令牌,返回 +// {allowed, retry_after_ms}。Redis TIME 是多 gateway 节点的统一时间源。 +const redisTokenBucketScript = ` +local redis_time = redis.call("TIME") +local now_ms = tonumber(redis_time[1]) * 1000 + math.floor(tonumber(redis_time[2]) / 1000) +local capacity = tonumber(ARGV[1]) +local refill_per_ms = tonumber(ARGV[2]) +local weight = tonumber(ARGV[3]) +local bucket_ttl_ms = tonumber(ARGV[4]) + +local tokens = tonumber(redis.call("HGET", KEYS[1], "tokens")) +local last_ms = tonumber(redis.call("HGET", KEYS[1], "last_ms")) +if not tokens or not last_ms then + tokens = capacity + last_ms = now_ms +else + tokens = math.min(capacity, tokens + math.max(0, now_ms - last_ms) * refill_per_ms) +end + +if tokens < weight then + redis.call("HSET", KEYS[1], "tokens", tokens, "last_ms", now_ms) + redis.call("PEXPIRE", KEYS[1], bucket_ttl_ms) + local retry_ms = math.ceil((weight - tokens) / refill_per_ms) + return {0, math.max(1, retry_ms)} +end + +redis.call("HSET", KEYS[1], "tokens", tokens - weight, "last_ms", now_ms) +redis.call("PEXPIRE", KEYS[1], bucket_ttl_ms) +return {1, 0} +` + +// redisUserAcquireScript 把用户桶和 app:user 在途 ZSET 放进同一个 hash slot,因此用户 +// 额度检查、并发检查和租约写入在单个 Redis 节点内保持原子。 +const redisUserAcquireScript = ` +local redis_time = redis.call("TIME") +local now_ms = tonumber(redis_time[1]) * 1000 + math.floor(tonumber(redis_time[2]) / 1000) +local capacity = tonumber(ARGV[1]) +local refill_per_ms = tonumber(ARGV[2]) +local weight = tonumber(ARGV[3]) +local max_in_flight = tonumber(ARGV[4]) +local lease_ms = tonumber(ARGV[5]) +local member = ARGV[6] +local bucket_ttl_ms = tonumber(ARGV[7]) + +-- gateway 异常退出不会执行 Release;每次申请前清理过期租约,确保 lease_ms 后自动恢复。 +redis.call("ZREMRANGEBYSCORE", KEYS[2], "-inf", now_ms) +local in_flight = redis.call("ZCARD", KEYS[2]) +if in_flight >= max_in_flight then + local earliest = redis.call("ZRANGE", KEYS[2], 0, 0, "WITHSCORES") + local retry_ms = 1 + if earliest[2] then + retry_ms = math.max(1, tonumber(earliest[2]) - now_ms) + end + return {0, retry_ms} +end + +local tokens = tonumber(redis.call("HGET", KEYS[1], "tokens")) +local last_ms = tonumber(redis.call("HGET", KEYS[1], "last_ms")) +if not tokens or not last_ms then + tokens = capacity + last_ms = now_ms +else + tokens = math.min(capacity, tokens + math.max(0, now_ms - last_ms) * refill_per_ms) +end + +if tokens < weight then + redis.call("HSET", KEYS[1], "tokens", tokens, "last_ms", now_ms) + redis.call("PEXPIRE", KEYS[1], bucket_ttl_ms) + local retry_ms = math.ceil((weight - tokens) / refill_per_ms) + return {0, math.max(1, retry_ms)} +end + +redis.call("HSET", KEYS[1], "tokens", tokens - weight, "last_ms", now_ms) +redis.call("PEXPIRE", KEYS[1], bucket_ttl_ms) +redis.call("ZADD", KEYS[2], now_ms + lease_ms, member) +redis.call("PEXPIRE", KEYS[2], lease_ms * 2) +return {1, 0} +` + +const redisReleaseScript = ` +redis.call("ZREM", KEYS[1], ARGV[1]) +if redis.call("ZCARD", KEYS[1]) == 0 then + redis.call("DEL", KEYS[1]) +end +return {1} +` + +// Normalize 收敛与 10k MQ TPS 和当前送礼行为匹配的安全默认值。 +func Normalize(config Config) Config { + config.KeyPrefix = strings.TrimSpace(config.KeyPrefix) + if config.KeyPrefix == "" { + config.KeyPrefix = defaultKeyPrefix + } + if config.UserBucketCapacity <= 0 { + config.UserBucketCapacity = defaultUserBucketCapacity + } + if config.UserRefillTokensPerSecond <= 0 { + config.UserRefillTokensPerSecond = defaultUserRefillTokensPerSecond + } + if config.RoomBucketCapacity <= 0 { + config.RoomBucketCapacity = defaultRoomBucketCapacity + } + if config.RoomRefillTokensPerSecond <= 0 { + config.RoomRefillTokensPerSecond = defaultRoomRefillTokensPerSecond + } + if config.AppBucketCapacity <= 0 { + config.AppBucketCapacity = defaultAppBucketCapacity + } + if config.AppRefillTokensPerSecond <= 0 { + config.AppRefillTokensPerSecond = defaultAppRefillTokensPerSecond + } + if config.MaxInFlight <= 0 { + config.MaxInFlight = defaultMaxInFlight + } + if config.InFlightLease <= 0 { + config.InFlightLease = defaultInFlightLease + } + if config.RedisOperationTimeout <= 0 { + config.RedisOperationTimeout = defaultRedisOperationTimeout + } + return config +} + +// NewRedisClient 建立并探测容量保护使用的 Redis 连接。 +// ContextTimeoutEnabled 必须开启,否则 go-redis 可能只依赖较长 socket timeout,使 handler +// 的 500ms fail-closed 预算和 Release 的 1s 回收预算失效。 +func NewRedisClient(ctx context.Context, addr string, password string, db int) (*redis.Client, error) { + client := redis.NewClient(redisClientOptions(addr, password, db)) + if err := client.Ping(ctx).Err(); err != nil { + _ = client.Close() + return nil, err + } + return client, nil +} + +func redisClientOptions(addr string, password string, db int) *redis.Options { + return &redis.Options{ + Addr: strings.TrimSpace(addr), + Password: password, + DB: db, + MaxRetries: 1, + MinRetryBackoff: redisMinRetryBackoff, + MaxRetryBackoff: redisMaxRetryBackoff, + DialTimeout: redisDialTimeout, + DialerRetries: 1, + ReadTimeout: redisReadTimeout, + WriteTimeout: redisWriteTimeout, + PoolTimeout: redisPoolTimeout, + ContextTimeoutEnabled: true, + } +} + +// NewRedisLimiter 创建生产使用的分布式 limiter。 +func NewRedisLimiter(client *redis.Client, config Config) *RedisLimiter { + return newRedisLimiter(goRedisScriptRunner{client: client}, config) +} + +func newRedisLimiter(runner redisScriptRunner, config Config) *RedisLimiter { + return &RedisLimiter{runner: runner, config: Normalize(config)} +} + +// Acquire 在一个独立短 deadline 内按 user→room→app 顺序保守扣减。 +// 用户并发先行可挡住低成本全局 DoS;后层失败会立即释放用户 lease,但不会返还已扣令牌。 +func (l *RedisLimiter) Acquire(ctx context.Context, input Input) (Decision, error) { + if l == nil || l.runner == nil { + return Decision{}, errors.New("gift capacity limiter is not configured") + } + appCode := strings.ToLower(strings.TrimSpace(input.AppCode)) + if appCode == "" || input.UserID <= 0 || strings.TrimSpace(input.RoomID) == "" || strings.TrimSpace(input.RequestID) == "" || input.Weight <= 0 { + return Decision{}, errors.New("gift capacity limiter input is invalid") + } + config := Normalize(l.config) + if input.Weight > config.UserBucketCapacity || input.Weight > config.RoomBucketCapacity || input.Weight > config.AppBucketCapacity { + // handler 会先用 max_targets_per_request 返回 4xx;到达这里说明容量配置与协议上限漂移, + // 必须 fail-closed,而不是返回一个永远无法成功的 429 重试循环。 + return Decision{}, errors.New("gift target weight exceeds configured bucket capacity") + } + if ctx == nil { + ctx = context.Background() + } + operationCtx, cancel := context.WithTimeout(ctx, config.RedisOperationTimeout) + defer cancel() + + keys := buildRedisScopeKeys(config.KeyPrefix, appCode, input) + userPolicy := bucketPolicy{capacity: config.UserBucketCapacity, refillPerSecond: config.UserRefillTokensPerSecond} + result, err := l.runner.evalSlice(operationCtx, redisUserAcquireScript, []string{keys.userBucket, keys.inFlight}, + userPolicy.capacity, + refillPerMillisecond(userPolicy.refillPerSecond), + input.Weight, + config.MaxInFlight, + config.InFlightLease.Milliseconds(), + keys.leaseMember, + bucketTTL(userPolicy, config.InFlightLease).Milliseconds(), + ) + if err != nil { + return Decision{}, err + } + userDecision, err := decodeDecision(result) + if err != nil { + // Lua 已返回但协议解码失败时执行幂等 ZREM;脚本可能已经写入 lease,不能等到 TTL 才恢复。 + l.releaseAcquiredLease(operationCtx, Lease{inFlightKey: keys.inFlight, member: keys.leaseMember}) + return Decision{}, err + } + if !userDecision.Allowed { + // 并发已满或用户桶不足时尚未触达 room/app key,因此攻击者不能靠必拒请求耗尽共享额度。 + return userDecision, nil + } + lease := Lease{inFlightKey: keys.inFlight, member: keys.leaseMember} + + layers := []struct { + key string + policy bucketPolicy + }{ + {key: keys.roomBucket, policy: bucketPolicy{capacity: config.RoomBucketCapacity, refillPerSecond: config.RoomRefillTokensPerSecond}}, + {key: keys.appBucket, policy: bucketPolicy{capacity: config.AppBucketCapacity, refillPerSecond: config.AppRefillTokensPerSecond}}, + } + for _, layer := range layers { + decision, err := l.consumeBucket(operationCtx, layer.key, layer.policy, input.Weight) + if err != nil || !decision.Allowed { + // 使用同一个短 deadline 立即释放用户 lease;deadline 已耗尽时释放可能失败, + // 但 ZSET score 的 InFlightLease 仍会兜底回收,绝不会永久锁死用户。 + l.releaseAcquiredLease(operationCtx, lease) + return decision, err + } + } + userDecision.Lease = lease + return userDecision, nil +} + +func (l *RedisLimiter) consumeBucket(ctx context.Context, key string, policy bucketPolicy, weight int) (Decision, error) { + result, err := l.runner.evalSlice(ctx, redisTokenBucketScript, []string{key}, + policy.capacity, + refillPerMillisecond(policy.refillPerSecond), + weight, + bucketTTL(policy, 0).Milliseconds(), + ) + if err != nil { + return Decision{}, err + } + return decodeDecision(result) +} + +func (l *RedisLimiter) releaseAcquiredLease(ctx context.Context, lease Lease) { + if lease.inFlightKey == "" || lease.member == "" { + return + } + // 不调用公开 Release,避免给一次 Acquire 额外再增加 1s;补偿动作必须服从 Acquire 的 + // 共同短预算。失败时租约 TTL 是安全兜底,用户 token 则有意不返还。 + _, _ = l.runner.evalSlice(ctx, redisReleaseScript, []string{lease.inFlightKey}, lease.member) +} + +// Release 始终再包一层 1s deadline。handler 使用与客户端取消解耦的 background context, +// go-redis 又启用 ContextTimeoutEnabled,因此慢 Redis 不会把请求收尾永久挂住。 +func (l *RedisLimiter) Release(ctx context.Context, lease Lease) error { + if l == nil || l.runner == nil || lease.inFlightKey == "" || lease.member == "" { + return nil + } + if ctx == nil { + ctx = context.Background() + } + releaseCtx, cancel := context.WithTimeout(ctx, redisReleaseTimeout) + defer cancel() + _, err := l.runner.evalSlice(releaseCtx, redisReleaseScript, []string{lease.inFlightKey}, lease.member) + return err +} + +func (r goRedisScriptRunner) evalSlice(ctx context.Context, script string, keys []string, args ...any) ([]any, error) { + return r.client.Eval(ctx, script, keys, args...).Slice() +} + +func buildRedisScopeKeys(prefix string, appCode string, input Input) redisScopeKeys { + appPart := boundedHash(appCode) + roomPart := boundedHash(appCode + ":" + strings.TrimSpace(input.RoomID)) + userPart := boundedHash(appCode + ":" + strconv.FormatInt(input.UserID, 10)) + member := boundedHash(input.RequestID) + // 每个聚合域拥有自己的 hash tag;只有用户桶与 in-flight ZSET 共享 user tag,因而只有 + // 这两个 key 会放入同一 Lua。App/room/user 三层绝不跨 slot 伪原子。 + return redisScopeKeys{ + appBucket: prefix + "{app:" + appPart + "}:bucket", + roomBucket: prefix + "{room:" + roomPart + "}:bucket", + userBucket: prefix + "{user:" + userPart + "}:bucket", + inFlight: prefix + "{user:" + userPart + "}:inflight", + leaseMember: member, + } +} + +func bucketTTL(policy bucketPolicy, minimum time.Duration) time.Duration { + ttl := time.Duration(math.Ceil(float64(policy.capacity)/policy.refillPerSecond*2)) * time.Second + if ttl < time.Second { + ttl = time.Second + } + if ttl < minimum { + ttl = minimum + } + return ttl +} + +func refillPerMillisecond(tokensPerSecond float64) string { + return strconv.FormatFloat(tokensPerSecond/1000, 'f', -1, 64) +} + +func decodeDecision(result []any) (Decision, error) { + if len(result) != 2 { + return Decision{}, fmt.Errorf("gift capacity limiter returned %d values", len(result)) + } + allowed, err := redisInt64(result[0]) + if err != nil { + return Decision{}, fmt.Errorf("decode gift capacity allowed: %w", err) + } + retryAfterMS, err := redisInt64(result[1]) + if err != nil { + return Decision{}, fmt.Errorf("decode gift capacity retry_after_ms: %w", err) + } + if allowed == 1 { + return Decision{Allowed: true}, nil + } + if retryAfterMS < 1 { + retryAfterMS = 1 + } + return Decision{RetryAfter: time.Duration(retryAfterMS) * time.Millisecond}, nil +} + +func boundedHash(value string) string { + sum := sha256.Sum256([]byte(strings.TrimSpace(value))) + return hex.EncodeToString(sum[:16]) +} + +func redisInt64(value any) (int64, error) { + switch typed := value.(type) { + case int64: + return typed, nil + case int: + return int64(typed), nil + case string: + return strconv.ParseInt(typed, 10, 64) + case []byte: + return strconv.ParseInt(string(typed), 10, 64) + default: + return 0, fmt.Errorf("unsupported redis integer type %T", value) + } +} diff --git a/services/gateway-service/internal/transport/http/giftlimit/limiter_integration_test.go b/services/gateway-service/internal/transport/http/giftlimit/limiter_integration_test.go new file mode 100644 index 00000000..7bb216b0 --- /dev/null +++ b/services/gateway-service/internal/transport/http/giftlimit/limiter_integration_test.go @@ -0,0 +1,135 @@ +package giftlimit + +import ( + "context" + "fmt" + "os" + "testing" + "time" +) + +// TestRedisLimiterIntegration 在真实 Redis 上验证三层聚合、加权扣减和租约时序。 +// 常规 CI 没有 Redis 时跳过;本地用 GIFT_LIMIT_REDIS_ADDR=127.0.0.1:13379 显式执行。 +func TestRedisLimiterIntegration(t *testing.T) { + addr := os.Getenv("GIFT_LIMIT_REDIS_ADDR") + if addr == "" { + t.Skip("GIFT_LIMIT_REDIS_ADDR is not configured") + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + client, err := NewRedisClient(ctx, addr, os.Getenv("GIFT_LIMIT_REDIS_PASSWORD"), 0) + if err != nil { + t.Fatalf("connect Redis: %v", err) + } + defer client.Close() + + prefix := fmt.Sprintf("test:gift:capacity:%d:", time.Now().UnixNano()) + t.Cleanup(func() { + cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cleanupCancel() + var cursor uint64 + for { + keys, next, scanErr := client.Scan(cleanupCtx, cursor, prefix+"*", 100).Result() + if scanErr != nil { + return + } + if len(keys) > 0 { + _ = client.Del(cleanupCtx, keys...).Err() + } + cursor = next + if cursor == 0 { + return + } + } + }) + + // 小容量专门让测试在无等待情况下依次触发 App、房间和用户层;每个场景使用不同 + // app_code,避免前一场景的保守扣减影响后一场景。 + limiter := NewRedisLimiter(client, Config{ + Enabled: true, + KeyPrefix: prefix, + UserBucketCapacity: 2, + UserRefillTokensPerSecond: 0.1, + RoomBucketCapacity: 2, + RoomRefillTokensPerSecond: 0.1, + AppBucketCapacity: 3, + AppRefillTokensPerSecond: 0.1, + MaxInFlight: 1, + InFlightLease: 300 * time.Millisecond, + RedisOperationTimeout: 500 * time.Millisecond, + }) + + t.Run("app aggregate", func(t *testing.T) { + first := mustAcquire(t, limiter, Input{AppCode: "app-total", UserID: 1, RoomID: "room-a", RequestID: "app-1", Weight: 2}) + mustRelease(t, limiter, first.Lease) + second, err := limiter.Acquire(context.Background(), Input{AppCode: "app-total", UserID: 2, RoomID: "room-b", RequestID: "app-2", Weight: 2}) + if err != nil || second.Allowed || second.RetryAfter <= 0 { + t.Fatalf("app aggregate must reject cross-room users: decision=%+v err=%v", second, err) + } + }) + + t.Run("room aggregate across users", func(t *testing.T) { + first := mustAcquire(t, limiter, Input{AppCode: "room-total", UserID: 11, RoomID: "same-room", RequestID: "room-1", Weight: 1}) + mustRelease(t, limiter, first.Lease) + second := mustAcquire(t, limiter, Input{AppCode: "room-total", UserID: 12, RoomID: "same-room", RequestID: "room-2", Weight: 1}) + mustRelease(t, limiter, second.Lease) + third, err := limiter.Acquire(context.Background(), Input{AppCode: "room-total", UserID: 13, RoomID: "same-room", RequestID: "room-3", Weight: 1}) + if err != nil || third.Allowed || third.RetryAfter <= 0 { + t.Fatalf("room bucket must aggregate different users: decision=%+v err=%v", third, err) + } + // room 层拒绝后用户 lease 必须立即释放;用户 token 有意保留扣减,因此剩余的一个 + // token 仍可在另一个未满房间使用。 + afterRoomDenial := mustAcquire(t, limiter, Input{AppCode: "room-total", UserID: 13, RoomID: "other-room", RequestID: "room-4", Weight: 1}) + mustRelease(t, limiter, afterRoomDenial.Lease) + }) + + t.Run("user weighted", func(t *testing.T) { + first := mustAcquire(t, limiter, Input{AppCode: "user-total", UserID: 21, RoomID: "room-a", RequestID: "user-1", Weight: 1}) + mustRelease(t, limiter, first.Lease) + weighted, err := limiter.Acquire(context.Background(), Input{AppCode: "user-total", UserID: 21, RoomID: "room-b", RequestID: "user-2", Weight: 2}) + if err != nil || weighted.Allowed || weighted.RetryAfter < time.Second { + t.Fatalf("user bucket must retain weighted consumption across rooms: decision=%+v err=%v", weighted, err) + } + }) + + t.Run("in-flight lease does not expire early", func(t *testing.T) { + leaseLimiter := NewRedisLimiter(client, Config{ + KeyPrefix: prefix, + UserBucketCapacity: 100, + UserRefillTokensPerSecond: 100, + RoomBucketCapacity: 100, + RoomRefillTokensPerSecond: 100, + AppBucketCapacity: 100, + AppRefillTokensPerSecond: 100, + MaxInFlight: 1, + InFlightLease: 300 * time.Millisecond, + RedisOperationTimeout: 500 * time.Millisecond, + }) + first := mustAcquire(t, leaseLimiter, Input{AppCode: "lease", UserID: 31, RoomID: "room-a", RequestID: "lease-1", Weight: 1}) + _ = first // 故意不释放,模拟 gateway 在 RPC 期间仍持有槽位。 + time.Sleep(100 * time.Millisecond) + beforeExpiry, err := leaseLimiter.Acquire(context.Background(), Input{AppCode: "lease", UserID: 31, RoomID: "room-b", RequestID: "lease-2", Weight: 1}) + if err != nil || beforeExpiry.Allowed || beforeExpiry.RetryAfter < 100*time.Millisecond { + t.Fatalf("lease expired before configured boundary: decision=%+v err=%v", beforeExpiry, err) + } + time.Sleep(230 * time.Millisecond) + afterExpiry := mustAcquire(t, leaseLimiter, Input{AppCode: "lease", UserID: 31, RoomID: "room-c", RequestID: "lease-3", Weight: 1}) + mustRelease(t, leaseLimiter, afterExpiry.Lease) + }) +} + +func mustAcquire(t *testing.T, limiter *RedisLimiter, input Input) Decision { + t.Helper() + decision, err := limiter.Acquire(context.Background(), input) + if err != nil || !decision.Allowed { + t.Fatalf("Acquire mismatch: input=%+v decision=%+v err=%v", input, decision, err) + } + return decision +} + +func mustRelease(t *testing.T, limiter *RedisLimiter, lease Lease) { + t.Helper() + if err := limiter.Release(context.Background(), lease); err != nil { + t.Fatalf("Release failed: %v", err) + } +} diff --git a/services/gateway-service/internal/transport/http/giftlimit/limiter_test.go b/services/gateway-service/internal/transport/http/giftlimit/limiter_test.go new file mode 100644 index 00000000..e8160872 --- /dev/null +++ b/services/gateway-service/internal/transport/http/giftlimit/limiter_test.go @@ -0,0 +1,254 @@ +package giftlimit + +import ( + "context" + "errors" + "reflect" + "strings" + "testing" + "time" +) + +type redisScriptCall struct { + script string + keys []string + args []any +} + +type fakeRedisScriptRunner struct { + result []any + results [][]any + errors []error + calls []redisScriptCall +} + +func (f *fakeRedisScriptRunner) evalSlice(_ context.Context, script string, keys []string, args ...any) ([]any, error) { + f.calls = append(f.calls, redisScriptCall{ + script: script, + keys: append([]string(nil), keys...), + args: append([]any(nil), args...), + }) + if len(f.errors) > 0 { + err := f.errors[0] + f.errors = f.errors[1:] + if err != nil { + return nil, err + } + } + if len(f.results) > 0 { + result := f.results[0] + f.results = f.results[1:] + return result, nil + } + return f.result, nil +} + +type blockingRedisScriptRunner struct{} + +func (blockingRedisScriptRunner) evalSlice(ctx context.Context, _ string, _ []string, _ ...any) ([]any, error) { + <-ctx.Done() + return nil, ctx.Err() +} + +func TestRedisClientOptionsEnforceBoundedContextAwareIO(t *testing.T) { + options := redisClientOptions(" 127.0.0.1:6379 ", "secret", 3) + if options.Addr != "127.0.0.1:6379" || options.Password != "secret" || options.DB != 3 { + t.Fatalf("Redis identity options mismatch: %+v", options) + } + if !options.ContextTimeoutEnabled || options.MaxRetries != 1 || options.DialerRetries != 1 { + t.Fatalf("Redis timeout/retry guard mismatch: %+v", options) + } + if options.DialTimeout != redisDialTimeout || options.ReadTimeout != redisReadTimeout || options.WriteTimeout != redisWriteTimeout || options.PoolTimeout != redisPoolTimeout { + t.Fatalf("Redis socket/pool timeouts mismatch: %+v", options) + } +} + +func TestRedisLimiterAcquireUsesThreeWeightedClusterSafeLayers(t *testing.T) { + runner := &fakeRedisScriptRunner{result: []any{int64(1), int64(0)}} + limiter := newRedisLimiter(runner, Config{ + KeyPrefix: "test:gift:", + UserBucketCapacity: 40, + UserRefillTokensPerSecond: 15, + RoomBucketCapacity: 120, + RoomRefillTokensPerSecond: 40, + AppBucketCapacity: 1000, + AppRefillTokensPerSecond: 500, + MaxInFlight: 2, + InFlightLease: 10 * time.Second, + RedisOperationTimeout: time.Second, + }) + + decision, err := limiter.Acquire(context.Background(), Input{ + AppCode: "LALU", + UserID: 42, + RoomID: "room:unsafe/value", + RequestID: "req-1", + Weight: 13, + }) + if err != nil { + t.Fatalf("Acquire failed: %v", err) + } + if !decision.Allowed || decision.Lease.inFlightKey == "" || decision.Lease.member == "" { + t.Fatalf("allowed decision must include release lease: %+v", decision) + } + if len(runner.calls) != 3 { + t.Fatalf("Acquire must execute app, room and user layers: calls=%d", len(runner.calls)) + } + if runner.calls[0].script != redisUserAcquireScript || runner.calls[1].script != redisTokenBucketScript || runner.calls[2].script != redisTokenBucketScript { + t.Fatalf("layer script order mismatch: %+v", runner.calls) + } + if len(runner.calls[0].keys) != 2 || len(runner.calls[1].keys) != 1 || len(runner.calls[2].keys) != 1 { + t.Fatalf("each Lua must stay inside one aggregation slot: %+v", runner.calls) + } + appTag := redisHashTag(runner.calls[2].keys[0]) + roomTag := redisHashTag(runner.calls[1].keys[0]) + userTag := redisHashTag(runner.calls[0].keys[0]) + if appTag == "" || roomTag == "" || userTag == "" || appTag == roomTag || appTag == userTag || roomTag == userTag { + t.Fatalf("aggregation layers must use distinct hash slots: app=%q room=%q user=%q", appTag, roomTag, userTag) + } + if redisHashTag(runner.calls[0].keys[1]) != userTag { + t.Fatalf("user bucket and in-flight lease must share one hash slot: %v", runner.calls[0].keys) + } + for _, call := range runner.calls { + for _, key := range call.keys { + if strings.Contains(key, "room:unsafe") || strings.Contains(key, "LALU") { + t.Fatalf("untrusted scope leaked into Redis key: %q", key) + } + } + if got := call.args[2]; got != 13 { + t.Fatalf("target_count must weight every layer: got=%v call=%+v", got, call) + } + } + if runner.calls[0].args[3] != 2 || runner.calls[0].args[4] != int64(10_000) { + t.Fatalf("user concurrency/lease args mismatch: %v", runner.calls[0].args) + } +} + +func TestRedisLimiterStopsAtRejectedLayerWithoutUnsafeRollback(t *testing.T) { + tests := []struct { + name string + results [][]any + wantCalls int + wantRetryMS int64 + }{ + {name: "user", results: [][]any{{int64(0), int64(101)}}, wantCalls: 1, wantRetryMS: 101}, + {name: "room", results: [][]any{{int64(1), int64(0)}, {int64(0), int64(202)}, {int64(1)}}, wantCalls: 3, wantRetryMS: 202}, + {name: "app", results: [][]any{{int64(1), int64(0)}, {int64(1), int64(0)}, {int64(0), int64(303)}, {int64(1)}}, wantCalls: 4, wantRetryMS: 303}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + runner := &fakeRedisScriptRunner{results: test.results} + limiter := newRedisLimiter(runner, Config{}) + decision, err := limiter.Acquire(context.Background(), Input{AppCode: "lalu", UserID: 42, RoomID: "room-1", RequestID: "req-deny", Weight: 1}) + if err != nil { + t.Fatalf("Acquire failed: %v", err) + } + if decision.Allowed || decision.RetryAfter != time.Duration(test.wantRetryMS)*time.Millisecond || len(runner.calls) != test.wantCalls { + t.Fatalf("denied layer mismatch: decision=%+v calls=%d", decision, len(runner.calls)) + } + if test.name != "user" && runner.calls[len(runner.calls)-1].script != redisReleaseScript { + t.Fatalf("shared-layer rejection must immediately release user lease: calls=%+v", runner.calls) + } + }) + } +} + +func TestRedisLimiterInFlightDenialDoesNotConsumeRoomOrApp(t *testing.T) { + runner := &fakeRedisScriptRunner{result: []any{int64(0), int64(250)}} + limiter := newRedisLimiter(runner, Config{}) + decision, err := limiter.Acquire(context.Background(), Input{AppCode: "lalu", UserID: 42, RoomID: "room-1", RequestID: "req-concurrent", Weight: 13}) + if err != nil || decision.Allowed || decision.RetryAfter != 250*time.Millisecond { + t.Fatalf("user in-flight denial mismatch: decision=%+v err=%v", decision, err) + } + if len(runner.calls) != 1 || runner.calls[0].script != redisUserAcquireScript || len(runner.calls[0].keys) != 2 { + t.Fatalf("in-flight denial must stop before room/app: calls=%+v", runner.calls) + } +} + +func TestRedisScopeKeysAggregateDifferentUsersInSameRoom(t *testing.T) { + left := buildRedisScopeKeys("test:", "lalu", Input{UserID: 42, RoomID: "room-1", RequestID: "req-left"}) + right := buildRedisScopeKeys("test:", "lalu", Input{UserID: 43, RoomID: "room-1", RequestID: "req-right"}) + if left.appBucket != right.appBucket || left.roomBucket != right.roomBucket { + t.Fatalf("different users in one room must share app and room buckets: left=%+v right=%+v", left, right) + } + if left.userBucket == right.userBucket || left.inFlight == right.inFlight { + t.Fatalf("different users must keep independent user buckets and leases: left=%+v right=%+v", left, right) + } +} + +func TestRedisLimiterReleaseUsesLeaseIdentity(t *testing.T) { + runner := &fakeRedisScriptRunner{result: []any{int64(1), int64(0)}} + limiter := newRedisLimiter(runner, Config{}) + decision, err := limiter.Acquire(context.Background(), Input{AppCode: "lalu", UserID: 42, RoomID: "room-1", RequestID: "req-release", Weight: 1}) + if err != nil { + t.Fatalf("Acquire failed: %v", err) + } + wantKey := decision.Lease.inFlightKey + wantMember := decision.Lease.member + if err := limiter.Release(context.Background(), decision.Lease); err != nil { + t.Fatalf("Release failed: %v", err) + } + lastCall := runner.calls[len(runner.calls)-1] + if !reflect.DeepEqual(lastCall.keys, []string{wantKey}) || !reflect.DeepEqual(lastCall.args, []any{wantMember}) || lastCall.script != redisReleaseScript { + t.Fatalf("release identity mismatch: %+v", lastCall) + } +} + +func TestRedisLimiterAcquireDeadlineFailsClosedQuickly(t *testing.T) { + limiter := newRedisLimiter(blockingRedisScriptRunner{}, Config{RedisOperationTimeout: 30 * time.Millisecond}) + startedAt := time.Now() + _, err := limiter.Acquire(context.Background(), Input{AppCode: "lalu", UserID: 42, RoomID: "room-1", RequestID: "req-slow", Weight: 1}) + elapsed := time.Since(startedAt) + if !errors.Is(err, context.DeadlineExceeded) { + t.Fatalf("blocked Redis must fail closed with deadline: %v", err) + } + if elapsed < 20*time.Millisecond || elapsed > 200*time.Millisecond { + t.Fatalf("blocked Redis escaped short operation budget: elapsed=%s", elapsed) + } +} + +func TestRedisLimiterReleaseDeadlineIsBounded(t *testing.T) { + limiter := newRedisLimiter(blockingRedisScriptRunner{}, Config{}) + startedAt := time.Now() + err := limiter.Release(context.Background(), Lease{inFlightKey: "test:{user}:inflight", member: "req"}) + elapsed := time.Since(startedAt) + if !errors.Is(err, context.DeadlineExceeded) { + t.Fatalf("blocked release must honor deadline: %v", err) + } + if elapsed < 900*time.Millisecond || elapsed > 1500*time.Millisecond { + t.Fatalf("release deadline is not the configured 1s bound: elapsed=%s", elapsed) + } +} + +func TestRedisLimiterPropagatesLayerAndProtocolErrors(t *testing.T) { + wantErr := errors.New("redis unavailable") + runner := &fakeRedisScriptRunner{result: []any{int64(1), int64(0)}, errors: []error{nil, wantErr, nil}} + limiter := newRedisLimiter(runner, Config{}) + input := Input{AppCode: "lalu", UserID: 42, RoomID: "room-1", RequestID: "req-error", Weight: 1} + if _, err := limiter.Acquire(context.Background(), input); !errors.Is(err, wantErr) || len(runner.calls) != 3 || runner.calls[2].script != redisReleaseScript { + t.Fatalf("room-layer backend error mismatch: err=%v calls=%d", err, len(runner.calls)) + } + + limiter = newRedisLimiter(&fakeRedisScriptRunner{result: []any{int64(1)}}, Config{}) + if _, err := limiter.Acquire(context.Background(), input); err == nil { + t.Fatal("invalid Lua response must fail closed") + } +} + +func TestRedisLimiterRejectsWeightAboveConfiguredCapacity(t *testing.T) { + runner := &fakeRedisScriptRunner{result: []any{int64(1), int64(0)}} + limiter := newRedisLimiter(runner, Config{UserBucketCapacity: 2}) + _, err := limiter.Acquire(context.Background(), Input{AppCode: "lalu", UserID: 42, RoomID: "room-1", RequestID: "req-heavy", Weight: 3}) + if err == nil || len(runner.calls) != 0 { + t.Fatalf("impossible weight must fail before Redis: err=%v calls=%d", err, len(runner.calls)) + } +} + +func redisHashTag(key string) string { + start := strings.IndexByte(key, '{') + end := strings.IndexByte(key, '}') + if start < 0 || end <= start { + return "" + } + return key[start+1 : end] +} diff --git a/services/gateway-service/internal/transport/http/handler.go b/services/gateway-service/internal/transport/http/handler.go index 97c5eb51..3cc451b1 100644 --- a/services/gateway-service/internal/transport/http/handler.go +++ b/services/gateway-service/internal/transport/http/handler.go @@ -15,6 +15,7 @@ import ( "hyapp/services/gateway-service/internal/client" "hyapp/services/gateway-service/internal/financewithdrawal" "hyapp/services/gateway-service/internal/transport/http/appapi" + "hyapp/services/gateway-service/internal/transport/http/giftlimit" "hyapp/services/gateway-service/internal/transport/http/httpkit" userv1 "hyapp.local/api/proto/user/v1" @@ -69,6 +70,12 @@ type Handler struct { objectUploader ObjectUploader authRateLimitConfig AuthRateLimitConfig authRateLimiter publicAuthRateLimiter + giftCapacityEnabled bool + giftCapacityLimiter giftlimit.Limiter + giftRequestTimeout time.Duration + giftMaxTargets int + giftMaxCount int + giftMaxUnits int loginRiskConfig LoginRiskConfig loginRiskCache loginRiskCache } @@ -174,6 +181,33 @@ func (h *Handler) SetObjectUploader(uploader ObjectUploader) { h.objectUploader = uploader } +// SetGiftCapacityProtection 注入跨 gateway 节点共享的送礼容量保护和 HTTP 编排边界。 +// enabled=true 且 limiter=nil 会在请求路径 fail-closed,避免配置漂移时静默失去保护。 +func (h *Handler) SetGiftCapacityProtection(enabled bool, requestTimeout time.Duration, maxTargets int, limiter giftlimit.Limiter) { + h.giftCapacityEnabled = enabled + h.giftCapacityLimiter = limiter + h.giftRequestTimeout = requestTimeout + h.giftMaxTargets = maxTargets + if h.giftMaxCount <= 0 { + h.giftMaxCount = 999 + } + if h.giftMaxUnits <= 0 { + h.giftMaxUnits = 5_000 + } +} + +// SetGiftRequestLimits keeps protocol hard limits separate from Redis availability. Even when the +// distributed limiter is intentionally disabled, malformed or explosive gift_count*target_count +// requests still fail before profile, wallet and Room Cell work begins. +func (h *Handler) SetGiftRequestLimits(maxCount int, maxUnits int) { + if maxCount > 0 { + h.giftMaxCount = maxCount + } + if maxUnits > 0 { + h.giftMaxUnits = maxUnits + } +} + // SetRoomQueryClient 注入 room-service 读模型查询 client。 func (h *Handler) SetRoomQueryClient(roomQueryClient client.RoomQueryClient) { h.roomQueryClient = roomQueryClient diff --git a/services/gateway-service/internal/transport/http/httpkit/response.go b/services/gateway-service/internal/transport/http/httpkit/response.go index bc28cf72..419fe06b 100644 --- a/services/gateway-service/internal/transport/http/httpkit/response.go +++ b/services/gateway-service/internal/transport/http/httpkit/response.go @@ -265,6 +265,11 @@ func UserMetaWithLoginContext(request *http.Request, deviceID string, platform s Platform: strings.ToLower(strings.TrimSpace(platform)), Language: strings.TrimSpace(language), Timezone: strings.TrimSpace(timezone), + // 设备资料头由 App 全局注入到每个请求;user-service 只在认证成功后把它们聚合进 user_devices。 + DeviceModel: FirstHeader(request, "X-Device-Model"), + DeviceManufacturer: FirstHeader(request, "X-Device-Manufacturer"), + OsVersion: FirstHeader(request, "X-OS-Version"), + Imei: FirstHeader(request, "X-Device-IMEI"), } } diff --git a/services/gateway-service/internal/transport/http/httproutes/router.go b/services/gateway-service/internal/transport/http/httproutes/router.go index 0edcba8b..8f3acdb8 100644 --- a/services/gateway-service/internal/transport/http/httproutes/router.go +++ b/services/gateway-service/internal/transport/http/httproutes/router.go @@ -6,7 +6,10 @@ import ( "hyapp/services/gateway-service/internal/transport/http/httpkit" ) -const APIV1Prefix = "/api/v1" +const ( + APIV1Prefix = "/api/v1" + APIV2Prefix = "/api/v2" +) type Wrapper func(http.HandlerFunc) http.Handler @@ -87,8 +90,10 @@ type UserHandlers struct { ListAgencyRooms http.HandlerFunc UpdateAgencyProfile http.HandlerFunc GetHostCenterAgency http.HandlerFunc + GetHostCenterStats http.HandlerFunc GetHostCenterPlatformPolicy http.HandlerFunc GetAgencyCenterOverview http.HandlerFunc + GetAgencyCenterStats http.HandlerFunc GetAgencyCenterPlatformPolicy http.HandlerFunc ListAgencyCenterHosts http.HandlerFunc ListAgencyCenterApplications http.HandlerFunc @@ -101,6 +106,7 @@ type UserHandlers struct { InviteBDLeaderBD http.HandlerFunc InviteBDLeaderAgency http.HandlerFunc GetBDCenterOverview http.HandlerFunc + GetBDCenterStats http.HandlerFunc ListBDCenterAgencies http.HandlerFunc InviteBDAgency http.HandlerFunc ListMyRoleInvitations http.HandlerFunc @@ -154,6 +160,7 @@ type ManagerHandlers struct { type RoomHandlers struct { GetMyRoom http.HandlerFunc ListRoomFeeds http.HandlerFunc + ListRoomFilters http.HandlerFunc ListRooms http.HandlerFunc GetCurrentRoom http.HandlerFunc GetRoomSnapshot http.HandlerFunc @@ -190,6 +197,7 @@ type RoomHandlers struct { UnbanUser http.HandlerFunc SendGift http.HandlerFunc SendGiftBatch http.HandlerFunc + SendGiftV2 http.HandlerFunc } type MessageHandlers struct { @@ -282,6 +290,7 @@ type WalletHandlers struct { WithdrawSalaryWallet http.HandlerFunc GetPointWalletOverview http.HandlerFunc WithdrawPointWallet http.HandlerFunc + TransferPointWalletToCoinSeller http.HandlerFunc GetRedPacketConfig http.HandlerFunc ListRoomRedPackets http.HandlerFunc CreateRoomRedPacket http.HandlerFunc @@ -298,9 +307,16 @@ type WalletHandlers struct { } type VIPHandlers struct { - GetMyVIP http.HandlerFunc - ListVIPPackages http.HandlerFunc - PurchaseVIP http.HandlerFunc + GetMyVIP http.HandlerFunc + ListVIPPackages http.HandlerFunc + PurchaseVIP http.HandlerFunc + EquipVIPTrialCard http.HandlerFunc + UnequipVIPTrialCard http.HandlerFunc + TriggerOnlineNotice http.HandlerFunc + HandleVIPSettings http.HandlerFunc + GetCurrentCoinRebate http.HandlerFunc + ListCoinRebateStatuses http.HandlerFunc + ClaimCoinRebate http.HandlerFunc } type GameHandlers struct { @@ -378,6 +394,10 @@ func (r routes) profile(path string, method string, next http.HandlerFunc) { r.register(path, method, r.config.ProfileWrap, next) } +func (r routes) profileV2(path string, method string, next http.HandlerFunc) { + r.registerRaw(APIV2Prefix+path, method, r.config.ProfileWrap, next) +} + func (r routes) register(path string, method string, wrap Wrapper, next http.HandlerFunc) { r.registerRaw(APIV1Prefix+path, method, wrap, next) } @@ -454,8 +474,10 @@ func (r routes) registerUserRoutes() { r.profile("/agencies/rooms", http.MethodGet, h.ListAgencyRooms) r.profile("/agencies/profile/update", http.MethodPost, h.UpdateAgencyProfile) r.profile("/host-center/agency", http.MethodGet, h.GetHostCenterAgency) + r.profile("/host-center/stats", http.MethodGet, h.GetHostCenterStats) r.profile("/host-center/platform-policy", http.MethodGet, h.GetHostCenterPlatformPolicy) r.profile("/agency-center/overview", http.MethodGet, h.GetAgencyCenterOverview) + r.profile("/agency-center/stats", http.MethodGet, h.GetAgencyCenterStats) r.profile("/agency-center/platform-policy", http.MethodGet, h.GetAgencyCenterPlatformPolicy) r.profile("/agency-center/hosts", http.MethodGet, h.ListAgencyCenterHosts) r.profile("/agency-center/applications", http.MethodGet, h.ListAgencyCenterApplications) @@ -468,6 +490,7 @@ func (r routes) registerUserRoutes() { r.profile("/bd-leader/invitations/bd", http.MethodPost, h.InviteBDLeaderBD) r.profile("/bd-leader/invitations/agency", http.MethodPost, h.InviteBDLeaderAgency) r.profile("/bd-center/overview", http.MethodGet, h.GetBDCenterOverview) + r.profile("/bd-center/stats", http.MethodGet, h.GetBDCenterStats) r.profile("/bd-center/agencies", http.MethodGet, h.ListBDCenterAgencies) r.profile("/bd/invitations/agency", http.MethodPost, h.InviteBDAgency) r.profile("/role-invitations", http.MethodGet, h.ListMyRoleInvitations) @@ -519,6 +542,7 @@ func (r routes) registerManagerRoutes() { func (r routes) registerRoomRoutes() { h := r.config.Room r.rootPublic("/share/rooms/{room_id}", http.MethodGet, h.GetRoomShareLanding) + r.profile("/rooms/filters", http.MethodGet, h.ListRoomFilters) r.profile("/rooms/me", http.MethodGet, h.GetMyRoom) r.profile("/rooms/feeds", http.MethodGet, h.ListRoomFeeds) r.profile("/rooms", http.MethodGet, h.ListRooms) @@ -556,6 +580,9 @@ func (r routes) registerRoomRoutes() { r.profile("/rooms/user/unban", http.MethodPost, h.UnbanUser) r.profile("/rooms/gift/send", http.MethodPost, h.SendGift) r.profile("/rooms/gift/batch-send", http.MethodPost, h.SendGiftBatch) + // V2 adds micro-batch identity and committed-result replay while the two V1 routes remain byte-level + // compatible for already released Flutter versions. + r.profileV2("/rooms/gift/send", http.MethodPost, h.SendGiftV2) } func (r routes) registerMessageRoutes() { @@ -670,6 +697,7 @@ func (r routes) registerWalletRoutes() { r.profile("/salary-wallet/withdraw", http.MethodPost, h.WithdrawSalaryWallet) r.profile("/point-wallet/overview", http.MethodGet, h.GetPointWalletOverview) r.profile("/point-wallet/withdraw", http.MethodPost, h.WithdrawPointWallet) + r.profile("/point-wallet/transfer-to-coin-seller", http.MethodPost, h.TransferPointWalletToCoinSeller) r.profile("/red-packets/config", http.MethodGet, h.GetRedPacketConfig) r.profile("/rooms/{room_id}/red-packets", http.MethodGet, h.ListRoomRedPackets) r.profile("/rooms/{room_id}/red-packets/create", http.MethodPost, h.CreateRoomRedPacket) @@ -691,6 +719,13 @@ func (r routes) registerVIPRoutes() { r.profile("/vip/me", http.MethodGet, h.GetMyVIP) r.profile("/vip/packages", http.MethodGet, h.ListVIPPackages) r.profile("/vip/purchase", http.MethodPost, h.PurchaseVIP) + r.profile("/vip/trial-cards/{entitlement_id}/equip", http.MethodPost, h.EquipVIPTrialCard) + r.profile("/vip/trial-cards/equipped", http.MethodDelete, h.UnequipVIPTrialCard) + r.profile("/vip/online-notice", http.MethodPost, h.TriggerOnlineNotice) + r.profile("/vip/settings", "", h.HandleVIPSettings) + r.profile("/vip/coin-rebates/current", http.MethodGet, h.GetCurrentCoinRebate) + r.profile("/vip/coin-rebates/statuses", http.MethodPost, h.ListCoinRebateStatuses) + r.profile("/vip/coin-rebates/{rebate_id}/claim", http.MethodPost, h.ClaimCoinRebate) } func (r routes) registerGameRoutes() { diff --git a/services/gateway-service/internal/transport/http/login_risk.go b/services/gateway-service/internal/transport/http/login_risk.go index b0fbde05..ec92b3a7 100644 --- a/services/gateway-service/internal/transport/http/login_risk.go +++ b/services/gateway-service/internal/transport/http/login_risk.go @@ -230,7 +230,7 @@ func (h *Handler) loginRiskIPWhitelisted(request *http.Request, input loginRiskI ctx, cancel := context.WithTimeout(request.Context(), 500*time.Millisecond) defer cancel() resp, err := h.userClient.CheckLoginRiskIPWhitelist(ctx, &userv1.CheckLoginRiskIPWhitelistRequest{ - Meta: authRequestMetaWithLoginContext(request, "", input.Platform, input.Language, input.Timezone), + Meta: authRequestMetaWithLoginContext(request, "", input.Platform, input.Language, input.Timezone, "", ""), ClientIp: ip, }) if err != nil { @@ -248,7 +248,7 @@ func (h *Handler) recordLoginBlocked(request *http.Request, input loginRiskInput ctx, cancel := context.WithTimeout(request.Context(), 500*time.Millisecond) defer cancel() _, err := h.userClient.RecordLoginBlocked(ctx, &userv1.RecordLoginBlockedRequest{ - Meta: authRequestMetaWithLoginContext(request, "", input.Platform, input.Language, input.Timezone), + Meta: authRequestMetaWithLoginContext(request, "", input.Platform, input.Language, input.Timezone, "", ""), Channel: input.Channel, Platform: input.Platform, Language: input.Language, diff --git a/services/gateway-service/internal/transport/http/manager_center_handler_test.go b/services/gateway-service/internal/transport/http/manager_center_handler_test.go index da3868e6..9beb7984 100644 --- a/services/gateway-service/internal/transport/http/manager_center_handler_test.go +++ b/services/gateway-service/internal/transport/http/manager_center_handler_test.go @@ -161,7 +161,7 @@ func TestBusinessUserLookupForwardsSceneAndActor(t *testing.T) { } } -func TestSearchManagerUsersDefaultsSameCountryAndAllowsAllForCountryTransfer(t *testing.T) { +func TestSearchManagerUsersDefaultsSameCountryForEveryAction(t *testing.T) { profileClient := &fakeUserProfileClient{ businessLookupResp: &userv1.BusinessUserLookupResponse{ Users: []*userv1.BusinessUserLookupItem{ @@ -298,18 +298,132 @@ func TestSearchManagerUsersDefaultsSameCountryAndAllowsAllForCountryTransfer(t * if err := json.NewDecoder(allRecorder.Body).Decode(&allEnvelope); err != nil { t.Fatalf("decode all-country response failed: %v", err) } - if allEnvelope.Data.Total != 2 || allEnvelope.Data.Items[1].UserID != "900002" { - t.Fatalf("country transfer search must include cross-country users: %+v", allEnvelope.Data) + if allEnvelope.Data.Total != 1 || allEnvelope.Data.Items[0].UserID != "900001" { + t.Fatalf("scope=all must not bypass manager data scope: %+v", allEnvelope.Data) } - if !hostClient.sawCapability("manager_transfer_user_country") { - t.Fatalf("scope=all search must check transfer-country capability, saw %#v", hostClient.capabilities) + if hostClient.sawCapability("manager_transfer_user_country") { + t.Fatalf("HTTP scope must not become a second authorization path, saw %#v", hostClient.capabilities) + } +} + +func TestSearchManagerUsersUsesResolvedGlobalPolicyForAllHuwaaActions(t *testing.T) { + profileClient := &fakeUserProfileClient{ + businessLookupResp: &userv1.BusinessUserLookupResponse{Users: []*userv1.BusinessUserLookupItem{ + {UserId: 900001, Status: userv1.UserStatus_USER_STATUS_ACTIVE}, + {UserId: 900002, Status: userv1.UserStatus_USER_STATUS_ACTIVE}, + }}, + usersByID: map[int64]*userv1.User{ + 42: {UserId: 42, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "US"}, + 900001: {UserId: 900001, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "US"}, + 900002: {UserId: 900002, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "CA"}, + }, + } + hostClient := &fakeUserHostClient{roleScopePolicyResp: &userv1.GetRoleScopePolicyResponse{Policy: &userv1.RoleScopePolicy{ + Scene: "manager_operations", BaseScope: "global", ExpandedScope: "global", RegionExpansionConfigurable: false, + }}} + router := newManagerCenterTestRouter(&fakeWalletClient{}, hostClient, profileClient) + + bdLeaderRequest := httptest.NewRequest(http.MethodGet, "/api/v1/manager-center/users/search?keyword=900&action=add-bd-leader", nil) + bdLeaderRequest.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "huwaa")) + bdLeaderRequest.Header.Set("X-Request-ID", "req-huwaa-manager-search-bdleader") + bdLeaderRecorder := httptest.NewRecorder() + router.ServeHTTP(bdLeaderRecorder, bdLeaderRequest) + + if bdLeaderRecorder.Code != http.StatusOK { + t.Fatalf("Huwaa BD Leader search status mismatch: got %d body=%s", bdLeaderRecorder.Code, bdLeaderRecorder.Body.String()) + } + var bdLeaderEnvelope struct { + Data struct { + Items []struct { + UserID string `json:"user_id"` + } `json:"items"` + Total int `json:"total"` + } `json:"data"` + } + if err := json.NewDecoder(bdLeaderRecorder.Body).Decode(&bdLeaderEnvelope); err != nil { + t.Fatalf("decode Huwaa BD Leader search response failed: %v", err) + } + if bdLeaderEnvelope.Data.Total != 2 || profileClient.lastBusinessLookup.GetScene() != "manager_add_bd_leader" || profileClient.lastBusinessLookup.GetMeta().GetAppCode() != "huwaa" { + t.Fatalf("Huwaa BD Leader search must keep app scope while including cross-country targets: data=%+v req=%+v", bdLeaderEnvelope.Data, profileClient.lastBusinessLookup) + } + + adminRequest := httptest.NewRequest(http.MethodGet, "/api/v1/manager-center/users/search?keyword=900&action=add-admin", nil) + adminRequest.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "huwaa")) + adminRequest.Header.Set("X-Request-ID", "req-huwaa-manager-search-admin") + adminRecorder := httptest.NewRecorder() + router.ServeHTTP(adminRecorder, adminRequest) + if adminRecorder.Code != http.StatusOK { + t.Fatalf("Huwaa admin search status mismatch: got %d body=%s", adminRecorder.Code, adminRecorder.Body.String()) + } + var adminEnvelope struct { + Data struct { + Total int `json:"total"` + } `json:"data"` + } + if err := json.NewDecoder(adminRecorder.Body).Decode(&adminEnvelope); err != nil { + t.Fatalf("decode Huwaa admin search response failed: %v", err) + } + if adminEnvelope.Data.Total != 2 { + t.Fatalf("Huwaa manager actions must all use global scope: %+v", adminEnvelope.Data) + } +} + +func TestSearchManagerUsersAppliesGeneralCrossRegionScopeToEveryAction(t *testing.T) { + profileClient := &fakeUserProfileClient{ + businessLookupResp: &userv1.BusinessUserLookupResponse{Users: []*userv1.BusinessUserLookupItem{ + {UserId: 900001, Status: userv1.UserStatus_USER_STATUS_ACTIVE}, + {UserId: 900002, Status: userv1.UserStatus_USER_STATUS_ACTIVE}, + {UserId: 900003, Status: userv1.UserStatus_USER_STATUS_ACTIVE}, + }}, + usersByID: map[int64]*userv1.User{ + 42: {UserId: 42, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "US", RegionId: 1001}, + 900001: {UserId: 900001, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "US", RegionId: 1001}, + 900002: {UserId: 900002, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "CA", RegionId: 1001}, + 900003: {UserId: 900003, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "MX", RegionId: 2002}, + }, + } + for _, testCase := range []struct { + name string + crossRegion bool + wantVisible int + }{ + {name: "closed keeps country", crossRegion: false, wantVisible: 1}, + {name: "open expands inside region", crossRegion: true, wantVisible: 2}, + } { + t.Run(testCase.name, func(t *testing.T) { + hostClient := &fakeUserHostClient{capabilityAllowed: map[string]bool{ + "manager_cross_region": testCase.crossRegion, + }} + router := newManagerCenterTestRouter(&fakeWalletClient{}, hostClient, profileClient) + for _, action := range []string{"send-avatar-frame", "send-vehicle", "send-badge", "send-vip", "update-user-level", "add-bd-leader", "add-admin", "add-superadmin", "block-user", "transfer-user-country"} { + request := httptest.NewRequest(http.MethodGet, "/api/v1/manager-center/users/search?keyword=900&scope=all&action="+action, nil) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + request.Header.Set("X-Request-ID", "req-manager-cross-region-search") + recorder := httptest.NewRecorder() + router.ServeHTTP(recorder, request) + if recorder.Code != http.StatusOK { + t.Fatalf("%s status mismatch: got %d body=%s", action, recorder.Code, recorder.Body.String()) + } + var envelope struct { + Data struct { + Total int `json:"total"` + } `json:"data"` + } + if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { + t.Fatalf("decode %s response: %v", action, err) + } + if envelope.Data.Total != testCase.wantVisible { + t.Fatalf("%s visible total = %d, want %d", action, envelope.Data.Total, testCase.wantVisible) + } + } + }) } } func TestGrantManagerResourceUsesServerControlledGrantShape(t *testing.T) { walletClient := &fakeWalletClient{} - hostClient := &fakeUserHostClient{} - profileClient := &fakeUserProfileClient{} + hostClient := &fakeUserHostClient{capabilityAllowed: map[string]bool{"manager_cross_region": true}} + profileClient := crossRegionManagerProfileClient() router := newManagerCenterTestRouter(walletClient, hostClient, profileClient) body := []byte(`{"command_id":"mgr-grant-1","target_user_id":"900001","resource_id":"101"}`) request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/resource-grants", bytes.NewReader(body)) @@ -426,8 +540,11 @@ func TestGrantManagerResourceChecksCapabilityByWalletResourceType(t *testing.T) if walletClient.lastGetResource == nil || walletClient.lastGetResource.GetResourceId() != 202 { t.Fatalf("resource lookup mismatch: %+v", walletClient.lastGetResource) } - if hostClient.lastCapability == nil || hostClient.lastCapability.GetCapability() != "manager_center" { - t.Fatalf("last manager capability should be final same-country actor check, got %+v", hostClient.lastCapability) + if hostClient.lastCapability == nil || hostClient.lastCapability.GetCapability() != "manager_cross_region" { + t.Fatalf("last manager capability should resolve the actor's effective target scope, got %+v", hostClient.lastCapability) + } + if !hostClient.sawCapability("manager_center") { + t.Fatalf("resource grant must still validate the active manager entry permission, saw %#v", hostClient.capabilities) } if !hostClient.sawCapability("manager_grant_vehicle") { t.Fatalf("vehicle grant must check manager_grant_vehicle, saw %#v", hostClient.capabilities) @@ -525,7 +642,7 @@ func TestGrantManagerVIPRequiresManagerVIPCapability(t *testing.T) { func TestGrantManagerVIPUsesManagerCenterGrantSource(t *testing.T) { walletClient := &fakeWalletClient{} - router := newManagerCenterTestRouter(walletClient, &fakeUserHostClient{}, &fakeUserProfileClient{}) + router := newManagerCenterTestRouter(walletClient, &fakeUserHostClient{capabilityAllowed: map[string]bool{"manager_cross_region": true}}, crossRegionManagerProfileClient()) body := []byte(`{"command_id":"mgr-vip-5","target_user_id":"900001","level":5}`) request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/vip-grants", bytes.NewReader(body)) request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) @@ -542,9 +659,58 @@ func TestGrantManagerVIPUsesManagerCenterGrantSource(t *testing.T) { } } +func TestGrantManagerVIPUsesTrialCardForConfiguredApp(t *testing.T) { + walletClient := &fakeWalletClient{ + vipProgramResp: &walletv1.GetVipProgramConfigResponse{Config: &walletv1.VipProgramConfig{ + AppCode: "lalu", GrantMode: "trial_card", TrialCardEnabled: true, Status: "active", + }}, + trialGrantResp: &walletv1.GrantVipTrialCardResponse{ + TrialCard: &walletv1.VipTrialCard{ + TrialCardId: "card-5", EntitlementId: "ent-5", SourceGrantId: "grant-5", + Level: 5, Status: "active", ExpiresAtMs: 1_800_000_000_000, + }, + ServerTimeMs: 1_790_000_000_000, + }, + } + router := newManagerCenterTestRouter(walletClient, &fakeUserHostClient{}, &fakeUserProfileClient{}) + body := []byte(`{"command_id":"mgr-fami-vip-5","target_user_id":"900001","level":5}`) + request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/vip-grants", bytes.NewReader(body)) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + request.Header.Set("X-Request-ID", "req-manager-fami-vip5") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if walletClient.lastGrantVip != nil { + t.Fatalf("trial-card program must not call direct GrantVip: %+v", walletClient.lastGrantVip) + } + req := walletClient.lastTrialGrant + if req == nil || req.GetAppCode() != "lalu" || req.GetCommandId() != "mgr-fami-vip-5" || req.GetTargetUserId() != 900001 || req.GetLevel() != 5 || req.GetDurationMs() != 30*24*60*60*1000 || req.GetGrantSource() != "manager_center" || req.GetOperatorUserId() != 42 { + t.Fatalf("wallet GrantVipTrialCard request mismatch: %+v", req) + } + var envelope struct { + Data struct { + GrantMode string `json:"grant_mode"` + TrialCardID string `json:"trial_card_id"` + EntitlementID string `json:"entitlement_id"` + Active bool `json:"active"` + Equipped bool `json:"equipped"` + } `json:"data"` + } + if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil { + t.Fatalf("decode response failed: %v", err) + } + if envelope.Data.GrantMode != "trial_card" || envelope.Data.TrialCardID != "card-5" || envelope.Data.EntitlementID != "ent-5" || envelope.Data.Active || envelope.Data.Equipped { + t.Fatalf("trial-card manager response mismatch: %+v", envelope.Data) + } +} + func TestManagerBlockAndUnblockCallUserService(t *testing.T) { - profileClient := &fakeUserProfileClient{} - router := newManagerCenterTestRouter(&fakeWalletClient{}, &fakeUserHostClient{}, profileClient) + profileClient := crossRegionManagerProfileClient() + router := newManagerCenterTestRouter(&fakeWalletClient{}, &fakeUserHostClient{capabilityAllowed: map[string]bool{"manager_cross_region": true}}, profileClient) body := []byte(`{"command_id":"block-163006","target_user_id":"900001","blocked_until_ms":1780000000000}`) request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/blocks", bytes.NewReader(body)) request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) @@ -577,7 +743,7 @@ func TestManagerBlockAndUnblockCallUserService(t *testing.T) { func TestSetManagerUserLevelCallsGrowthService(t *testing.T) { growthClient := &fakeGrowthLevelClient{} - router := newManagerCenterTestRouterWithGrowth(&fakeWalletClient{}, &fakeUserHostClient{}, &fakeUserProfileClient{}, growthClient) + router := newManagerCenterTestRouterWithGrowth(&fakeWalletClient{}, &fakeUserHostClient{capabilityAllowed: map[string]bool{"manager_cross_region": true}}, crossRegionManagerProfileClient(), growthClient) body := []byte(`{"command_id":"level-wealth-5","target_user_id":"900001","track":"wealth","level":5}`) request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/levels", bytes.NewReader(body)) request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) @@ -643,9 +809,10 @@ func TestCreateManagerBDLeaderRejectsCrossCountryTarget(t *testing.T) { hostAdminClient := &fakeUserHostAdminClient{} profileClient := &fakeUserProfileClient{usersByID: map[int64]*userv1.User{ 42: {UserId: 42, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "US", RegionId: 1001}, - 900001: {UserId: 900001, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "CA", RegionId: 2002}, + 900001: {UserId: 900001, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "CA", RegionId: 1001}, }} - router := newManagerCenterTestRouterWithHostAdmin(&fakeWalletClient{}, &fakeUserHostClient{}, hostAdminClient, profileClient) + hostClient := &fakeUserHostClient{capabilityAllowed: map[string]bool{"manager_cross_region": false}} + router := newManagerCenterTestRouterWithHostAdmin(&fakeWalletClient{}, hostClient, hostAdminClient, profileClient) body := []byte(`{"command_id":"mgr-bdleader-cross","target_user_id":"900001"}`) request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/bd-leaders", bytes.NewReader(body)) request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) @@ -660,12 +827,88 @@ func TestCreateManagerBDLeaderRejectsCrossCountryTarget(t *testing.T) { } } -func TestTransferManagerUserCountryUsesAdminCountryChangeWithoutSameCountryFilter(t *testing.T) { +func TestCreateManagerBDLeaderAllowsSameRegionTargetForCrossRegionManager(t *testing.T) { + hostAdminClient := &fakeUserHostAdminClient{} profileClient := &fakeUserProfileClient{usersByID: map[int64]*userv1.User{ - 42: {UserId: 42, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "US", RegionId: 1001}, + 42: {UserId: 42, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "US", RegionId: 1001}, + 900001: {UserId: 900001, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "CA", RegionId: 1001}, }} - hostClient := &fakeUserHostClient{} - router := newManagerCenterTestRouter(&fakeWalletClient{}, hostClient, profileClient) + hostClient := &fakeUserHostClient{capabilityAllowed: map[string]bool{"manager_cross_region": true}} + router := newManagerCenterTestRouterWithHostAdmin(&fakeWalletClient{}, hostClient, hostAdminClient, profileClient) + request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/bd-leaders", bytes.NewReader([]byte(`{"command_id":"mgr-bdleader-region","target_user_id":"900001"}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + request.Header.Set("X-Request-ID", "req-manager-bdleader-region") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("same-region BD Leader status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if req := hostAdminClient.lastCreateBDLeader; req == nil || req.GetTargetUserId() != 900001 { + t.Fatalf("same-region target must reach CreateBDLeader: %+v", req) + } +} + +func TestCreateManagerBDLeaderRejectsDifferentRegionForCrossRegionManager(t *testing.T) { + hostAdminClient := &fakeUserHostAdminClient{} + profileClient := &fakeUserProfileClient{usersByID: map[int64]*userv1.User{ + 42: {UserId: 42, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "US", RegionId: 1001}, + 900001: {UserId: 900001, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "CA", RegionId: 2002}, + }} + hostClient := &fakeUserHostClient{capabilityAllowed: map[string]bool{"manager_cross_region": true}} + router := newManagerCenterTestRouterWithHostAdmin(&fakeWalletClient{}, hostClient, hostAdminClient, profileClient) + request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/bd-leaders", bytes.NewReader([]byte(`{"command_id":"mgr-bdleader-other-region","target_user_id":"900001"}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + request.Header.Set("X-Request-ID", "req-manager-bdleader-other-region") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + assertEnvelope(t, recorder, http.StatusForbidden, httpkit.CodePermissionDenied, "req-manager-bdleader-other-region") + if hostAdminClient.lastCreateBDLeader != nil { + t.Fatalf("different-region target must not reach CreateBDLeader: %+v", hostAdminClient.lastCreateBDLeader) + } +} + +func TestCreateManagerBDLeaderAllowsCrossCountryTargetForHuwaa(t *testing.T) { + hostAdminClient := &fakeUserHostAdminClient{} + profileClient := &fakeUserProfileClient{usersByID: map[int64]*userv1.User{ + 42: {UserId: 42, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "US", RegionId: 1001}, + 900001: {UserId: 900001, Status: userv1.UserStatus_USER_STATUS_ACTIVE, DisplayUserId: "163006", Country: "CA", RegionId: 2002}, + }} + hostClient := &fakeUserHostClient{roleScopePolicyResp: &userv1.GetRoleScopePolicyResponse{Policy: &userv1.RoleScopePolicy{ + Scene: "manager_operations", BaseScope: "global", ExpandedScope: "global", RegionExpansionConfigurable: false, + }}} + router := newManagerCenterTestRouterWithHostAdmin(&fakeWalletClient{}, hostClient, hostAdminClient, profileClient) + body := []byte(`{"command_id":"huwaa-mgr-bdleader-cross","target_user_id":"900001"}`) + request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/bd-leaders", bytes.NewReader(body)) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "huwaa")) + request.Header.Set("X-Request-ID", "req-huwaa-manager-bdleader-cross") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("Huwaa cross-country BD Leader status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if req := hostAdminClient.lastCreateBDLeader; req == nil || req.GetMeta().GetAppCode() != "huwaa" || req.GetAdminUserId() != 42 || req.GetTargetUserId() != 900001 { + t.Fatalf("Huwaa cross-country target must reach app-scoped CreateBDLeader: %+v", req) + } +} + +func TestTransferManagerUserCountryUsesCrossRegionManagerScope(t *testing.T) { + profileClient := &fakeUserProfileClient{usersByID: map[int64]*userv1.User{ + 42: {UserId: 42, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "US", RegionId: 1001}, + 900001: {UserId: 900001, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "MX", RegionId: 1001}, + }} + hostClient := &fakeUserHostClient{capabilityAllowed: map[string]bool{"manager_cross_region": true}} + broadcastClient := &fakeBroadcastClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient) + handler.SetWalletClient(&fakeWalletClient{}) + handler.SetUserHostClient(hostClient) + handler.SetBroadcastClient(broadcastClient) + router := handler.Routes(auth.NewVerifier("secret")) body := []byte(`{"command_id":"mgr-country-1","target_user_id":"900001","country":"CA"}`) request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/users/country", bytes.NewReader(body)) request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) @@ -684,6 +927,16 @@ func TestTransferManagerUserCountryUsesAdminCountryChangeWithoutSameCountryFilte if !hostClient.sawCapability("manager_transfer_user_country") { t.Fatalf("country transfer must check manager_transfer_user_country, saw %#v", hostClient.capabilities) } + if req := broadcastClient.lastRemove; req == nil || req.GetUserId() != 900001 || req.GetRegionId() != 1001 || req.GetReason() != "manager_user_country_changed" || req.GetMeta().GetAppCode() != "lalu" { + t.Fatalf("manager country transfer must remove the old region broadcast member: %+v", req) + } +} + +func crossRegionManagerProfileClient() *fakeUserProfileClient { + return &fakeUserProfileClient{usersByID: map[int64]*userv1.User{ + 42: {UserId: 42, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "US", RegionId: 1001}, + 900001: {UserId: 900001, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "MX", RegionId: 1001}, + }} } // TestGetManagerOverviewIncludesTeamSalaryDashboard 验证经理概览聚合团队工资卡片: diff --git a/services/gateway-service/internal/transport/http/managerapi/handler.go b/services/gateway-service/internal/transport/http/managerapi/handler.go index 147658b8..4bb05ba9 100644 --- a/services/gateway-service/internal/transport/http/managerapi/handler.go +++ b/services/gateway-service/internal/transport/http/managerapi/handler.go @@ -13,6 +13,8 @@ type Handler struct { userHostAdminClient client.UserHostAdminClient userProfileClient client.UserProfileClient growthLevelClient client.GrowthLevelClient + // broadcastClient 只负责国家跨区域后的旧群清理;国家和房间迁移事实仍由 user-service outbox 驱动。 + broadcastClient client.BroadcastClient // dashboardCache 缓存经理团队工资统计,避免每次进入经理中心都做组织树 + 周期账户双聚合。 dashboardCache managerDashboardCache } @@ -23,6 +25,7 @@ type Config struct { UserHostAdminClient client.UserHostAdminClient UserProfileClient client.UserProfileClient GrowthLevelClient client.GrowthLevelClient + BroadcastClient client.BroadcastClient } func New(config Config) *Handler { @@ -32,6 +35,7 @@ func New(config Config) *Handler { userHostAdminClient: config.UserHostAdminClient, userProfileClient: config.UserProfileClient, growthLevelClient: config.GrowthLevelClient, + broadcastClient: config.BroadcastClient, } } diff --git a/services/gateway-service/internal/transport/http/managerapi/manager_center_handler.go b/services/gateway-service/internal/transport/http/managerapi/manager_center_handler.go index dffa3114..50f79dac 100644 --- a/services/gateway-service/internal/transport/http/managerapi/manager_center_handler.go +++ b/services/gateway-service/internal/transport/http/managerapi/manager_center_handler.go @@ -24,15 +24,22 @@ const managerGrantBadgeCapability = "manager_grant_badge" const managerGrantVIPCapability = "manager_grant_vip" const managerUpdateUserLevelCapability = "manager_update_user_level" const managerAddBDLeaderCapability = "manager_add_bd_leader" +const managerCrossRegionCapability = "manager_cross_region" const managerAddAdminCapability = "manager_add_admin" const managerAddSuperadminCapability = "manager_add_superadmin" const managerBlockUserCapability = "manager_block_user" const managerTransferUserCountryCapability = "manager_transfer_user_country" const managerGrantDayMS int64 = 24 * 60 * 60 * 1000 const managerGrantDefaultDurationMS int64 = 7 * managerGrantDayMS +const managerGrantVIPDurationMS int64 = 30 * managerGrantDayMS const managerGrantMaxResourceIDs = 20 const managerGrantMaxVIPLevel int32 = 5 +const managerRoleScopeCountry = "country" +const managerRoleScopeRegion = "region" +const managerRoleScopeGlobal = "global" +const managerOperationScopeScene = "manager_operations" + type managerGrantResourceData struct { ResourceID string `json:"resource_id"` ResourceType string `json:"resource_type"` @@ -165,15 +172,14 @@ func (h *Handler) searchManagerUsers(writer http.ResponseWriter, request *http.R httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } - // 搜索先走已有 business lookup 保持短号/昵称解析一致,再用 BatchGetUsers 拿国家和等级投影做最终过滤。 - // 只有国家转移入口显式带 scope=all 且经理拥有对应开关时,才允许跨国家搜索;其他动作继续默认同国家。 + // 搜索先走已有 business lookup 保持短号/昵称解析一致,再用 BatchGetUsers 拿国家和区域投影做最终过滤。 + // 所有经理动作的 App 差异都来自 user-service 策略,handler 不直接判断 app_code。 actor, ok := h.requireManagerCenterActor(writer, request) if !ok { return } - scope := strings.ToLower(strings.TrimSpace(request.URL.Query().Get("scope"))) - allCountries := scope == "all" - if allCountries && !h.requireManagerCapability(writer, request, managerTransferUserCountryCapability) { + managerTargetScope, ok := h.managerEffectiveScope(writer, request) + if !ok { return } pageSize, ok := httpkit.PositiveInt32Query(request, "page_size", 20) @@ -220,8 +226,8 @@ func (h *Handler) searchManagerUsers(writer http.ResponseWriter, request *http.R visibleUserIDs := make([]int64, 0, len(userIDs)) for _, userID := range userIDs { user := usersResp.GetUsers()[userID] - // 默认搜索必须和写入口一样收敛到同国家;国家转移已单独校验权限,允许在这里跨国家挑选目标。 - if user == nil || user.GetStatus() != userv1.UserStatus_USER_STATUS_ACTIVE || (!allCountries && !sameCountry(actor.GetCountry(), user.GetCountry())) { + // 搜索与最终创建共用 scope evaluator,避免同一 App 的规则在两个 handler 分叉。 + if user == nil || user.GetStatus() != userv1.UserStatus_USER_STATUS_ACTIVE || !managerTargetMatchesScope(managerTargetScope, actor, user) { continue } item := managerUserFromProto(user, levels[userID]) @@ -376,8 +382,8 @@ func (h *Handler) grantManagerResource(writer http.ResponseWriter, request *http } resources = append(resources, resource) } - // 赠送资源前先确认目标用户仍然 active 且与经理同国家;wallet-service 还会在事务内复验资源白名单。 - if _, ok := h.requireSameCountryTarget(writer, request, targetUserID); !ok { + // 所有经理目标用户动作共用同一有效范围;wallet-service 还会在事务内复验资源白名单。 + if _, ok := h.requireManagerTarget(writer, request, targetUserID); !ok { return } reason := strings.TrimSpace(body.Reason) @@ -460,17 +466,72 @@ func (h *Handler) grantManagerVIP(writer http.ResponseWriter, request *http.Requ if !h.requireManagerCapability(writer, request, managerGrantVIPCapability) { return } - // 经理中心复用后台 VIP 发放账务能力,但 source 固定为 manager_center,钱包侧据此保留统一 IM/outbox 行为。 - if _, ok := h.requireSameCountryTarget(writer, request, targetUserID); !ok { + // 经理中心先读取 App 级 program,再选择直接会员或体验卡专用命令。钱包会在写事务内再次校验, + // 因此配置在两次 RPC 之间切换时只会明确失败,不会绕过当前发放语义。 + if _, ok := h.requireManagerTarget(writer, request, targetUserID); !ok { return } reason := strings.TrimSpace(body.Reason) if reason == "" { reason = "manager_center" } + appCode := appcode.FromContext(request.Context()) + programResp, err := h.walletClient.GetVipProgramConfig(request.Context(), &walletv1.GetVipProgramConfigRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + AppCode: appCode, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + program := programResp.GetConfig() + if program == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + if program.GetGrantMode() == "trial_card" { + if !program.GetTrialCardEnabled() { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + resp, err := h.walletClient.GrantVipTrialCard(request.Context(), &walletv1.GrantVipTrialCardRequest{ + CommandId: commandID, + AppCode: appCode, + TargetUserId: targetUserID, + Level: body.Level, + DurationMs: managerGrantVIPDurationMS, + GrantSource: "manager_center", + OperatorUserId: auth.UserIDFromContext(request.Context()), + Reason: reason, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + card := resp.GetTrialCard() + httpkit.WriteOK(writer, request, map[string]any{ + "grant_mode": "trial_card", + "transaction_id": "", + "trial_card_id": card.GetTrialCardId(), + "entitlement_id": card.GetEntitlementId(), + "source_grant_id": card.GetSourceGrantId(), + "level": card.GetLevel(), + // 发卡只入背包且从发卡时开始绝对计时;active=false 明确表示未直接激活会员。 + "active": false, + "card_status": card.GetStatus(), + "equipped": card.GetEquipped(), + "expires_at_ms": card.GetExpiresAtMs(), + "server_time_ms": resp.GetServerTimeMs(), + }) + return + } + if program.GetGrantMode() != "direct_membership" { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } resp, err := h.walletClient.GrantVip(request.Context(), &walletv1.GrantVipRequest{ CommandId: commandID, - AppCode: appcode.FromContext(request.Context()), + AppCode: appCode, TargetUserId: targetUserID, Level: body.Level, GrantSource: "manager_center", @@ -482,6 +543,7 @@ func (h *Handler) grantManagerVIP(writer http.ResponseWriter, request *http.Requ return } httpkit.WriteOK(writer, request, map[string]any{ + "grant_mode": "direct_membership", "transaction_id": resp.GetTransactionId(), "level": resp.GetVip().GetLevel(), "active": resp.GetVip().GetActive(), @@ -490,6 +552,27 @@ func (h *Handler) grantManagerVIP(writer http.ResponseWriter, request *http.Requ }) } +func (h *Handler) removeManagerOldRegionBroadcastMemberBestEffort(request *http.Request, userID int64, oldRegionID int64, newRegionID int64) { + if h == nil || h.broadcastClient == nil || userID <= 0 || oldRegionID <= 0 || oldRegionID == newRegionID { + return + } + _, err := h.broadcastClient.RemoveRegionBroadcastMember(request.Context(), &activityv1.RemoveRegionBroadcastMemberRequest{ + Meta: httpkit.ActivityMeta(request), + UserId: userID, + RegionId: oldRegionID, + Reason: "manager_user_country_changed", + }) + if err != nil { + // 国家和房间迁移事实已经提交,腾讯 IM 旧群清理失败不能把已成功的国家修改伪装成失败。 + logx.Warn(request.Context(), "manager_im_region_group_member_remove_failed", + slog.Int64("user_id", userID), + slog.Int64("old_region_id", oldRegionID), + slog.Int64("new_region_id", newRegionID), + slog.String("error", err.Error()), + ) + } +} + func (h *Handler) managerBlocks(writer http.ResponseWriter, request *http.Request) { switch request.Method { case http.MethodGet: @@ -576,8 +659,8 @@ func (h *Handler) createManagerBlock(writer http.ResponseWriter, request *http.R httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } - // user-service 会再次读取双方用户并校验同国家,这里保留 gateway 早拒绝以减少无效封禁事务。 - if _, ok := h.requireSameCountryTargetWithActor(writer, request, actor, targetUserID); !ok { + // user-service 会按同一 App 策略复验,gateway 先拒绝越界目标以减少无效封禁事务。 + if _, ok := h.requireManagerTargetWithActor(writer, request, actor, targetUserID); !ok { return } resp, err := h.userProfileClient.CreateManagerUserBlock(request.Context(), &userv1.CreateManagerUserBlockRequest{ @@ -661,7 +744,7 @@ func (h *Handler) setManagerUserLevel(writer http.ResponseWriter, request *http. return } // 等级设置会把目标轨道 total_value 直接改到对应规则阈值,并由 activity-service 补齐 1..目标等级奖励。 - if _, ok := h.requireSameCountryTarget(writer, request, targetUserID); !ok { + if _, ok := h.requireManagerTarget(writer, request, targetUserID); !ok { return } resp, err := h.growthLevelClient.SetUserLevel(request.Context(), &activityv1.SetUserLevelRequest{ @@ -711,9 +794,8 @@ func (h *Handler) createManagerBDLeader(writer http.ResponseWriter, request *htt httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } - // manager-center 的搜索接口已经按同国家过滤,但写入口必须重新读取目标用户做权限判定。 - // 这样即使 H5 被篡改直接提交 target_user_id,也不能把跨国家用户提升为 BD Leader。 - target, ok := h.requireSameCountryTargetWithActor(writer, request, actor, targetUserID) + // 写入口重新解析策略并读取 active 目标,不能信任搜索结果或客户端自报范围。 + target, ok := h.requireManagerTargetWithActor(writer, request, actor, targetUserID) if !ok { return } @@ -777,7 +859,10 @@ func (h *Handler) transferManagerUserCountry(writer http.ResponseWriter, request if reason == "" { reason = "manager_center_country_transfer" } - // 国家转移是治理动作,不复用同国家目标校验;权限由 manager_profiles 独立开关控制,实际国家/区域事务由 user-service 统一落库和发 outbox。 + // 国家转移也属于经理数据范围;拥有功能权限不代表可以越过 country/region/global 有效范围选择任意目标。 + if _, ok := h.requireManagerTargetWithActor(writer, request, actor, targetUserID); !ok { + return + } resp, err := h.userProfileClient.AdminChangeUserCountry(request.Context(), &userv1.AdminChangeUserCountryRequest{ Meta: httpkit.UserMeta(request, ""), TargetUserId: targetUserID, @@ -789,6 +874,8 @@ func (h *Handler) transferManagerUserCountry(writer http.ResponseWriter, request httpkit.WriteRPCError(writer, request, err) return } + // 经理入口和 App/后台入口必须保持国家变更副作用一致;房间迁移由 user_outbox 保证,旧区域 IM 群清理在提交后尽力执行。 + h.removeManagerOldRegionBroadcastMemberBestEffort(request, targetUserID, resp.GetOldRegionId(), resp.GetNewRegionId()) httpkit.WriteOK(writer, request, map[string]any{ "user": managerUserFromProto(resp.GetUser(), nil), "old_region_id": resp.GetOldRegionId(), @@ -880,15 +967,74 @@ func (h *Handler) requireManagerCapability(writer http.ResponseWriter, request * return true } -func (h *Handler) requireSameCountryTarget(writer http.ResponseWriter, request *http.Request, targetUserID int64) (*userv1.User, bool) { +func (h *Handler) managerEffectiveScope(writer http.ResponseWriter, request *http.Request) (string, bool) { + resp, err := h.userHostClient.GetRoleScopePolicy(request.Context(), &userv1.GetRoleScopePolicyRequest{ + Meta: httpkit.UserMeta(request, ""), + Scene: managerOperationScopeScene, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return "", false + } + policy := resp.GetPolicy() + if policy == nil || !validManagerRoleScope(policy.GetBaseScope()) { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return "", false + } + scope := policy.GetBaseScope() + if !policy.GetRegionExpansionConfigurable() { + return scope, true + } + expandedScope := policy.GetExpandedScope() + if expandedScope == "" { + // 兼容上一版 user-service;旧协议只支持扩到同区域,滚动升级期间保持旧边界而不是放大到 global。 + expandedScope = managerRoleScopeRegion + } + if !validManagerRoleScope(expandedScope) { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return "", false + } + capability, err := h.userHostClient.CheckBusinessCapability(request.Context(), &userv1.CheckBusinessCapabilityRequest{ + Meta: httpkit.UserMeta(request, ""), + ActorUserId: auth.UserIDFromContext(request.Context()), + Capability: managerCrossRegionCapability, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return "", false + } + if capability.GetAllowed() { + scope = expandedScope + } + return scope, true +} + +func (h *Handler) requireManagerTarget(writer http.ResponseWriter, request *http.Request, targetUserID int64) (*userv1.User, bool) { actor, ok := h.requireManagerCenterActor(writer, request) if !ok { return nil, false } - return h.requireSameCountryTargetWithActor(writer, request, actor, targetUserID) + return h.requireManagerTargetWithActor(writer, request, actor, targetUserID) } -func (h *Handler) requireSameCountryTargetWithActor(writer http.ResponseWriter, request *http.Request, actor *userv1.User, targetUserID int64) (*userv1.User, bool) { +func (h *Handler) requireManagerTargetWithActor(writer http.ResponseWriter, request *http.Request, actor *userv1.User, targetUserID int64) (*userv1.User, bool) { + scope, ok := h.managerEffectiveScope(writer, request) + if !ok { + return nil, false + } + target, ok := h.requireActiveTarget(writer, request, targetUserID) + if !ok { + return nil, false + } + if !managerTargetMatchesScope(scope, actor, target) { + httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "permission denied") + return nil, false + } + return target, true +} + +// requireActiveTarget 是经理写入口共同的目标用户真实性门禁;范围策略只能决定后续国家/区域匹配,不能绕过 active 用户校验。 +func (h *Handler) requireActiveTarget(writer http.ResponseWriter, request *http.Request, targetUserID int64) (*userv1.User, bool) { targetResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ Meta: httpkit.UserMeta(request, ""), UserId: targetUserID, @@ -902,13 +1048,36 @@ func (h *Handler) requireSameCountryTargetWithActor(writer http.ResponseWriter, httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found") return nil, false } - if !sameCountry(actor.GetCountry(), target.GetCountry()) { - httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "permission denied") - return nil, false - } return target, true } +func validManagerRoleScope(scope string) bool { + switch scope { + case managerRoleScopeCountry, managerRoleScopeRegion, managerRoleScopeGlobal: + return true + default: + return false + } +} + +// managerTargetMatchesScope 是搜索和写入口共享的唯一匹配器。region 表示在同国家基础上扩到同区域, +// 因而国家映射短暂异常时,打开扩区开关也不会反而失去原本可邀请的同国家用户。 +func managerTargetMatchesScope(scope string, actor *userv1.User, target *userv1.User) bool { + if actor == nil || target == nil { + return false + } + switch scope { + case managerRoleScopeGlobal: + return true + case managerRoleScopeRegion: + return sameCountry(actor.GetCountry(), target.GetCountry()) || sameRegion(actor.GetRegionId(), target.GetRegionId()) + case managerRoleScopeCountry: + return sameCountry(actor.GetCountry(), target.GetCountry()) + default: + return false + } +} + func (h *Handler) managerPermissionOverview(request *http.Request) map[string]bool { permissions := map[string]bool{ "can_grant_avatar_frame": false, @@ -917,6 +1086,7 @@ func (h *Handler) managerPermissionOverview(request *http.Request) map[string]bo "can_grant_vip": false, "can_update_user_level": false, "can_add_bd_leader": false, + "can_manage_cross_region": false, "can_add_admin": false, "can_add_superadmin": false, "can_block_user": false, @@ -929,6 +1099,7 @@ func (h *Handler) managerPermissionOverview(request *http.Request) map[string]bo "can_grant_vip": managerGrantVIPCapability, "can_update_user_level": managerUpdateUserLevelCapability, "can_add_bd_leader": managerAddBDLeaderCapability, + "can_manage_cross_region": managerCrossRegionCapability, "can_add_admin": managerAddAdminCapability, "can_add_superadmin": managerAddSuperadminCapability, "can_block_user": managerBlockUserCapability, @@ -966,6 +1137,10 @@ func sameCountry(left string, right string) bool { return strings.EqualFold(strings.TrimSpace(left), strings.TrimSpace(right)) && strings.TrimSpace(left) != "" } +func sameRegion(left int64, right int64) bool { + return left > 0 && left == right +} + func managerGrantResourceFromProto(resource *walletv1.Resource) managerGrantResourceData { if resource == nil { return managerGrantResourceData{} diff --git a/services/gateway-service/internal/transport/http/response_test.go b/services/gateway-service/internal/transport/http/response_test.go index 850a6568..4a511f10 100644 --- a/services/gateway-service/internal/transport/http/response_test.go +++ b/services/gateway-service/internal/transport/http/response_test.go @@ -40,35 +40,36 @@ const testAppAccessTokenTTLSec int64 = 604800 // fakeRoomClient 只承接 gateway transport 测试需要观察的 gRPC 入参。 // 测试目标是 HTTP envelope 和 RequestMeta 传递,不验证 room-service 业务行为。 type fakeRoomClient struct { - lastCreate *roomv1.CreateRoomRequest - lastUpdateProfile *roomv1.UpdateRoomProfileRequest - lastSaveBackground *roomv1.SaveRoomBackgroundRequest - lastSetBackground *roomv1.SetRoomBackgroundRequest - lastJoin *roomv1.JoinRoomRequest - lastHeartbeat *roomv1.RoomHeartbeatRequest - lastLeave *roomv1.LeaveRoomRequest - lastMicUp *roomv1.MicUpRequest - lastMicDown *roomv1.MicDownRequest - lastChangeMicSeat *roomv1.ChangeMicSeatRequest - lastConfirmMic *roomv1.ConfirmMicPublishingRequest - lastMicHeartbeat *roomv1.MicHeartbeatRequest - lastSetMicMute *roomv1.SetMicMuteRequest - lastRTCEvent *roomv1.ApplyRTCEventRequest - lastMicSeatLock *roomv1.SetMicSeatLockRequest - lastChatEnabled *roomv1.SetChatEnabledRequest - lastRoomPassword *roomv1.SetRoomPasswordRequest - lastSetAdmin *roomv1.SetRoomAdminRequest - lastMute *roomv1.MuteUserRequest - lastKick *roomv1.KickUserRequest - lastUnban *roomv1.UnbanUserRequest - lastGift *roomv1.SendGiftRequest - sendGiftResp *roomv1.SendGiftResponse - lastFollowRoom *roomv1.FollowRoomRequest - lastUnfollowRoom *roomv1.UnfollowRoomRequest - createErr error - updateProfileErr error - joinErr error - joinResp *roomv1.JoinRoomResponse + lastCreate *roomv1.CreateRoomRequest + lastUpdateProfile *roomv1.UpdateRoomProfileRequest + lastSaveBackground *roomv1.SaveRoomBackgroundRequest + lastSetBackground *roomv1.SetRoomBackgroundRequest + lastJoin *roomv1.JoinRoomRequest + lastHeartbeat *roomv1.RoomHeartbeatRequest + lastLeave *roomv1.LeaveRoomRequest + lastMicUp *roomv1.MicUpRequest + lastMicDown *roomv1.MicDownRequest + lastChangeMicSeat *roomv1.ChangeMicSeatRequest + lastConfirmMic *roomv1.ConfirmMicPublishingRequest + lastMicHeartbeat *roomv1.MicHeartbeatRequest + lastSetMicMute *roomv1.SetMicMuteRequest + lastRTCEvent *roomv1.ApplyRTCEventRequest + lastMicSeatLock *roomv1.SetMicSeatLockRequest + lastChatEnabled *roomv1.SetChatEnabledRequest + lastRoomPassword *roomv1.SetRoomPasswordRequest + lastSetAdmin *roomv1.SetRoomAdminRequest + lastMute *roomv1.MuteUserRequest + lastKick *roomv1.KickUserRequest + lastUnban *roomv1.UnbanUserRequest + lastGift *roomv1.SendGiftRequest + sendGiftResp *roomv1.SendGiftResponse + sendGiftWaitForContext bool + lastFollowRoom *roomv1.FollowRoomRequest + lastUnfollowRoom *roomv1.UnfollowRoomRequest + createErr error + updateProfileErr error + joinErr error + joinResp *roomv1.JoinRoomResponse } func (f *fakeRoomClient) CreateRoom(_ context.Context, req *roomv1.CreateRoomRequest) (*roomv1.CreateRoomResponse, error) { @@ -274,8 +275,12 @@ func (f *fakeRoomClient) UnbanUser(_ context.Context, req *roomv1.UnbanUserReque return &roomv1.UnbanUserResponse{Result: &roomv1.CommandResult{Applied: true, RoomVersion: 13}}, nil } -func (f *fakeRoomClient) SendGift(_ context.Context, req *roomv1.SendGiftRequest) (*roomv1.SendGiftResponse, error) { +func (f *fakeRoomClient) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*roomv1.SendGiftResponse, error) { f.lastGift = req + if f.sendGiftWaitForContext { + <-ctx.Done() + return nil, status.FromContextError(ctx.Err()).Err() + } if f.sendGiftResp != nil { return f.sendGiftResp, nil } @@ -458,9 +463,12 @@ type fakeUserCountryQueryClient struct { } type fakeUserRegionClient struct { - last *userv1.GetRegionRequest - resp *userv1.RegionResponse - err error + lastList *userv1.ListRegionsRequest + listResp *userv1.ListRegionsResponse + listErr error + last *userv1.GetRegionRequest + resp *userv1.RegionResponse + err error } type fakeUserHostClient struct { @@ -549,8 +557,12 @@ type fakeUserHostClient struct { kickErr error lastCapability *userv1.CheckBusinessCapabilityRequest capabilities []string + capabilityAllowed map[string]bool capabilityResp *userv1.CheckBusinessCapabilityResponse capabilityErr error + lastRoleScopePolicy *userv1.GetRoleScopePolicyRequest + roleScopePolicyResp *userv1.GetRoleScopePolicyResponse + roleScopePolicyErr error } type fakeUserHostAdminClient struct { @@ -617,11 +629,29 @@ type fakeWalletClient struct { vipPackagesResp *walletv1.ListVipPackagesResponse lastMyVip *walletv1.GetMyVipRequest myVipResp *walletv1.GetMyVipResponse + lastUpdateVIPSettings *walletv1.UpdateMyVipSettingsRequest + updateVIPSettingsResp *walletv1.UpdateMyVipSettingsResponse vipErr error + lastVipProgram *walletv1.GetVipProgramConfigRequest + vipProgramResp *walletv1.GetVipProgramConfigResponse + lastBenefitCheck *walletv1.CheckVipBenefitRequest + benefitCheckResp *walletv1.CheckVipBenefitResponse lastPurchaseVip *walletv1.PurchaseVipRequest purchaseVipResp *walletv1.PurchaseVipResponse lastGrantVip *walletv1.GrantVipRequest grantVipResp *walletv1.GrantVipResponse + lastTrialGrant *walletv1.GrantVipTrialCardRequest + trialGrantResp *walletv1.GrantVipTrialCardResponse + lastTrialEquip *walletv1.EquipVipTrialCardRequest + trialEquipResp *walletv1.EquipVipTrialCardResponse + lastTrialUnequip *walletv1.UnequipVipTrialCardRequest + trialUnequipResp *walletv1.UnequipVipTrialCardResponse + lastCurrentVIPRebate *walletv1.GetMyCurrentVipDailyCoinRebateRequest + currentVIPRebateResp *walletv1.GetMyCurrentVipDailyCoinRebateResponse + lastVIPRebateStatuses *walletv1.ListMyVipDailyCoinRebateStatusesRequest + vipRebateStatusesResp *walletv1.ListMyVipDailyCoinRebateStatusesResponse + lastClaimVIPRebate *walletv1.ClaimVipDailyCoinRebateRequest + claimVIPRebateResp *walletv1.ClaimVipDailyCoinRebateResponse lastTransfer *walletv1.TransferCoinFromSellerRequest transferResp *walletv1.TransferCoinFromSellerResponse transferErr error @@ -787,6 +817,7 @@ type fakeUserCPClient struct { } type fakeBroadcastClient struct { + lastGlobal *activityv1.PublishGlobalBroadcastRequest lastRemove *activityv1.RemoveRegionBroadcastMemberRequest err error } @@ -1438,6 +1469,18 @@ func (f *fakeUserCountryQueryClient) ListLoginRiskBlockedCountries(_ context.Con return &userv1.ListLoginRiskBlockedCountriesResponse{}, nil } +func (f *fakeUserRegionClient) ListRegions(_ context.Context, req *userv1.ListRegionsRequest) (*userv1.ListRegionsResponse, error) { + f.lastList = req + if f.listErr != nil { + return nil, f.listErr + } + if f.listResp != nil { + return f.listResp, nil + } + + return &userv1.ListRegionsResponse{}, nil +} + func (f *fakeUserRegionClient) GetRegion(_ context.Context, req *userv1.GetRegionRequest) (*userv1.RegionResponse, error) { f.last = req if f.err != nil { @@ -1637,6 +1680,14 @@ func (f *fakeUserCPClient) CancelBreakCPRelationship(context.Context, *userv1.Ca return &userv1.CancelBreakCPRelationshipResponse{}, nil } +func (f *fakeBroadcastClient) PublishGlobalBroadcast(_ context.Context, req *activityv1.PublishGlobalBroadcastRequest) (*activityv1.PublishBroadcastResponse, error) { + f.lastGlobal = req + if f.err != nil { + return nil, f.err + } + return &activityv1.PublishBroadcastResponse{EventId: req.GetEventId(), GroupId: "hy_fami_bc_g_v2", Status: "pending", Created: true}, nil +} + func (f *fakeBroadcastClient) RemoveRegionBroadcastMember(_ context.Context, req *activityv1.RemoveRegionBroadcastMemberRequest) (*activityv1.RemoveRegionBroadcastMemberResponse, error) { f.lastRemove = req if f.err != nil { @@ -1930,9 +1981,31 @@ func (f *fakeUserHostClient) CheckBusinessCapability(_ context.Context, req *use if f.capabilityResp != nil { return f.capabilityResp, nil } + if allowed, configured := f.capabilityAllowed[req.GetCapability()]; configured { + return &userv1.CheckBusinessCapabilityResponse{Allowed: allowed}, nil + } + if req.GetCapability() == "manager_cross_region" { + return &userv1.CheckBusinessCapabilityResponse{Allowed: false}, nil + } return &userv1.CheckBusinessCapabilityResponse{Allowed: true}, nil } +func (f *fakeUserHostClient) GetRoleScopePolicy(_ context.Context, req *userv1.GetRoleScopePolicyRequest) (*userv1.GetRoleScopePolicyResponse, error) { + f.lastRoleScopePolicy = req + if f.roleScopePolicyErr != nil { + return nil, f.roleScopePolicyErr + } + if f.roleScopePolicyResp != nil { + return f.roleScopePolicyResp, nil + } + return &userv1.GetRoleScopePolicyResponse{Policy: &userv1.RoleScopePolicy{ + Scene: "manager_operations", + BaseScope: "country", + ExpandedScope: "region", + RegionExpansionConfigurable: true, + }}, nil +} + func (f *fakeUserHostClient) sawCapability(capability string) bool { for _, item := range f.capabilities { if item == capability { @@ -2299,6 +2372,42 @@ func (f *fakeWalletClient) GetMyVip(_ context.Context, req *walletv1.GetMyVipReq return &walletv1.GetMyVipResponse{Vip: &walletv1.UserVip{UserId: req.GetUserId()}}, nil } +func (f *fakeWalletClient) UpdateMyVipSettings(_ context.Context, req *walletv1.UpdateMyVipSettingsRequest) (*walletv1.UpdateMyVipSettingsResponse, error) { + f.lastUpdateVIPSettings = req + if f.vipErr != nil { + return nil, f.vipErr + } + if f.updateVIPSettingsResp != nil { + return f.updateVIPSettingsResp, nil + } + return &walletv1.UpdateMyVipSettingsResponse{}, nil +} + +func (f *fakeWalletClient) GetVipProgramConfig(_ context.Context, req *walletv1.GetVipProgramConfigRequest) (*walletv1.GetVipProgramConfigResponse, error) { + f.lastVipProgram = req + if f.vipErr != nil { + return nil, f.vipErr + } + if f.vipProgramResp != nil { + return f.vipProgramResp, nil + } + // 多数既有 gateway 测试建模 Lalu;明确返回 legacy direct,避免测试桩用空配置掩盖真实路由分支。 + return &walletv1.GetVipProgramConfigResponse{Config: &walletv1.VipProgramConfig{ + AppCode: req.GetAppCode(), GrantMode: "direct_membership", Status: "active", + }}, nil +} + +func (f *fakeWalletClient) CheckVipBenefit(_ context.Context, req *walletv1.CheckVipBenefitRequest) (*walletv1.CheckVipBenefitResponse, error) { + f.lastBenefitCheck = req + if f.vipErr != nil { + return nil, f.vipErr + } + if f.benefitCheckResp != nil { + return f.benefitCheckResp, nil + } + return &walletv1.CheckVipBenefitResponse{}, nil +} + func (f *fakeWalletClient) PurchaseVip(_ context.Context, req *walletv1.PurchaseVipRequest) (*walletv1.PurchaseVipResponse, error) { f.lastPurchaseVip = req if f.vipErr != nil { @@ -2310,6 +2419,72 @@ func (f *fakeWalletClient) PurchaseVip(_ context.Context, req *walletv1.Purchase return &walletv1.PurchaseVipResponse{}, nil } +func (f *fakeWalletClient) GetMyCurrentVipDailyCoinRebate(_ context.Context, req *walletv1.GetMyCurrentVipDailyCoinRebateRequest) (*walletv1.GetMyCurrentVipDailyCoinRebateResponse, error) { + f.lastCurrentVIPRebate = req + if f.vipErr != nil { + return nil, f.vipErr + } + if f.currentVIPRebateResp != nil { + return f.currentVIPRebateResp, nil + } + return &walletv1.GetMyCurrentVipDailyCoinRebateResponse{}, nil +} + +func (f *fakeWalletClient) ListMyVipDailyCoinRebateStatuses(_ context.Context, req *walletv1.ListMyVipDailyCoinRebateStatusesRequest) (*walletv1.ListMyVipDailyCoinRebateStatusesResponse, error) { + f.lastVIPRebateStatuses = req + if f.vipErr != nil { + return nil, f.vipErr + } + if f.vipRebateStatusesResp != nil { + return f.vipRebateStatusesResp, nil + } + return &walletv1.ListMyVipDailyCoinRebateStatusesResponse{}, nil +} + +func (f *fakeWalletClient) ClaimVipDailyCoinRebate(_ context.Context, req *walletv1.ClaimVipDailyCoinRebateRequest) (*walletv1.ClaimVipDailyCoinRebateResponse, error) { + f.lastClaimVIPRebate = req + if f.vipErr != nil { + return nil, f.vipErr + } + if f.claimVIPRebateResp != nil { + return f.claimVIPRebateResp, nil + } + return &walletv1.ClaimVipDailyCoinRebateResponse{}, nil +} + +func (f *fakeWalletClient) GrantVipTrialCard(_ context.Context, req *walletv1.GrantVipTrialCardRequest) (*walletv1.GrantVipTrialCardResponse, error) { + f.lastTrialGrant = req + if f.vipErr != nil { + return nil, f.vipErr + } + if f.trialGrantResp != nil { + return f.trialGrantResp, nil + } + return &walletv1.GrantVipTrialCardResponse{}, nil +} + +func (f *fakeWalletClient) EquipVipTrialCard(_ context.Context, req *walletv1.EquipVipTrialCardRequest) (*walletv1.EquipVipTrialCardResponse, error) { + f.lastTrialEquip = req + if f.vipErr != nil { + return nil, f.vipErr + } + if f.trialEquipResp != nil { + return f.trialEquipResp, nil + } + return &walletv1.EquipVipTrialCardResponse{}, nil +} + +func (f *fakeWalletClient) UnequipVipTrialCard(_ context.Context, req *walletv1.UnequipVipTrialCardRequest) (*walletv1.UnequipVipTrialCardResponse, error) { + f.lastTrialUnequip = req + if f.vipErr != nil { + return nil, f.vipErr + } + if f.trialUnequipResp != nil { + return f.trialUnequipResp, nil + } + return &walletv1.UnequipVipTrialCardResponse{}, nil +} + func (f *fakeWalletClient) DebitCPBreakupFee(context.Context, *walletv1.DebitCPBreakupFeeRequest) (*walletv1.DebitCPBreakupFeeResponse, error) { return &walletv1.DebitCPBreakupFeeResponse{}, nil } @@ -2624,8 +2799,10 @@ func valueOfString(value *string) string { type fakeRoomGuardClient struct { speakResp *roomv1.CheckSpeakPermissionResponse presenceResp *roomv1.VerifyRoomPresenceResponse + roomAppResp *roomv1.ResolveRoomAppCodeResponse lastSpeak *roomv1.CheckSpeakPermissionRequest lastPresence *roomv1.VerifyRoomPresenceRequest + lastRoomApp *roomv1.ResolveRoomAppCodeRequest err error } @@ -2653,6 +2830,17 @@ func (f *fakeRoomGuardClient) VerifyRoomPresence(_ context.Context, req *roomv1. return &roomv1.VerifyRoomPresenceResponse{Present: true, RoomVersion: 1}, nil } +func (f *fakeRoomGuardClient) ResolveRoomAppCode(_ context.Context, req *roomv1.ResolveRoomAppCodeRequest) (*roomv1.ResolveRoomAppCodeResponse, error) { + f.lastRoomApp = req + if f.err != nil { + return nil, f.err + } + if f.roomAppResp != nil { + return f.roomAppResp, nil + } + return &roomv1.ResolveRoomAppCodeResponse{AppCode: "lalu"}, nil +} + type fakeObjectUploader struct { lastKey string lastContent []byte @@ -3305,8 +3493,9 @@ func TestJoinRoomReturnsInitialDataWithIMGroupRTCTokenAndProfiles(t *testing.T) defer func() { roomapi.TimeNow = previousNow }() roomClient := &fakeRoomClient{joinResp: &roomv1.JoinRoomResponse{ - Result: &roomv1.CommandResult{Applied: true, RoomVersion: 7, ServerTimeMs: 1_700_000_000_123}, - User: &roomv1.RoomUser{UserId: 42, Role: "audience", JoinedAtMs: 1_700_000_000_000, LastSeenAtMs: 1_700_000_000_100}, + Result: &roomv1.CommandResult{Applied: true, RoomVersion: 7, ServerTimeMs: 1_700_000_000_123}, + User: &roomv1.RoomUser{UserId: 42, Role: "audience", JoinedAtMs: 1_700_000_000_000, LastSeenAtMs: 1_700_000_000_100}, + EffectiveVip: &roomv1.RoomEffectiveVipSnapshot{ProgramType: "tiered_privilege_v1", Level: 8, Name: "VIP8", RoomEntryNoticeEnabled: true}, Room: &roomv1.RoomSnapshot{ RoomId: "room_1001", OwnerUserId: 101, @@ -3422,6 +3611,10 @@ func TestJoinRoomReturnsInitialDataWithIMGroupRTCTokenAndProfiles(t *testing.T) if roomClient.lastJoin == nil || roomClient.lastJoin.GetPassword() != "1234" { t.Fatalf("join must forward room password to room-service: %+v", roomClient.lastJoin) } + effectiveVIP := data["effective_vip"].(map[string]any) + if effectiveVIP["program_type"] != "tiered_privilege_v1" || effectiveVIP["level"] != float64(8) || effectiveVIP["name"] != "VIP8" || effectiveVIP["room_entry_notice_enabled"] != true { + t.Fatalf("join response must expose room effective VIP snapshot: %+v", effectiveVIP) + } joinProfile := roomClient.lastJoin.GetActorDisplayProfile() if joinProfile.GetUserId() != 42 || joinProfile.GetNickname() != "hy" || joinProfile.GetAvatar() != "https://cdn.example/avatar.png" || joinProfile.GetDisplayUserId() != "100001" { t.Fatalf("join must inject actor display profile snapshot: %+v", joinProfile) @@ -3522,7 +3715,7 @@ func TestJoinRoomLiteReturnsFirstScreenFieldsWithoutDisplayHydration(t *testing. AppScene: "voice_chat_room", }) router := handler.Routes(auth.NewVerifier("secret")) - request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/join", bytes.NewReader([]byte(`{"room_id":"room_lite","command_id":"cmd-join-lite","role":"audience","response_mode":"lite"}`))) + request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/join", bytes.NewReader([]byte(`{"room_id":"room_lite","command_id":"cmd-join-lite","role":"audience","response_mode":"lite","presence_recovery":true}`))) request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) request.Header.Set("X-Request-ID", "req-join-lite") recorder := httptest.NewRecorder() @@ -3532,7 +3725,7 @@ func TestJoinRoomLiteReturnsFirstScreenFieldsWithoutDisplayHydration(t *testing. if recorder.Code != http.StatusOK { t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) } - if roomClient.lastJoin == nil || roomClient.lastJoin.GetResponseMode() != "lite" || roomClient.lastJoin.GetEntryVehicle() != nil { + if roomClient.lastJoin == nil || roomClient.lastJoin.GetResponseMode() != "lite" || !roomClient.lastJoin.GetPresenceRecovery() || roomClient.lastJoin.GetEntryVehicle() != nil { t.Fatalf("lite join must pass response_mode and skip entry vehicle: %+v", roomClient.lastJoin) } if len(walletClient.batchEquippedRequests) != 0 { @@ -3650,6 +3843,242 @@ func TestListRoomsUsesUserRegionFromUserService(t *testing.T) { } } +func TestListRoomsHuwaaDefaultsToAllRegionsAndSupportsValidatedFilters(t *testing.T) { + newHandler := func(regionClient *fakeUserRegionClient, countryClient *fakeUserCountryQueryClient, queryClient *fakeRoomQueryClient) http.Handler { + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{regionID: 1001}) + handler.SetRoomQueryClient(queryClient) + handler.SetUserRegionClient(regionClient) + handler.SetUserCountryQueryClient(countryClient) + return handler.Routes(auth.NewVerifier("secret")) + } + currentRegion := &userv1.RegionResponse{Region: &userv1.Region{ + RegionId: 1001, RegionCode: "ME", Status: "active", Countries: []string{"US", "AE"}, + }} + activeRegions := &userv1.ListRegionsResponse{Regions: []*userv1.Region{ + {RegionId: 1001, RegionCode: "ME", Status: "active", Countries: []string{"US", "AE"}}, + {RegionId: 2002, RegionCode: "SEA", Status: "active", Countries: []string{"BD", "PK"}}, + }} + registrationCountries := &userv1.ListRegistrationCountriesResponse{Countries: []*userv1.Country{ + {CountryId: 1, CountryCode: "US"}, + {CountryId: 2, CountryCode: "AE"}, + {CountryId: 3, CountryCode: "BD"}, + {CountryId: 4, CountryCode: "PK"}, + }} + + t.Run("all", func(t *testing.T) { + regionClient := &fakeUserRegionClient{resp: currentRegion} + queryClient := &fakeRoomQueryClient{} + router := newHandler(regionClient, &fakeUserCountryQueryClient{resp: registrationCountries}, queryClient) + request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms?tab=hot", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "huwaa")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if queryClient.lastList == nil || !queryClient.lastList.GetAllVisibleRegions() || queryClient.lastList.GetFilterRegionId() != 0 || queryClient.lastList.GetVisibleRegionId() != 1001 { + t.Fatalf("Huwaa All must preserve viewer region only as sort context and query every region: %+v", queryClient.lastList) + } + if regionClient.lastList != nil { + t.Fatalf("All needs no catalog validation RPC: %+v", regionClient.lastList) + } + }) + + t.Run("region", func(t *testing.T) { + regionClient := &fakeUserRegionClient{resp: currentRegion, listResp: activeRegions} + queryClient := &fakeRoomQueryClient{} + router := newHandler(regionClient, &fakeUserCountryQueryClient{resp: registrationCountries}, queryClient) + request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms?region_id=2002", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "huwaa")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if queryClient.lastList == nil || queryClient.lastList.GetAllVisibleRegions() || queryClient.lastList.GetFilterRegionId() != 2002 || queryClient.lastList.GetVisibleRegionId() != 2002 { + t.Fatalf("validated Huwaa region filter mismatch: %+v", queryClient.lastList) + } + if regionClient.lastList == nil || regionClient.lastList.GetStatus() != "active" { + t.Fatalf("region filter must resolve against active catalog: %+v", regionClient.lastList) + } + }) + + t.Run("country", func(t *testing.T) { + regionClient := &fakeUserRegionClient{resp: currentRegion, listResp: activeRegions} + queryClient := &fakeRoomQueryClient{} + router := newHandler(regionClient, &fakeUserCountryQueryClient{resp: registrationCountries}, queryClient) + request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms?country_code=BD", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "huwaa")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if queryClient.lastList == nil || !queryClient.lastList.GetAllVisibleRegions() || queryClient.lastList.GetCountryCode() != "BD" || queryClient.lastList.GetFilterRegionId() != 0 { + t.Fatalf("validated Huwaa country filter mismatch: %+v", queryClient.lastList) + } + }) + + t.Run("country_without_active_region_mapping", func(t *testing.T) { + regionClient := &fakeUserRegionClient{resp: currentRegion, listResp: activeRegions} + queryClient := &fakeRoomQueryClient{} + countries := &userv1.ListRegistrationCountriesResponse{Countries: append(append([]*userv1.Country{}, registrationCountries.GetCountries()...), &userv1.Country{CountryId: 5, CountryCode: "CN"})} + router := newHandler(regionClient, &fakeUserCountryQueryClient{resp: countries}, queryClient) + request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms?country_code=CN", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "huwaa")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusBadRequest || queryClient.lastList != nil { + t.Fatalf("unmapped country must be rejected before room-service: status=%d req=%+v body=%s", recorder.Code, queryClient.lastList, recorder.Body.String()) + } + }) + + t.Run("region_outside_active_catalog", func(t *testing.T) { + regionClient := &fakeUserRegionClient{resp: currentRegion, listResp: activeRegions} + queryClient := &fakeRoomQueryClient{} + router := newHandler(regionClient, &fakeUserCountryQueryClient{resp: registrationCountries}, queryClient) + request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms?region_id=9999", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "huwaa")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusBadRequest || queryClient.lastList != nil { + t.Fatalf("unknown region must be rejected before room-service: status=%d req=%+v body=%s", recorder.Code, queryClient.lastList, recorder.Body.String()) + } + }) +} + +func TestListRoomsHuwaaAllowsAllAndCatalogFiltersWithoutViewerRegion(t *testing.T) { + activeRegions := &userv1.ListRegionsResponse{Regions: []*userv1.Region{ + {RegionId: 2002, RegionCode: "SEA", Status: "active", Countries: []string{"BD"}}, + }} + registrationCountries := &userv1.ListRegistrationCountriesResponse{Countries: []*userv1.Country{ + {CountryId: 3, CountryCode: "BD"}, + }} + tests := []struct { + name string + path string + wantAll bool + wantRegion int64 + wantVisibleRegion int64 + wantCountry string + }{ + {name: "all", path: "/api/v1/rooms", wantAll: true}, + {name: "region", path: "/api/v1/rooms?region_id=2002", wantRegion: 2002, wantVisibleRegion: 2002}, + {name: "country", path: "/api/v1/rooms?country_code=BD", wantAll: true, wantCountry: "BD"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + profileClient := &fakeUserProfileClient{usersByID: map[int64]*userv1.User{ + 42: {UserId: 42, Country: "US", RegionId: 0}, + }} + regionClient := &fakeUserRegionClient{listResp: activeRegions} + countryClient := &fakeUserCountryQueryClient{resp: registrationCountries} + queryClient := &fakeRoomQueryClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient) + handler.SetRoomQueryClient(queryClient) + handler.SetUserRegionClient(regionClient) + handler.SetUserCountryQueryClient(countryClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodGet, tt.path, nil) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "huwaa")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if queryClient.lastList == nil || queryClient.lastList.GetVisibleRegionId() != tt.wantVisibleRegion || queryClient.lastList.GetAllVisibleRegions() != tt.wantAll || queryClient.lastList.GetFilterRegionId() != tt.wantRegion || queryClient.lastList.GetCountryCode() != tt.wantCountry { + t.Fatalf("regionless Huwaa list scope mismatch: %+v", queryClient.lastList) + } + if regionClient.last != nil { + t.Fatalf("regionless Huwaa user must not trigger GetRegion or guess a legacy region: %+v", regionClient.last) + } + var envelope httpkit.ResponseEnvelope + if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { + t.Fatalf("decode room list response failed: %v", err) + } + if rawCountries, exists := envelope.Data.(map[string]any)["countries"]; exists { + if countries, ok := rawCountries.([]any); !ok || len(countries) != 0 { + t.Fatalf("regionless Huwaa legacy countries must stay empty: %+v", rawCountries) + } + } + }) + } +} + +func TestListRoomsRejectsConflictingFiltersAndKeepsNonHuwaaRegionIsolation(t *testing.T) { + t.Run("conflicting_huwaa_filters", func(t *testing.T) { + queryClient := &fakeRoomQueryClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{regionID: 1001}) + handler.SetRoomQueryClient(queryClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms?region_id=2002&country_code=BD", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "huwaa")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusBadRequest || queryClient.lastList != nil { + t.Fatalf("conflicting filters must fail before upstream calls: status=%d req=%+v body=%s", recorder.Code, queryClient.lastList, recorder.Body.String()) + } + }) + + t.Run("non_huwaa_region_filter", func(t *testing.T) { + profileClient := &fakeUserProfileClient{regionID: 1001} + regionClient := &fakeUserRegionClient{resp: &userv1.RegionResponse{Region: &userv1.Region{RegionId: 1001, Countries: []string{"US"}}}} + countryClient := &fakeUserCountryQueryClient{resp: &userv1.ListRegistrationCountriesResponse{Countries: []*userv1.Country{{CountryId: 1, CountryCode: "US"}}}} + queryClient := &fakeRoomQueryClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient) + handler.SetRoomQueryClient(queryClient) + handler.SetUserRegionClient(regionClient) + handler.SetUserCountryQueryClient(countryClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms?region_id=2002", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "lalu")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusBadRequest || queryClient.lastList != nil { + t.Fatalf("non-Huwaa region filter must not bypass current-region isolation: status=%d req=%+v body=%s", recorder.Code, queryClient.lastList, recorder.Body.String()) + } + }) + + t.Run("non_huwaa_missing_viewer_region", func(t *testing.T) { + profileClient := &fakeUserProfileClient{usersByID: map[int64]*userv1.User{ + 42: {UserId: 42, Country: "US", RegionId: 0}, + }} + regionClient := &fakeUserRegionClient{} + countryClient := &fakeUserCountryQueryClient{} + queryClient := &fakeRoomQueryClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient) + handler.SetRoomQueryClient(queryClient) + handler.SetUserRegionClient(regionClient) + handler.SetUserCountryQueryClient(countryClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "lalu")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusBadRequest || queryClient.lastList != nil || regionClient.last != nil || countryClient.last != nil { + t.Fatalf("non-Huwaa missing region must keep the previous fail-closed behavior: status=%d req=%+v body=%s", recorder.Code, queryClient.lastList, recorder.Body.String()) + } + }) +} + func TestGetRoomRocketEmbedsRewardResourceGroups(t *testing.T) { queryClient := &fakeRoomQueryClient{rocketResp: &roomv1.GetRoomRocketResponse{ Rocket: &roomv1.RoomRocketInfo{ @@ -4196,6 +4625,36 @@ func TestReportAppEventsRejectsInvalidRequests(t *testing.T) { } } +func TestReportAppEventsRejects185ByteErrorCodeBeforeDownstream(t *testing.T) { + statisticsClient := &fakeStatisticsClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + handler.SetStatisticsClient(statisticsClient) + router := handler.Routes(auth.NewVerifier("secret")) + body, err := json.Marshal(map[string]any{ + "events": []map[string]any{{ + "event_id": "evt-error-code-too-long", + "event_name": "pay_failed", + "device_id": "dev-1", + "error_code": strings.Repeat("e", 185), + }}, + }) + if err != nil { + t.Fatalf("marshal app events request: %v", err) + } + request := httptest.NewRequest(http.MethodPost, "/api/v1/app/events", bytes.NewReader(body)) + request.Header.Set("X-Request-ID", "req-app-events-error-code-limit") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + // Regression: statistics-service used to reject this after forwarding and + // gateway translated that downstream 400 into a misleading 502. + assertEnvelope(t, recorder, http.StatusBadRequest, httpkit.CodeInvalidArgument, "req-app-events-error-code-limit") + if len(statisticsClient.lastAppEvents) != 0 { + t.Fatalf("oversized error_code must not reach statistics-service: %+v", statisticsClient.lastAppEvents) + } +} + func TestReportAppEventsUpstreamFailure(t *testing.T) { statisticsClient := &fakeStatisticsClient{appErr: errors.New("statistics unavailable")} handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) @@ -6856,6 +7315,9 @@ func TestThirdPartyLoginUsesPublicEnvelopeAndPropagatesRequestID(t *testing.T) { if req.GetMeta().GetRequestId() != generatedRequestID || req.GetMeta().GetClientIp() != "203.0.113.10" || req.GetMeta().GetUserAgent() != "hyapp-test/1.0" || req.GetMeta().GetCountryByIp() != "SG" { t.Fatalf("auth meta was not propagated from gateway context: %+v", req.GetMeta()) } + if req.GetMeta().GetAppVersion() != "1.2.3" || req.GetMeta().GetBuildNumber() != "123" { + t.Fatalf("third-party body version fallback was not propagated into auth meta: %+v", req.GetMeta()) + } if req.GetUsername() != "hy" || req.GetInviteCode() != "INV-1" || req.GetDeviceId() != "dev-1" { t.Fatalf("third-party registration fields were not propagated: %+v", req) } @@ -6879,6 +7341,8 @@ func TestThirdPartyRegisterUsesPublicEnvelopeAndReturnsCompletedToken(t *testing request.Header.Set("X-Forwarded-For", "203.0.113.10") request.Header.Set("User-Agent", "hyapp-test/1.0") request.Header.Set("CF-IPCountry", "sg") + request.Header.Set("X-App-Version", "2.0.0") + request.Header.Set("X-App-Build-Number", "200") recorder := httptest.NewRecorder() router.ServeHTTP(recorder, request) @@ -6908,6 +7372,9 @@ func TestThirdPartyRegisterUsesPublicEnvelopeAndReturnsCompletedToken(t *testing if req.GetMeta().GetRequestId() != generatedRequestID || req.GetMeta().GetClientIp() != "203.0.113.10" || req.GetMeta().GetCountryByIp() != "SG" { t.Fatalf("auth meta was not propagated from gateway context: %+v", req.GetMeta()) } + if req.GetMeta().GetAppVersion() != "2.0.0" || req.GetMeta().GetBuildNumber() != "200" || req.GetAppVersion() != "2.0.0" || req.GetBuildNumber() != "200" { + t.Fatalf("register version headers must override body values in both auth meta and registration fields: %+v meta=%+v", req, req.GetMeta()) + } if req.GetProvider() != "firebase" || req.GetCredential() != "firebase-id-token-1" || req.GetDeviceId() != "dev-1" { t.Fatalf("provider credential device fields mismatch: %+v", req) } @@ -7026,9 +7493,11 @@ func TestAccountLoginRateLimitByAccount(t *testing.T) { func TestAccountLoginAcceptsAccountField(t *testing.T) { userClient := &fakeUserAuthClient{} router := NewHandler(&fakeRoomClient{}, userClient).Routes(auth.NewVerifier("secret")) - body := []byte(`{"account":"123456","password":"1234567","device_id":"ios","platform":"ios","language":"en-US","timezone":"America/Los_Angeles"}`) + body := []byte(`{"account":"123456","password":"1234567","device_id":"ios","app_version":"1.2.3","build_number":"123","platform":"ios","language":"en-US","timezone":"America/Los_Angeles"}`) request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/account/login", bytes.NewReader(body)) request.Header.Set("X-Request-ID", "req-account-login") + request.Header.Set("X-App-Version", "2.0.0") + request.Header.Set("X-App-Build-Number", "200") recorder := httptest.NewRecorder() router.ServeHTTP(recorder, request) @@ -7056,6 +7525,9 @@ func TestAccountLoginAcceptsAccountField(t *testing.T) { if userClient.lastLoginPassword.GetMeta().GetPlatform() != "ios" || userClient.lastLoginPassword.GetMeta().GetLanguage() != "en-US" || userClient.lastLoginPassword.GetMeta().GetTimezone() != "America/Los_Angeles" { t.Fatalf("password login risk meta mismatch: %+v", userClient.lastLoginPassword.GetMeta()) } + if userClient.lastLoginPassword.GetMeta().GetAppVersion() != "2.0.0" || userClient.lastLoginPassword.GetMeta().GetBuildNumber() != "200" { + t.Fatalf("password login version headers must override body fallback: %+v", userClient.lastLoginPassword.GetMeta()) + } } func TestQuickCreateAccountCreatesCompletedPasswordAccount(t *testing.T) { @@ -7065,6 +7537,8 @@ func TestQuickCreateAccountCreatesCompletedPasswordAccount(t *testing.T) { request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/account/quick-create", bytes.NewReader(body)) request.Header.Set("X-Request-ID", "req-quick-create") request.Header.Set("X-App-Code", "lalu") + request.Header.Set("X-App-Version", "3.0.0") + request.Header.Set("X-App-Build-Number", "300") recorder := httptest.NewRecorder() router.ServeHTTP(recorder, request) @@ -7095,6 +7569,9 @@ func TestQuickCreateAccountCreatesCompletedPasswordAccount(t *testing.T) { if req.GetMeta().GetAppCode() != "lalu" || req.GetMeta().GetDeviceId() != req.GetDeviceId() { t.Fatalf("quick create meta mismatch: %+v", req.GetMeta()) } + if req.GetMeta().GetAppVersion() != "3.0.0" || req.GetMeta().GetBuildNumber() != "300" || req.GetAppVersion() != "3.0.0" || req.GetBuildNumber() != "300" { + t.Fatalf("quick create version headers were not propagated consistently: %+v meta=%+v", req, req.GetMeta()) + } data, ok := response.Data.(map[string]any) if !ok || data["uid"] != "100001" || data["account"] != "100001" || data["access_token"] != "access-quick" || data["password_set"] != true { @@ -7255,6 +7732,27 @@ func TestRefreshRateLimitByDeviceID(t *testing.T) { } } +func TestRefreshPropagatesCurrentClientVersionWithHeaderPriority(t *testing.T) { + userClient := &fakeUserAuthClient{} + router := NewHandler(&fakeRoomClient{}, userClient).Routes(auth.NewVerifier("secret")) + body := []byte(`{"refresh_token":"refresh-1","device_id":"dev-1","app_version":"1.2.3","build_number":"123"}`) + request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/token/refresh", bytes.NewReader(body)) + request.Header.Set("X-Request-ID", "req-refresh-version") + request.Header.Set("X-App-Version", "2.0.0") + request.Header.Set("X-App-Build-Number", "200") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: got=%d body=%s", recorder.Code, recorder.Body.String()) + } + meta := userClient.lastRefresh.GetMeta() + if meta.GetAppVersion() != "2.0.0" || meta.GetBuildNumber() != "200" { + t.Fatalf("refresh client version mismatch: %+v", meta) + } +} + func TestLogoutRateLimitBySessionIDAndIP(t *testing.T) { userClient := &fakeUserAuthClient{} handler := NewHandler(&fakeRoomClient{}, userClient) @@ -11177,7 +11675,8 @@ func TestTencentRTCTokenFailsClosedForConfigAndUpstreamErrors(t *testing.T) { func TestTencentRTCCallbackVerifiesSignatureAndAppliesEvent(t *testing.T) { roomClient := &fakeRoomClient{} - handler := NewHandlerWithConfig(roomClient, &fakeRoomGuardClient{}, nil, nil, TencentIMConfig{}) + guard := &fakeRoomGuardClient{roomAppResp: &roomv1.ResolveRoomAppCodeResponse{AppCode: "fami"}} + handler := NewHandlerWithConfig(roomClient, guard, nil, nil, TencentIMConfig{}) handler.SetTencentRTC(TencentRTCConfig{ SDKAppID: 1400000000, CallbackSignKey: "callback-key", @@ -11200,9 +11699,12 @@ func TestTencentRTCCallbackVerifiesSignatureAndAppliesEvent(t *testing.T) { t.Fatalf("RTC event fields mismatch: %+v", roomClient.lastRTCEvent) } meta := roomClient.lastRTCEvent.GetMeta() - if meta.GetRoomId() != "room_1001" || meta.GetActorUserId() != 42 || meta.GetRequestId() != assertGeneratedRequestID(t, recorder, "req-rtc-callback") || meta.GetAppCode() != "lalu" { + if meta.GetRoomId() != "room_1001" || meta.GetActorUserId() != 42 || meta.GetRequestId() != assertGeneratedRequestID(t, recorder, "req-rtc-callback") || meta.GetAppCode() != "fami" { t.Fatalf("RTC event meta mismatch: %+v", meta) } + if guard.lastRoomApp == nil || guard.lastRoomApp.GetRoomId() != "room_1001" { + t.Fatalf("RTC callback must resolve app from room owner: %+v", guard.lastRoomApp) + } if !strings.HasPrefix(meta.GetCommandId(), "cmd_") || !strings.HasPrefix(roomClient.lastRTCEvent.GetExternalEventId(), "rtc_evt_") { t.Fatalf("RTC callback must derive stable ids: meta=%+v event=%+v", meta, roomClient.lastRTCEvent) } diff --git a/services/gateway-service/internal/transport/http/room_filter_handler_test.go b/services/gateway-service/internal/transport/http/room_filter_handler_test.go new file mode 100644 index 00000000..1efe9e8a --- /dev/null +++ b/services/gateway-service/internal/transport/http/room_filter_handler_test.go @@ -0,0 +1,148 @@ +package http + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + userv1 "hyapp.local/api/proto/user/v1" + "hyapp/services/gateway-service/internal/auth" + "hyapp/services/gateway-service/internal/transport/http/httpkit" +) + +func TestListRoomFiltersReturnsAllAppScopedFiltersForHuwaa(t *testing.T) { + regionClient := &fakeUserRegionClient{listResp: &userv1.ListRegionsResponse{Regions: []*userv1.Region{ + {RegionId: 1001, RegionCode: "MIDDLE_EAST", Name: "Middle East", Status: "active", Countries: []string{"AE", "SA"}, SortOrder: 10}, + {RegionId: 1002, RegionCode: "SOUTH_ASIA", Name: "South Asia", Status: "active", Countries: []string{"PK"}, SortOrder: 20}, + // 即使上游违反 status 过滤契约返回禁用区域,gateway 也不能把不可用筛选值下发给 Flutter。 + {RegionId: 1003, RegionCode: "INDIA", Name: "India", Status: "disabled", Countries: []string{"IN"}, SortOrder: 30}, + }}} + countryClient := &fakeUserCountryQueryClient{resp: &userv1.ListRegistrationCountriesResponse{Countries: []*userv1.Country{ + {CountryId: 971, CountryCode: "ae", CountryName: "United Arab Emirates", CountryDisplayName: "UAE", Flag: "🇦🇪", SortOrder: 10}, + {CountryId: 966, CountryCode: "SA", CountryName: "Saudi Arabia", CountryDisplayName: "Saudi Arabia", SortOrder: 20}, + {CountryId: 92, CountryCode: "PK", CountryName: "Pakistan", CountryDisplayName: "Pakistan", Flag: "🇵🇰", SortOrder: 30}, + // Enabled 但未配置 active 业务区域的国家不能成为可回传筛选项。 + {CountryId: 95, CountryCode: "MM", CountryName: "Myanmar", CountryDisplayName: "Myanmar", Flag: "🇲🇲", SortOrder: 40}, + {CountryId: 91, CountryCode: "IN", CountryName: "India", CountryDisplayName: "India", Flag: "🇮🇳", SortOrder: 50}, + }}} + profileClient := &fakeUserProfileClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient) + handler.SetUserRegionClient(regionClient) + handler.SetUserCountryQueryClient(countryClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms/filters", nil) + request.Header.Set("X-App-Code", "huwaa") + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "huwaa")) + request.Header.Set("X-Request-ID", "req-room-filters") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if regionClient.lastList == nil || regionClient.lastList.GetStatus() != "active" || regionClient.lastList.GetMeta().GetAppCode() != "huwaa" { + t.Fatalf("region query must be active and app-scoped: %+v", regionClient.lastList) + } + if countryClient.last == nil || countryClient.last.GetMeta().GetAppCode() != "huwaa" { + t.Fatalf("country query must use the same app scope: %+v", countryClient.last) + } + if profileClient.lastGet != nil || regionClient.last != nil { + t.Fatalf("Huwaa catalog must read all active regions without narrowing to the viewer: profile=%+v region=%+v", profileClient.lastGet, regionClient.last) + } + + var response httpkit.ResponseEnvelope + if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil { + t.Fatalf("decode room filter response failed: %v", err) + } + if response.Code != httpkit.CodeOK { + t.Fatalf("unexpected envelope: %+v", response) + } + data := response.Data.(map[string]any) + all := data["all"].(map[string]any) + if all["filter_type"] != "all" || all["query_key"] != "" || all["query_value"] != "" { + t.Fatalf("all filter mismatch: %+v", all) + } + regions := data["regions"].([]any) + if len(regions) != 2 || regions[0].(map[string]any)["region_id"] != float64(1001) || regions[0].(map[string]any)["query_key"] != "region_id" || regions[0].(map[string]any)["query_value"] != "1001" || regions[1].(map[string]any)["region_code"] != "SOUTH_ASIA" { + t.Fatalf("region filters mismatch: %+v", regions) + } + countries := data["countries"].([]any) + if len(countries) != 3 { + t.Fatalf("country filters mismatch: %+v", countries) + } + ae := countries[0].(map[string]any) + if ae["filter_type"] != "country" || ae["query_key"] != "country_code" || ae["query_value"] != "AE" || ae["country_code"] != "AE" || ae["parent_region_id"] != float64(1001) || ae["country_flag"] != "🇦🇪" { + t.Fatalf("mapped country filter mismatch: %+v", ae) + } + sa := countries[1].(map[string]any) + if sa["country_flag"] != "🇸🇦" { + t.Fatalf("empty upstream flag must use ISO flag fallback: %+v", sa) + } + for _, item := range countries { + if code := item.(map[string]any)["country_code"]; code == "MM" || code == "IN" { + t.Fatalf("country without an active region mapping must not become a filter: %+v", countries) + } + } +} + +func TestListRoomFiltersKeepsNonHuwaaInsideCurrentUserRegion(t *testing.T) { + profileClient := &fakeUserProfileClient{regionID: 1002} + regionClient := &fakeUserRegionClient{resp: &userv1.RegionResponse{Region: &userv1.Region{ + RegionId: 1002, RegionCode: "SOUTH_ASIA", Name: "South Asia", Status: "active", Countries: []string{"PK"}, SortOrder: 20, + }}} + countryClient := &fakeUserCountryQueryClient{resp: &userv1.ListRegistrationCountriesResponse{Countries: []*userv1.Country{ + {CountryId: 971, CountryCode: "AE", CountryName: "United Arab Emirates"}, + {CountryId: 92, CountryCode: "PK", CountryName: "Pakistan", Flag: "🇵🇰"}, + }}} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient) + handler.SetUserRegionClient(regionClient) + handler.SetUserCountryQueryClient(countryClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms/filters", nil) + request.Header.Set("X-App-Code", "lalu") + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if regionClient.lastList != nil || profileClient.lastGet == nil || profileClient.lastGet.GetUserId() != 42 || regionClient.last == nil || regionClient.last.GetRegionId() != 1002 { + t.Fatalf("non-Huwaa catalog must resolve only the viewer region: list=%+v user=%+v region=%+v", regionClient.lastList, profileClient.lastGet, regionClient.last) + } + var response httpkit.ResponseEnvelope + if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil { + t.Fatalf("decode room filter response failed: %v", err) + } + data := response.Data.(map[string]any) + regions := data["regions"].([]any) + countries := data["countries"].([]any) + if len(regions) != 1 || regions[0].(map[string]any)["region_id"] != float64(1002) || len(countries) != 1 || countries[0].(map[string]any)["country_code"] != "PK" { + t.Fatalf("non-Huwaa filter scope leaked another region or country: regions=%+v countries=%+v", regions, countries) + } +} + +func TestListRoomFiltersRejectsNonGETWithoutCallingUserService(t *testing.T) { + regionClient := &fakeUserRegionClient{} + countryClient := &fakeUserCountryQueryClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + handler.SetUserRegionClient(regionClient) + handler.SetUserCountryQueryClient(countryClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/filters", nil) + request.Header.Set("X-App-Code", "huwaa") + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "huwaa")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusMethodNotAllowed { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if regionClient.lastList != nil || countryClient.last != nil { + t.Fatalf("invalid method must not call user-service: region=%+v country=%+v", regionClient.lastList, countryClient.last) + } +} diff --git a/services/gateway-service/internal/transport/http/roomapi/gift_capacity.go b/services/gateway-service/internal/transport/http/roomapi/gift_capacity.go new file mode 100644 index 00000000..f20be552 --- /dev/null +++ b/services/gateway-service/internal/transport/http/roomapi/gift_capacity.go @@ -0,0 +1,75 @@ +package roomapi + +import ( + "context" + "net/http" + "strconv" + "time" + + "hyapp/pkg/appcode" + "hyapp/services/gateway-service/internal/auth" + "hyapp/services/gateway-service/internal/transport/http/giftlimit" + "hyapp/services/gateway-service/internal/transport/http/httpkit" +) + +const giftCapacityReleaseTimeout = time.Second + +type giftRateLimitedData struct { + RetryAfterMS int64 `json:"retry_after_ms"` +} + +// acquireGiftCapacity 在任何用户资料或 room-service RPC 前占用容量。 +// 返回的 lease 必须 defer 释放;令牌代表本次请求已经给下游造成的成本,不因后续业务失败退还。 +func (h *Handler) acquireGiftCapacity(writer http.ResponseWriter, request *http.Request, roomID string, totalGiftUnits int) (giftlimit.Lease, bool) { + if !h.giftCapacityEnabled { + return giftlimit.Lease{}, true + } + if h.giftCapacityLimiter == nil { + // 生产配置要求保护默认开启;后端缺失时 fail-closed,不能在 Redis/装配故障时静默放开洪峰。 + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return giftlimit.Lease{}, false + } + if totalGiftUnits < 1 { + totalGiftUnits = 1 + } + + decision, err := h.giftCapacityLimiter.Acquire(request.Context(), giftlimit.Input{ + AppCode: appcode.FromContext(request.Context()), + UserID: auth.UserIDFromContext(request.Context()), + RoomID: roomID, + RequestID: httpkit.ResponseRequestID(request), + Weight: totalGiftUnits, + }) + if err != nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return giftlimit.Lease{}, false + } + if decision.Allowed { + return decision.Lease, true + } + + retryAfter := decision.RetryAfter + if retryAfter < time.Millisecond { + retryAfter = time.Millisecond + } + retryAfterMS := retryAfter.Milliseconds() + // Retry-After 是标准 HTTP 秒级提示;data.retry_after_ms 保留连击送礼所需的毫秒精度。 + writer.Header().Set("Retry-After", strconv.FormatInt((retryAfterMS+999)/1000, 10)) + httpkit.WriteEnvelope(writer, http.StatusTooManyRequests, httpkit.ResponseEnvelope{ + Code: httpkit.CodeRateLimited, + Message: "rate limited", + RequestID: httpkit.ResponseRequestID(request), + Data: giftRateLimitedData{RetryAfterMS: retryAfterMS}, + }) + return giftlimit.Lease{}, false +} + +// releaseGiftCapacity 使用与客户端断连解耦的短 context;Redis 暂时失败时租约 TTL 仍会兜底回收。 +func (h *Handler) releaseGiftCapacity(lease giftlimit.Lease) { + if h.giftCapacityLimiter == nil { + return + } + ctx, cancel := context.WithTimeout(context.Background(), giftCapacityReleaseTimeout) + defer cancel() + _ = h.giftCapacityLimiter.Release(ctx, lease) +} diff --git a/services/gateway-service/internal/transport/http/roomapi/gift_combo_policy.go b/services/gateway-service/internal/transport/http/roomapi/gift_combo_policy.go new file mode 100644 index 00000000..a2f1f84f --- /dev/null +++ b/services/gateway-service/internal/transport/http/roomapi/gift_combo_policy.go @@ -0,0 +1,52 @@ +package roomapi + +import ( + "context" + "crypto/sha256" + "encoding/binary" + "fmt" + "strings" + + "hyapp/services/gateway-service/internal/appconfig" +) + +type giftComboConfigReader interface { + GiftComboConfig(ctx context.Context, appCode string) (appconfig.GiftComboConfig, error) +} + +// roomGiftComboConfig resolves rollout on the server so every device logged in as the same App user +// receives the same policy. The client does not need to reproduce a hashing algorithm or trust a +// locally generated random bucket across reinstallations. +func (h *Handler) roomGiftComboConfig(ctx context.Context, appCode string, userID int64) (appconfig.GiftComboConfig, error) { + config := appconfig.DefaultGiftComboConfig() + reader := h.giftComboReader + if reader != nil { + var err error + config, err = reader.GiftComboConfig(ctx, appCode) + if err != nil { + return disabledGiftComboFallback(), err + } + } + + assigned := giftComboRolloutBucket(appCode, userID) < uint32(config.RolloutBasisPoints) + // 灰度和 enabled 只控制客户端是否把连续点击合成微批;当前 Flutter 的账务入口始终是 + // /api/v2/rooms/gift/send。旧客户端是否使用 V1 由其已发布代码决定,不能通过面板配置 + // 把新客户端切回旧财务协议。 + config.Enabled = config.Enabled && assigned + config.APIVersion = 2 + return config, nil +} + +func disabledGiftComboFallback() appconfig.GiftComboConfig { + config := appconfig.DefaultGiftComboConfig() + config.Enabled = false + // 配置读取失败只关闭微批,避免未知策略放大请求;接口版本仍固定为 V2。 + config.APIVersion = 2 + return config +} + +func giftComboRolloutBucket(appCode string, userID int64) uint32 { + identity := fmt.Sprintf("%s:%d", strings.ToLower(strings.TrimSpace(appCode)), userID) + digest := sha256.Sum256([]byte(identity)) + return binary.BigEndian.Uint32(digest[:4]) % 10_000 +} diff --git a/services/gateway-service/internal/transport/http/roomapi/gift_combo_policy_test.go b/services/gateway-service/internal/transport/http/roomapi/gift_combo_policy_test.go new file mode 100644 index 00000000..1088d53f --- /dev/null +++ b/services/gateway-service/internal/transport/http/roomapi/gift_combo_policy_test.go @@ -0,0 +1,44 @@ +package roomapi + +import ( + "context" + "errors" + "testing" + + "hyapp/services/gateway-service/internal/appconfig" +) + +type fakeGiftComboConfigReader struct { + config appconfig.GiftComboConfig + err error +} + +func (f fakeGiftComboConfigReader) GiftComboConfig(context.Context, string) (appconfig.GiftComboConfig, error) { + return f.config, f.err +} + +func TestRoomGiftComboConfigAssignsStableRolloutAndDisablesBatchingForHoldback(t *testing.T) { + config := appconfig.DefaultGiftComboConfig() + bucket := giftComboRolloutBucket("lalu", 42) + config.RolloutBasisPoints = int32(bucket) + handler := &Handler{giftComboReader: fakeGiftComboConfigReader{config: config}} + + resolved, err := handler.roomGiftComboConfig(context.Background(), "lalu", 42) + if err != nil { + t.Fatalf("roomGiftComboConfig failed: %v", err) + } + if resolved.Enabled || resolved.APIVersion != 2 { + t.Fatalf("holdback must disable only micro-batching while preserving the V2 contract: %+v", resolved) + } + if got := giftComboRolloutBucket(" LALU ", 42); got != bucket { + t.Fatalf("rollout bucket must be normalized and stable: got=%d want=%d", got, bucket) + } +} + +func TestRoomGiftComboConfigReadFailureDisablesBatching(t *testing.T) { + handler := &Handler{giftComboReader: fakeGiftComboConfigReader{err: errors.New("admin db unavailable")}} + resolved, err := handler.roomGiftComboConfig(context.Background(), "lalu", 42) + if err == nil || resolved.Enabled || resolved.APIVersion != 2 || resolved.MaxPendingBatches <= 0 { + t.Fatalf("remote read outage must disable batching without downgrading the V2 contract: config=%+v err=%v", resolved, err) + } +} diff --git a/services/gateway-service/internal/transport/http/roomapi/gift_panel_pipeline.go b/services/gateway-service/internal/transport/http/roomapi/gift_panel_pipeline.go new file mode 100644 index 00000000..d0b4fe5a --- /dev/null +++ b/services/gateway-service/internal/transport/http/roomapi/gift_panel_pipeline.go @@ -0,0 +1,314 @@ +package roomapi + +import ( + "context" + "errors" + "log/slog" + "net/http" + "strings" + "sync" + "time" + + roomv1 "hyapp.local/api/proto/room/v1" + userv1 "hyapp.local/api/proto/user/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/logx" + "hyapp/pkg/roomid" + "hyapp/services/gateway-service/internal/appconfig" + "hyapp/services/gateway-service/internal/auth" + "hyapp/services/gateway-service/internal/transport/http/httpkit" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const ( + // Flutter 在 15 秒终止请求;gateway 留出 5 秒给 DNS/TLS/传输和客户端解码,必须先返回带 request_id 的稳定错误。 + roomGiftPanelTotalBudget = 10 * time.Second + // 用户主资料是收礼人头像昵称的来源,单独给足正常批量查询时间,但不允许占满整个面板预算。 + roomGiftPanelRecipientCoreBudget = 1500 * time.Millisecond +) + +type roomGiftPanelCatalogState struct { + version int64 + cacheable bool +} + +type roomGiftPanelStageResult[T any] struct { + value T + degraded bool + err error +} + +// startRoomGiftPanelStage 只负责请求内并发和观测;业务 owner 仍由各下游服务保持。 +// 结果通道有缓冲,handler 因总预算先返回时,已响应的 gRPC goroutine 不会卡在投递结果上。 +func startRoomGiftPanelStage[T any](group *sync.WaitGroup, ctx context.Context, requestID string, roomID string, stage string, fn func() (T, bool, error)) <-chan roomGiftPanelStageResult[T] { + resultCh := make(chan roomGiftPanelStageResult[T], 1) + group.Add(1) + go func() { + defer group.Done() + startedAt := time.Now() + value, degraded, err := fn() + logRoomGiftPanelStage(ctx, requestID, roomID, stage, startedAt, err, degraded) + resultCh <- roomGiftPanelStageResult[T]{value: value, degraded: degraded, err: err} + }() + return resultCh +} + +func waitRoomGiftPanelStage[T any](ctx context.Context, resultCh <-chan roomGiftPanelStageResult[T]) (T, bool, error) { + select { + case result := <-resultCh: + return result.value, result.degraded, result.err + case <-ctx.Done(): + var zero T + return zero, false, ctx.Err() + } +} + +func logRoomGiftPanelStage(ctx context.Context, requestID string, roomID string, stage string, startedAt time.Time, err error, degraded bool) { + logCtx := logx.With(ctx, + slog.String("request_id", requestID), + slog.String("room_id", roomID), + slog.String("app_code", appcode.FromContext(ctx)), + ) + attrs := []slog.Attr{ + slog.String("stage", stage), + slog.Int64("duration_ms", time.Since(startedAt).Milliseconds()), + slog.Bool("success", err == nil), + slog.Bool("degraded", degraded), + } + switch { + case roomGiftPanelStageCanceled(err): + // 首个必要阶段失败后 handler 会取消仍在运行的 sibling RPC;这些 Canceled + // 只是请求内并发已被收敛,不是新的下游故障。保留 INFO 轨迹用于 request_id + // 关联,但不能为同一个主错误再制造多条 ERROR 告警。 + attrs = append(attrs, slog.String("error", err.Error()), slog.Bool("canceled", true)) + logx.Info(logCtx, "gateway_room_gift_panel_stage", attrs...) + case err != nil && degraded: + attrs = append(attrs, slog.String("error", err.Error())) + logx.Warn(logCtx, "gateway_room_gift_panel_stage", attrs...) + case err != nil: + logx.Error(logCtx, "gateway_room_gift_panel_stage", err, attrs...) + case degraded: + logx.Warn(logCtx, "gateway_room_gift_panel_stage", attrs...) + default: + logx.Info(logCtx, "gateway_room_gift_panel_stage", attrs...) + } +} + +func roomGiftPanelStageCanceled(err error) bool { + return err != nil && (errors.Is(err, context.Canceled) || status.Code(err) == codes.Canceled) +} + +// serveRoomGiftPanel 在单个明确预算内聚合 owner service 读模型。 +// 第一阶段只并发互不依赖的 RPC;依赖 visible_region_id 的礼物配置在快照返回后启动,避免猜测区域。 +func (h *Handler) serveRoomGiftPanel(writer http.ResponseWriter, request *http.Request) { + if h.roomQueryClient == nil || h.walletClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + roomID := strings.TrimSpace(request.PathValue("room_id")) + if !roomid.ValidStringID(roomID) { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + + totalStartedAt := time.Now() + requestID := httpkit.RequestIDFromContext(request.Context()) + totalCtx, cancel := context.WithTimeout(request.Context(), h.roomGiftPanelTotalTimeout()) + var stageGroup sync.WaitGroup + defer func() { + // 先取消所有尚未完成的 RPC,再等待 request-scoped worker 退出;HTTP handler 返回时不遗留后台访问。 + cancel() + stageGroup.Wait() + }() + request = request.WithContext(totalCtx) + viewerUserID := auth.UserIDFromContext(totalCtx) + app := appcode.FromContext(totalCtx) + + snapshotCh := startRoomGiftPanelStage(&stageGroup, totalCtx, requestID, roomID, "snapshot", func() (*roomv1.GetRoomSnapshotResponse, bool, error) { + resp, err := h.roomQueryClient.GetRoomSnapshot(totalCtx, &roomv1.GetRoomSnapshotRequest{ + Meta: httpkit.RoomMeta(request, roomID, ""), + RoomId: roomID, + ViewerUserId: viewerUserID, + }) + return resp, false, err + }) + balanceCh := startRoomGiftPanelStage(&stageGroup, totalCtx, requestID, roomID, "balance", func() (*walletv1.GetBalancesResponse, bool, error) { + resp, err := h.walletClient.GetBalances(totalCtx, &walletv1.GetBalancesRequest{ + RequestId: requestID, + UserId: viewerUserID, + AssetTypes: []string{"COIN"}, + AppCode: app, + }) + return resp, false, err + }) + catalogCh := startRoomGiftPanelStage(&stageGroup, totalCtx, requestID, roomID, "catalog", func() (roomGiftPanelCatalogState, bool, error) { + version, cacheable, err := h.roomGiftCatalogVersion(request, app) + return roomGiftPanelCatalogState{version: version, cacheable: cacheable}, false, err + }) + bagCh := startRoomGiftPanelStage(&stageGroup, totalCtx, requestID, roomID, "bag", func() (*walletv1.ListUserResourcesResponse, bool, error) { + resp, err := h.roomGiftBagResources(request, viewerUserID) + return resp, false, err + }) + comboConfigCh := startRoomGiftPanelStage(&stageGroup, totalCtx, requestID, roomID, "combo_config", func() (appconfig.GiftComboConfig, bool, error) { + config, err := h.roomGiftComboConfig(totalCtx, app, viewerUserID) + // Remote policy failure disables micro-batching without blocking the gift panel. The stage still + // returns the error so observability distinguishes an explicit kill switch from a database outage; + // 当前 Flutter 无论是否聚合都继续走 V2,不能因配置故障切回旧财务入口。 + return config, err != nil, err + }) + + snapshotResp, _, err := waitRoomGiftPanelStage(totalCtx, snapshotCh) + if err != nil { + h.writeRoomGiftPanelFailure(writer, request, requestID, roomID, totalStartedAt, err) + return + } + snapshot := snapshotResp.GetRoom() + recipientUserIDs := roomGiftRecipientUserIDs(snapshot) + profileCh := startRoomGiftPanelStage(&stageGroup, totalCtx, requestID, roomID, "recipient_profile", func() (map[int64]roomDisplayProfileData, bool, error) { + profiles, degraded := h.roomGiftRecipientProfileMap(request, requestID, roomID, recipientUserIDs) + return profiles, degraded, nil + }) + + catalog, _, err := waitRoomGiftPanelStage(totalCtx, catalogCh) + if err != nil { + h.writeRoomGiftPanelFailure(writer, request, requestID, roomID, totalStartedAt, err) + return + } + configCh := startRoomGiftPanelStage(&stageGroup, totalCtx, requestID, roomID, "config", func() (roomGiftPanelConfig, bool, error) { + config, err := h.roomGiftPanelBaseConfigForVersion(request, app, snapshot.GetVisibleRegionId(), catalog.version, catalog.cacheable) + return config, false, err + }) + + balances, _, err := waitRoomGiftPanelStage(totalCtx, balanceCh) + if err != nil { + h.writeRoomGiftPanelFailure(writer, request, requestID, roomID, totalStartedAt, err) + return + } + panelConfig, _, err := waitRoomGiftPanelStage(totalCtx, configCh) + if err != nil { + h.writeRoomGiftPanelFailure(writer, request, requestID, roomID, totalStartedAt, err) + return + } + bagResources, _, err := waitRoomGiftPanelStage(totalCtx, bagCh) + if err != nil { + h.writeRoomGiftPanelFailure(writer, request, requestID, roomID, totalStartedAt, err) + return + } + recipientProfiles, _, err := waitRoomGiftPanelStage(totalCtx, profileCh) + if err != nil { + // 核心资料的 1.5 秒子预算会在内部降级;这里出现 error 只可能是整个 gift-panel 总预算已耗尽。 + h.writeRoomGiftPanelFailure(writer, request, requestID, roomID, totalStartedAt, err) + return + } + comboConfig, _, comboErr := waitRoomGiftPanelStage(totalCtx, comboConfigCh) + if comboErr != nil { + if totalCtx.Err() != nil { + h.writeRoomGiftPanelFailure(writer, request, requestID, roomID, totalStartedAt, comboErr) + return + } + comboConfig = disabledGiftComboFallback() + } + + panelGifts := append([]giftConfigData{}, panelConfig.Gifts...) + panelGifts = append(panelGifts, roomGiftBagGiftsFromResources(bagResources, panelConfig.Gifts)...) + data := roomGiftPanelData{ + CoinBalance: coinBalanceFromProto(balances), + Recipients: roomGiftRecipients(snapshot, recipientProfiles), + Tabs: roomGiftTabs(panelGifts, panelConfig.GiftTypes), + Gifts: panelGifts, + QuantityPresets: []int32{1, 9, 99, 999}, + ComboConfig: comboConfig, + } + logRoomGiftPanelStage(totalCtx, requestID, roomID, "total", totalStartedAt, nil, false) + httpkit.WriteOK(writer, request, data) +} + +func (h *Handler) writeRoomGiftPanelFailure(writer http.ResponseWriter, request *http.Request, requestID string, roomID string, startedAt time.Time, err error) { + logRoomGiftPanelStage(request.Context(), requestID, roomID, "total", startedAt, err, false) + if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) || status.Code(err) == codes.DeadlineExceeded || status.Code(err) == codes.Canceled || request.Context().Err() != nil { + // 明确在 Flutter 15 秒前结束 HTTP 响应;envelope 使用原请求上下文中的 request_id,便于从客户端反查阶段日志。 + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + httpkit.WriteRPCError(writer, request, err) +} + +func (h *Handler) roomGiftPanelTotalTimeout() time.Duration { + if h.giftPanelTotalBudget > 0 { + return h.giftPanelTotalBudget + } + return roomGiftPanelTotalBudget +} + +func (h *Handler) roomGiftPanelRecipientCoreTimeout() time.Duration { + if h.giftPanelRecipientCoreBudget > 0 { + return h.giftPanelRecipientCoreBudget + } + return roomGiftPanelRecipientCoreBudget +} + +// roomGiftRecipientProfileMap 只读取 Flutter gift-panel 实际消费的头像昵称核心资料。 +// 装扮、徽章和等级字段维持空对象兼容旧 JSON 形状,但不再发起 activity/wallet enrich RPC; +// 这些资料属于用户卡片而非送礼决策,删除整条 N+1 链路比等待后再超时降级更可靠。 +func (h *Handler) roomGiftRecipientProfileMap(request *http.Request, requestID string, roomID string, userIDs []int64) (map[int64]roomDisplayProfileData, bool) { + userIDs = uniquePositiveUserIDs(userIDs) + if len(userIDs) == 0 { + return nil, false + } + if h.userProfileClient == nil { + return nil, true + } + + coreCtx, cancelCore := context.WithTimeout(request.Context(), h.roomGiftPanelRecipientCoreTimeout()) + defer cancelCore() + coreRequest := request.WithContext(coreCtx) + startedAt := time.Now() + resp, err := h.userProfileClient.BatchGetUsers(coreCtx, &userv1.BatchGetUsersRequest{ + Meta: httpkit.UserMeta(coreRequest, ""), + UserIds: userIDs, + }) + logRoomGiftPanelStage(coreCtx, requestID, roomID, "recipient_profile_core", startedAt, err, err != nil) + if err != nil { + // 房间目标 user_id 仍来自 Room Cell 快照;资料服务抖动时保留可送礼目标,只降级头像昵称。 + return nil, true + } + return roomGiftCoreProfilesFromUsers(userIDs, resp.GetUsers()), false +} + +func roomGiftCoreProfilesFromUsers(userIDs []int64, users map[int64]*userv1.User) map[int64]roomDisplayProfileData { + profiles := make(map[int64]roomDisplayProfileData, len(users)) + for _, userID := range userIDs { + user := users[userID] + if user == nil { + continue + } + // 核心层保留历史完整资料字段;可选资源统一初始化为空对象,超时降级不会改变 JSON 类型。 + profiles[userID] = roomDisplayProfileData{ + UserID: formatOptionalUserID(user.GetUserId()), + Username: user.GetUsername(), + Avatar: user.GetAvatar(), + DisplayUserID: user.GetDisplayUserId(), + PrettyID: user.GetPrettyId(), + PrettyDisplayUserID: user.GetPrettyDisplayUserId(), + Gender: strings.TrimSpace(user.GetGender()), + Age: roomProfileAgeFromBirth(user.GetBirth(), time.Now().UTC()), + Country: strings.ToUpper(strings.TrimSpace(user.GetCountry())), + CountryName: strings.TrimSpace(user.GetCountryName()), + CountryDisplayName: strings.TrimSpace(user.GetCountryDisplayName()), + CountryFlag: roomProfileCountryFlag(user.GetCountry()), + VIP: map[string]any{}, + Level: roomDefaultLevelDisplay(), + LevelBadges: map[string]any{}, + Badges: []map[string]any{}, + AvatarFrame: map[string]any{}, + ProfileCard: map[string]any{}, + Vehicle: map[string]any{}, + MicSeatAnimation: map[string]any{}, + Charm: 0, + } + } + return profiles +} diff --git a/services/gateway-service/internal/transport/http/roomapi/gift_panel_pipeline_test.go b/services/gateway-service/internal/transport/http/roomapi/gift_panel_pipeline_test.go new file mode 100644 index 00000000..c08b833d --- /dev/null +++ b/services/gateway-service/internal/transport/http/roomapi/gift_panel_pipeline_test.go @@ -0,0 +1,435 @@ +package roomapi + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "sync/atomic" + "testing" + "time" + + activityv1 "hyapp.local/api/proto/activity/v1" + roomv1 "hyapp.local/api/proto/room/v1" + userv1 "hyapp.local/api/proto/user/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + "hyapp/services/gateway-service/internal/auth" + gatewayclient "hyapp/services/gateway-service/internal/client" + "hyapp/services/gateway-service/internal/transport/http/httpkit" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type giftPanelTestRoomQueryClient struct { + gatewayclient.RoomQueryClient + getSnapshot func(context.Context, *roomv1.GetRoomSnapshotRequest) (*roomv1.GetRoomSnapshotResponse, error) +} + +func (f *giftPanelTestRoomQueryClient) GetRoomSnapshot(ctx context.Context, req *roomv1.GetRoomSnapshotRequest) (*roomv1.GetRoomSnapshotResponse, error) { + return f.getSnapshot(ctx, req) +} + +type giftPanelTestWalletClient struct { + gatewayclient.WalletClient + getBalances func(context.Context, *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error) + getCatalogVersion func(context.Context, *walletv1.GetGiftCatalogVersionRequest) (*walletv1.GetGiftCatalogVersionResponse, error) + listUserResources func(context.Context, *walletv1.ListUserResourcesRequest) (*walletv1.ListUserResourcesResponse, error) + listGiftTypes func(context.Context, *walletv1.ListGiftTypeConfigsRequest) (*walletv1.ListGiftTypeConfigsResponse, error) + listGiftConfigs func(context.Context, *walletv1.ListGiftConfigsRequest) (*walletv1.ListGiftConfigsResponse, error) + unusedEnrichRPCCalls atomic.Int32 +} + +func (f *giftPanelTestWalletClient) GetBalances(ctx context.Context, req *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error) { + return f.getBalances(ctx, req) +} + +func (f *giftPanelTestWalletClient) GetGiftCatalogVersion(ctx context.Context, req *walletv1.GetGiftCatalogVersionRequest) (*walletv1.GetGiftCatalogVersionResponse, error) { + return f.getCatalogVersion(ctx, req) +} + +func (f *giftPanelTestWalletClient) ListUserResources(ctx context.Context, req *walletv1.ListUserResourcesRequest) (*walletv1.ListUserResourcesResponse, error) { + return f.listUserResources(ctx, req) +} + +func (f *giftPanelTestWalletClient) ListGiftTypeConfigs(ctx context.Context, req *walletv1.ListGiftTypeConfigsRequest) (*walletv1.ListGiftTypeConfigsResponse, error) { + return f.listGiftTypes(ctx, req) +} + +func (f *giftPanelTestWalletClient) ListGiftConfigs(ctx context.Context, req *walletv1.ListGiftConfigsRequest) (*walletv1.ListGiftConfigsResponse, error) { + return f.listGiftConfigs(ctx, req) +} + +func (f *giftPanelTestWalletClient) BatchGetUserEquippedResources(context.Context, *walletv1.BatchGetUserEquippedResourcesRequest) (*walletv1.BatchGetUserEquippedResourcesResponse, error) { + f.unusedEnrichRPCCalls.Add(1) + return &walletv1.BatchGetUserEquippedResourcesResponse{}, nil +} + +func (f *giftPanelTestWalletClient) GetResource(context.Context, *walletv1.GetResourceRequest) (*walletv1.GetResourceResponse, error) { + f.unusedEnrichRPCCalls.Add(1) + return &walletv1.GetResourceResponse{}, nil +} + +type giftPanelTestUserClient struct { + gatewayclient.UserProfileClient + batchGetUsers func(context.Context, *userv1.BatchGetUsersRequest) (*userv1.BatchGetUsersResponse, error) + calls atomic.Int32 +} + +func (f *giftPanelTestUserClient) BatchGetUsers(ctx context.Context, req *userv1.BatchGetUsersRequest) (*userv1.BatchGetUsersResponse, error) { + f.calls.Add(1) + return f.batchGetUsers(ctx, req) +} + +type giftPanelUnusedAchievementClient struct { + gatewayclient.AchievementClient + calls atomic.Int32 +} + +func (f *giftPanelUnusedAchievementClient) ListMyBadges(context.Context, *activityv1.ListMyBadgesRequest) (*activityv1.ListMyBadgesResponse, error) { + f.calls.Add(1) + return &activityv1.ListMyBadgesResponse{}, nil +} + +type giftPanelUnusedGrowthClient struct { + gatewayclient.GrowthLevelClient + calls atomic.Int32 +} + +func (f *giftPanelUnusedGrowthClient) BatchGetUserLevelDisplayProfiles(context.Context, *activityv1.BatchGetUserLevelDisplayProfilesRequest) (*activityv1.BatchGetUserLevelDisplayProfilesResponse, error) { + f.calls.Add(1) + return &activityv1.BatchGetUserLevelDisplayProfilesResponse{}, nil +} + +type giftPanelStageGate struct { + started chan string + release chan struct{} +} + +func newGiftPanelStageGate() *giftPanelStageGate { + return &giftPanelStageGate{started: make(chan string, 16), release: make(chan struct{})} +} + +func (g *giftPanelStageGate) wait(ctx context.Context, stage string) error { + select { + case g.started <- stage: + case <-ctx.Done(): + return ctx.Err() + } + select { + case <-g.release: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +func awaitGiftPanelStages(t *testing.T, started <-chan string, expected ...string) { + t.Helper() + want := make(map[string]bool, len(expected)) + for _, stage := range expected { + want[stage] = true + } + deadline := time.NewTimer(time.Second) + defer deadline.Stop() + for len(want) > 0 { + select { + case stage := <-started: + delete(want, stage) + case <-deadline.C: + t.Fatalf("gift-panel stages did not start concurrently; missing=%v", want) + } + } +} + +type giftPanelBlockingTracker struct { + active atomic.Int32 + canceled atomic.Int32 +} + +func (t *giftPanelBlockingTracker) wait(ctx context.Context) error { + t.active.Add(1) + defer t.active.Add(-1) + <-ctx.Done() + t.canceled.Add(1) + return ctx.Err() +} + +func TestRoomGiftPanelProductionBudgetsStayBelowFlutterTimeout(t *testing.T) { + handler := New(Config{}) + if got := handler.roomGiftPanelTotalTimeout(); got != 10*time.Second || got >= 15*time.Second { + t.Fatalf("gift-panel total budget must leave client transport headroom: %s", got) + } + if got := handler.roomGiftPanelRecipientCoreTimeout(); got != 1500*time.Millisecond || got >= handler.roomGiftPanelTotalTimeout() { + t.Fatalf("recipient core budget must remain a short degradable stage: %s", got) + } +} + +func TestRoomGiftPanelStageCanceledRecognizesLocalAndGRPCCancellation(t *testing.T) { + for _, err := range []error{ + context.Canceled, + fmt.Errorf("query interrupted: %w", context.Canceled), + status.Error(codes.Canceled, "context canceled"), + } { + if !roomGiftPanelStageCanceled(err) { + t.Fatalf("expected cancellation for %v", err) + } + } + if roomGiftPanelStageCanceled(status.Error(codes.DeadlineExceeded, "deadline exceeded")) { + t.Fatal("deadline exhaustion is a real latency failure and must remain error-level") + } +} + +func TestRoomGiftPanelRunsIndependentStagesConcurrentlyAndSkipsUnusedEnrichment(t *testing.T) { + phaseOne := newGiftPanelStageGate() + phaseTwo := newGiftPanelStageGate() + roomID := "lalu_room_gift_parallel" + queryClient := &giftPanelTestRoomQueryClient{getSnapshot: func(ctx context.Context, _ *roomv1.GetRoomSnapshotRequest) (*roomv1.GetRoomSnapshotResponse, error) { + if err := phaseOne.wait(ctx, "snapshot"); err != nil { + return nil, err + } + return giftPanelTestSnapshot(roomID), nil + }} + walletClient := giftPanelWorkingWalletClient() + walletClient.getBalances = func(ctx context.Context, _ *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error) { + if err := phaseOne.wait(ctx, "balance"); err != nil { + return nil, err + } + return &walletv1.GetBalancesResponse{Balances: []*walletv1.AssetBalance{{AssetType: "COIN", AvailableAmount: 1000}}}, nil + } + walletClient.getCatalogVersion = func(ctx context.Context, _ *walletv1.GetGiftCatalogVersionRequest) (*walletv1.GetGiftCatalogVersionResponse, error) { + if err := phaseOne.wait(ctx, "catalog"); err != nil { + return nil, err + } + return &walletv1.GetGiftCatalogVersionResponse{Version: 1}, nil + } + walletClient.listUserResources = func(ctx context.Context, _ *walletv1.ListUserResourcesRequest) (*walletv1.ListUserResourcesResponse, error) { + if err := phaseOne.wait(ctx, "bag"); err != nil { + return nil, err + } + return &walletv1.ListUserResourcesResponse{}, nil + } + walletClient.listGiftTypes = func(ctx context.Context, _ *walletv1.ListGiftTypeConfigsRequest) (*walletv1.ListGiftTypeConfigsResponse, error) { + if err := phaseTwo.wait(ctx, "gift_types"); err != nil { + return nil, err + } + return &walletv1.ListGiftTypeConfigsResponse{GiftTypes: []*walletv1.GiftTypeConfig{{TypeCode: "normal", TabKey: "Gift", Status: "active"}}}, nil + } + walletClient.listGiftConfigs = func(ctx context.Context, _ *walletv1.ListGiftConfigsRequest) (*walletv1.ListGiftConfigsResponse, error) { + if err := phaseTwo.wait(ctx, "gifts"); err != nil { + return nil, err + } + return giftPanelTestGiftConfigs(), nil + } + userClient := &giftPanelTestUserClient{batchGetUsers: func(ctx context.Context, _ *userv1.BatchGetUsersRequest) (*userv1.BatchGetUsersResponse, error) { + if err := phaseTwo.wait(ctx, "recipient_core"); err != nil { + return nil, err + } + return giftPanelTestUsers(), nil + }} + achievementClient := &giftPanelUnusedAchievementClient{} + growthClient := &giftPanelUnusedGrowthClient{} + handler := New(Config{ + RoomQueryClient: queryClient, + WalletClient: walletClient, + UserProfileClient: userClient, + AchievementClient: achievementClient, + GrowthLevelClient: growthClient, + }) + handler.giftPanelRecipientCoreBudget = 3 * time.Second + + responseCh := make(chan *httptest.ResponseRecorder, 1) + go func() { responseCh <- serveGiftPanelForTest(handler, roomID) }() + awaitGiftPanelStages(t, phaseOne.started, "snapshot", "balance", "catalog", "bag") + close(phaseOne.release) + awaitGiftPanelStages(t, phaseTwo.started, "gift_types", "gifts", "recipient_core") + close(phaseTwo.release) + + recorder := <-responseCh + if recorder.Code != http.StatusOK { + t.Fatalf("gift-panel parallel response mismatch: status=%d body=%s", recorder.Code, recorder.Body.String()) + } + if userClient.calls.Load() != 1 { + t.Fatalf("recipient core must use one BatchGetUsers call, got=%d", userClient.calls.Load()) + } + if achievementClient.calls.Load() != 0 || growthClient.calls.Load() != 0 || walletClient.unusedEnrichRPCCalls.Load() != 0 { + t.Fatalf("gift-panel must not call unused enrich RPCs: badge=%d level=%d wallet=%d", achievementClient.calls.Load(), growthClient.calls.Load(), walletClient.unusedEnrichRPCCalls.Load()) + } + var envelope struct { + Code string `json:"code"` + Data struct { + Recipients []roomGiftRecipientData `json:"recipients"` + } `json:"data"` + } + if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { + t.Fatalf("decode gift-panel response: %v", err) + } + if envelope.Code != httpkit.CodeOK || len(envelope.Data.Recipients) != 3 { + t.Fatalf("gift-panel recipients mismatch: %+v", envelope) + } + profile := envelope.Data.Recipients[1].Profile + if profile == nil || profile.Username != "Host 101" || profile.Avatar != "https://cdn.example/101.png" { + t.Fatalf("gift-panel core profile must survive without enrich: %+v", profile) + } +} + +func TestRoomGiftPanelTotalBudgetReturnsTraceableUpstreamErrorAndStopsWorkers(t *testing.T) { + tracker := &giftPanelBlockingTracker{} + queryClient := &giftPanelTestRoomQueryClient{getSnapshot: func(ctx context.Context, _ *roomv1.GetRoomSnapshotRequest) (*roomv1.GetRoomSnapshotResponse, error) { + return nil, tracker.wait(ctx) + }} + walletClient := giftPanelWorkingWalletClient() + walletClient.getBalances = func(ctx context.Context, _ *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error) { + return nil, tracker.wait(ctx) + } + walletClient.getCatalogVersion = func(ctx context.Context, _ *walletv1.GetGiftCatalogVersionRequest) (*walletv1.GetGiftCatalogVersionResponse, error) { + return nil, tracker.wait(ctx) + } + walletClient.listUserResources = func(ctx context.Context, _ *walletv1.ListUserResourcesRequest) (*walletv1.ListUserResourcesResponse, error) { + return nil, tracker.wait(ctx) + } + handler := New(Config{RoomQueryClient: queryClient, WalletClient: walletClient}) + handler.giftPanelTotalBudget = 40 * time.Millisecond + + startedAt := time.Now() + recorder := serveGiftPanelForTest(handler, "lalu_room_gift_timeout") + if elapsed := time.Since(startedAt); elapsed > 500*time.Millisecond { + t.Fatalf("gift-panel budget did not stop response promptly: elapsed=%s", elapsed) + } + if recorder.Code != http.StatusBadGateway { + t.Fatalf("gift-panel timeout status mismatch: got=%d body=%s", recorder.Code, recorder.Body.String()) + } + var envelope httpkit.ResponseEnvelope + if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { + t.Fatalf("decode timeout envelope: %v", err) + } + if envelope.Code != httpkit.CodeUpstreamError || envelope.RequestID == "" || envelope.RequestID != recorder.Header().Get("X-Request-ID") { + t.Fatalf("timeout envelope must expose server request_id: envelope=%+v header=%q", envelope, recorder.Header().Get("X-Request-ID")) + } + if tracker.canceled.Load() != 4 || tracker.active.Load() != 0 { + t.Fatalf("all request workers must observe cancellation and exit: canceled=%d active=%d", tracker.canceled.Load(), tracker.active.Load()) + } +} + +func TestRoomGiftPanelRecipientCoreTimeoutDegradesWithoutFailingPanel(t *testing.T) { + tracker := &giftPanelBlockingTracker{} + handler := New(Config{ + RoomQueryClient: &giftPanelTestRoomQueryClient{getSnapshot: func(context.Context, *roomv1.GetRoomSnapshotRequest) (*roomv1.GetRoomSnapshotResponse, error) { + return giftPanelTestSnapshot("lalu_room_gift_profile_timeout"), nil + }}, + WalletClient: giftPanelWorkingWalletClient(), + UserProfileClient: &giftPanelTestUserClient{batchGetUsers: func(ctx context.Context, _ *userv1.BatchGetUsersRequest) (*userv1.BatchGetUsersResponse, error) { + return nil, tracker.wait(ctx) + }}, + }) + handler.giftPanelRecipientCoreBudget = 35 * time.Millisecond + + recorder := serveGiftPanelForTest(handler, "lalu_room_gift_profile_timeout") + if recorder.Code != http.StatusOK { + t.Fatalf("recipient core timeout must degrade, not fail panel: status=%d body=%s", recorder.Code, recorder.Body.String()) + } + if tracker.canceled.Load() != 1 || tracker.active.Load() != 0 { + t.Fatalf("recipient core worker must stop at its child deadline: canceled=%d active=%d", tracker.canceled.Load(), tracker.active.Load()) + } + var envelope struct { + Code string `json:"code"` + Data struct { + Recipients []roomGiftRecipientData `json:"recipients"` + } `json:"data"` + } + if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { + t.Fatalf("decode degraded panel: %v", err) + } + if envelope.Code != httpkit.CodeOK || len(envelope.Data.Recipients) != 3 || envelope.Data.Recipients[1].UserID == "" || envelope.Data.Recipients[1].Profile != nil { + t.Fatalf("degraded panel must preserve Room Cell recipient IDs only: %+v", envelope) + } +} + +func TestRoomGiftPanelConfigFailureCancelsSiblingRPCBeforeReturning(t *testing.T) { + tracker := &giftPanelBlockingTracker{} + giftStarted := make(chan struct{}) + walletClient := giftPanelWorkingWalletClient() + walletClient.listGiftConfigs = func(ctx context.Context, _ *walletv1.ListGiftConfigsRequest) (*walletv1.ListGiftConfigsResponse, error) { + close(giftStarted) + return nil, tracker.wait(ctx) + } + walletClient.listGiftTypes = func(context.Context, *walletv1.ListGiftTypeConfigsRequest) (*walletv1.ListGiftTypeConfigsResponse, error) { + <-giftStarted + return nil, errors.New("gift type test failure") + } + handler := New(Config{WalletClient: walletClient}) + request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms/lalu_room_gift_config_failure/gift-panel", nil) + request.SetPathValue("room_id", "lalu_room_gift_config_failure") + request = request.WithContext(appcode.WithContext(request.Context(), "lalu")) + + _, err := handler.roomGiftPanelBaseConfigForVersion(request, "lalu", 1001, 1, false) + if err == nil || err.Error() != "gift type test failure" { + t.Fatalf("config error mismatch: %v", err) + } + if tracker.canceled.Load() != 1 || tracker.active.Load() != 0 { + t.Fatalf("failed config sibling must observe cancellation before return: canceled=%d active=%d", tracker.canceled.Load(), tracker.active.Load()) + } +} + +func serveGiftPanelForTest(handler *Handler, roomID string) *httptest.ResponseRecorder { + request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms/"+roomID+"/gift-panel", nil) + request.SetPathValue("room_id", roomID) + request = request.WithContext(auth.WithUserID(appcode.WithContext(request.Context(), "lalu"), 42)) + recorder := httptest.NewRecorder() + httpkit.WithRequestID(http.HandlerFunc(handler.getRoomGiftPanel)).ServeHTTP(recorder, request) + return recorder +} + +func giftPanelTestSnapshot(roomID string) *roomv1.GetRoomSnapshotResponse { + return &roomv1.GetRoomSnapshotResponse{Room: &roomv1.RoomSnapshot{ + RoomId: roomID, + OwnerUserId: 101, + VisibleRegionId: 1001, + MicSeats: []*roomv1.SeatState{ + {SeatNo: 1, UserId: 101}, + {SeatNo: 2, UserId: 102}, + }, + }} +} + +func giftPanelWorkingWalletClient() *giftPanelTestWalletClient { + return &giftPanelTestWalletClient{ + getBalances: func(context.Context, *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error) { + return &walletv1.GetBalancesResponse{Balances: []*walletv1.AssetBalance{{AssetType: "COIN", AvailableAmount: 1000}}}, nil + }, + getCatalogVersion: func(context.Context, *walletv1.GetGiftCatalogVersionRequest) (*walletv1.GetGiftCatalogVersionResponse, error) { + return &walletv1.GetGiftCatalogVersionResponse{Version: 1}, nil + }, + listUserResources: func(context.Context, *walletv1.ListUserResourcesRequest) (*walletv1.ListUserResourcesResponse, error) { + return &walletv1.ListUserResourcesResponse{}, nil + }, + listGiftTypes: func(context.Context, *walletv1.ListGiftTypeConfigsRequest) (*walletv1.ListGiftTypeConfigsResponse, error) { + return &walletv1.ListGiftTypeConfigsResponse{GiftTypes: []*walletv1.GiftTypeConfig{{TypeCode: "normal", TabKey: "Gift", Status: "active"}}}, nil + }, + listGiftConfigs: func(context.Context, *walletv1.ListGiftConfigsRequest) (*walletv1.ListGiftConfigsResponse, error) { + return giftPanelTestGiftConfigs(), nil + }, + } +} + +func giftPanelTestGiftConfigs() *walletv1.ListGiftConfigsResponse { + return &walletv1.ListGiftConfigsResponse{Gifts: []*walletv1.GiftConfig{{ + GiftId: "rose", + ResourceId: 11, + Resource: &walletv1.Resource{ResourceId: 11, ResourceType: "gift", Status: "active", Name: "Rose"}, + Status: "active", + Name: "Rose", + GiftTypeCode: "normal", + CoinPrice: 100, + }}} +} + +func giftPanelTestUsers() *userv1.BatchGetUsersResponse { + return &userv1.BatchGetUsersResponse{Users: map[int64]*userv1.User{ + 101: {UserId: 101, Username: "Host 101", Avatar: "https://cdn.example/101.png", DisplayUserId: "100101"}, + 102: {UserId: 102, Username: "Host 102", Avatar: "https://cdn.example/102.png", DisplayUserId: "100102"}, + }} +} diff --git a/services/gateway-service/internal/transport/http/roomapi/handler.go b/services/gateway-service/internal/transport/http/roomapi/handler.go index e517f26a..5e6034c1 100644 --- a/services/gateway-service/internal/transport/http/roomapi/handler.go +++ b/services/gateway-service/internal/transport/http/roomapi/handler.go @@ -7,60 +7,105 @@ import ( "hyapp/pkg/tencentrtc" "hyapp/services/gateway-service/internal/client" "hyapp/services/gateway-service/internal/transport/http/appapi" + "hyapp/services/gateway-service/internal/transport/http/giftlimit" "hyapp/services/gateway-service/internal/transport/http/httproutes" ) // Handler owns room-facing HTTP endpoints in gateway. // It keeps gateway as protocol adapter only: room state still belongs to room-service. type Handler struct { - roomClient client.RoomClient - roomGuardClient client.RoomGuardClient - roomQueryClient client.RoomQueryClient - userProfileClient client.UserProfileClient - userCountryClient client.UserCountryQueryClient - userRegionClient client.UserRegionClient - userSocialClient client.UserSocialClient - userHostClient client.UserHostClient - appConfigReader appapi.ConfigReader - walletClient client.WalletClient - giftPanelCache *roomGiftPanelConfigCache - growthLevelClient client.GrowthLevelClient - achievementClient client.AchievementClient - rtcTokenConfig tencentrtc.TokenConfig + roomClient client.RoomClient + roomGuardClient client.RoomGuardClient + roomQueryClient client.RoomQueryClient + userProfileClient client.UserProfileClient + userCountryClient client.UserCountryQueryClient + userRegionClient client.UserRegionClient + userSocialClient client.UserSocialClient + userHostClient client.UserHostClient + appConfigReader appapi.ConfigReader + giftComboReader giftComboConfigReader + walletClient client.WalletClient + giftPanelCache *roomGiftPanelConfigCache + growthLevelClient client.GrowthLevelClient + achievementClient client.AchievementClient + giftCapacityEnabled bool + giftCapacityLimiter giftlimit.Limiter + giftRequestTimeout time.Duration + giftMaxTargets int + giftMaxCount int + giftMaxUnits int + rtcTokenConfig tencentrtc.TokenConfig + + // gift-panel 的预算保留在 handler 内,生产使用固定安全值;测试可缩短预算验证真实超时分支, + // 避免把 Flutter 的 15 秒客户端超时当成后端取消机制。 + giftPanelTotalBudget time.Duration + giftPanelRecipientCoreBudget time.Duration } type Config struct { - RoomClient client.RoomClient - RoomGuardClient client.RoomGuardClient - RoomQueryClient client.RoomQueryClient - UserProfileClient client.UserProfileClient - UserCountryClient client.UserCountryQueryClient - UserRegionClient client.UserRegionClient - UserSocialClient client.UserSocialClient - UserHostClient client.UserHostClient - AppConfigReader appapi.ConfigReader - WalletClient client.WalletClient - GrowthLevelClient client.GrowthLevelClient - AchievementClient client.AchievementClient - RTCTokenConfig tencentrtc.TokenConfig + RoomClient client.RoomClient + RoomGuardClient client.RoomGuardClient + RoomQueryClient client.RoomQueryClient + UserProfileClient client.UserProfileClient + UserCountryClient client.UserCountryQueryClient + UserRegionClient client.UserRegionClient + UserSocialClient client.UserSocialClient + UserHostClient client.UserHostClient + AppConfigReader appapi.ConfigReader + WalletClient client.WalletClient + GrowthLevelClient client.GrowthLevelClient + AchievementClient client.AchievementClient + GiftCapacityEnabled bool + GiftCapacityLimiter giftlimit.Limiter + GiftRequestTimeout time.Duration + GiftMaxTargets int + GiftMaxCount int + GiftMaxUnits int + RTCTokenConfig tencentrtc.TokenConfig } func New(config Config) *Handler { + giftRequestTimeout := config.GiftRequestTimeout + if giftRequestTimeout <= 0 { + giftRequestTimeout = 5 * time.Second + } + giftMaxTargets := config.GiftMaxTargets + if giftMaxTargets <= 0 { + giftMaxTargets = 30 + } + giftMaxCount := config.GiftMaxCount + if giftMaxCount <= 0 { + giftMaxCount = 999 + } + giftMaxUnits := config.GiftMaxUnits + if giftMaxUnits <= 0 { + giftMaxUnits = 5_000 + } + comboReader, _ := any(config.AppConfigReader).(giftComboConfigReader) return &Handler{ - roomClient: config.RoomClient, - roomGuardClient: config.RoomGuardClient, - roomQueryClient: config.RoomQueryClient, - userProfileClient: config.UserProfileClient, - userCountryClient: config.UserCountryClient, - userRegionClient: config.UserRegionClient, - userSocialClient: config.UserSocialClient, - userHostClient: config.UserHostClient, - appConfigReader: config.AppConfigReader, - walletClient: config.WalletClient, - giftPanelCache: newRoomGiftPanelConfigCache(45 * time.Second), - growthLevelClient: config.GrowthLevelClient, - achievementClient: config.AchievementClient, - rtcTokenConfig: config.RTCTokenConfig, + roomClient: config.RoomClient, + roomGuardClient: config.RoomGuardClient, + roomQueryClient: config.RoomQueryClient, + userProfileClient: config.UserProfileClient, + userCountryClient: config.UserCountryClient, + userRegionClient: config.UserRegionClient, + userSocialClient: config.UserSocialClient, + userHostClient: config.UserHostClient, + appConfigReader: config.AppConfigReader, + giftComboReader: comboReader, + walletClient: config.WalletClient, + giftPanelCache: newRoomGiftPanelConfigCache(45 * time.Second), + growthLevelClient: config.GrowthLevelClient, + achievementClient: config.AchievementClient, + giftCapacityEnabled: config.GiftCapacityEnabled, + giftCapacityLimiter: config.GiftCapacityLimiter, + giftRequestTimeout: giftRequestTimeout, + giftMaxTargets: giftMaxTargets, + giftMaxCount: giftMaxCount, + giftMaxUnits: giftMaxUnits, + rtcTokenConfig: config.RTCTokenConfig, + giftPanelTotalBudget: roomGiftPanelTotalBudget, + giftPanelRecipientCoreBudget: roomGiftPanelRecipientCoreBudget, } } @@ -68,6 +113,7 @@ func (h *Handler) RoomHandlers() httproutes.RoomHandlers { return httproutes.RoomHandlers{ GetMyRoom: h.getMyRoom, ListRoomFeeds: h.listRoomFeeds, + ListRoomFilters: h.listRoomFilters, ListRooms: h.listRooms, GetCurrentRoom: h.getCurrentRoom, GetRoomSnapshot: h.getRoomSnapshot, @@ -104,6 +150,7 @@ func (h *Handler) RoomHandlers() httproutes.RoomHandlers { UnbanUser: h.unbanUser, SendGift: h.sendGift, SendGiftBatch: h.sendGiftBatch, + SendGiftV2: h.sendGiftV2, } } diff --git a/services/gateway-service/internal/transport/http/roomapi/room_filter_handler.go b/services/gateway-service/internal/transport/http/roomapi/room_filter_handler.go new file mode 100644 index 00000000..00d4a7fd --- /dev/null +++ b/services/gateway-service/internal/transport/http/roomapi/room_filter_handler.go @@ -0,0 +1,186 @@ +package roomapi + +import ( + "net/http" + "strconv" + "strings" + + "hyapp/pkg/appcode" + "hyapp/services/gateway-service/internal/auth" + "hyapp/services/gateway-service/internal/transport/http/httpkit" + + userv1 "hyapp.local/api/proto/user/v1" +) + +const ( + roomFilterTypeAll = "all" + roomFilterTypeRegion = "region" + roomFilterTypeCountry = "country" + roomFilterAllRegionsAppCode = "huwaa" + + // 区域筛选目录只暴露当前可用的业务区域;禁用区域继续保留在后台治理面,不下发给客户端。 + roomFilterRegionStatusActive = "active" +) + +// roomFilterCatalogData 是首页筛选控件的稳定目录。 +// filter_type 告诉 Flutter 应读取哪个查询字段:region 使用 region_id,country 使用 country_code,all 不传两者。 +type roomFilterCatalogData struct { + All roomAllFilterData `json:"all"` + Regions []roomRegionFilterData `json:"regions"` + Countries []roomCountryFilterData `json:"countries"` +} + +type roomAllFilterData struct { + FilterType string `json:"filter_type"` + QueryKey string `json:"query_key"` + QueryValue string `json:"query_value"` +} + +type roomRegionFilterData struct { + FilterType string `json:"filter_type"` + QueryKey string `json:"query_key"` + QueryValue string `json:"query_value"` + RegionID int64 `json:"region_id"` + RegionCode string `json:"region_code"` + RegionName string `json:"region_name"` + SortOrder int32 `json:"sort_order"` +} + +type roomCountryFilterData struct { + FilterType string `json:"filter_type"` + QueryKey string `json:"query_key"` + QueryValue string `json:"query_value"` + ParentRegionID int64 `json:"parent_region_id,omitempty"` + CountryID int64 `json:"country_id"` + CountryCode string `json:"country_code"` + CountryName string `json:"country_name"` + CountryDisplayName string `json:"country_display_name"` + CountryFlag string `json:"country_flag"` + SortOrder int32 `json:"sort_order"` +} + +// listRoomFilters 返回当前 App 可用的首页区域/国家筛选目录。 +// Huwaa 已开放跨区域浏览,因此返回全部 active 区域;其他 App 继续只返回当前用户区域,避免下发不可用的跨区值。 +func (h *Handler) listRoomFilters(writer http.ResponseWriter, request *http.Request) { + if h.userRegionClient == nil || h.userCountryClient == nil || (appcode.FromContext(request.Context()) != roomFilterAllRegionsAppCode && h.userProfileClient == nil) { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + + regions, err := h.roomFilterRegions(request) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + countryResp, err := h.userCountryClient.ListRegistrationCountries(request.Context(), &userv1.ListRegistrationCountriesRequest{ + Meta: httpkit.UserMeta(request, ""), + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + + httpkit.WriteOK(writer, request, roomFilterCatalogDataFromProto(regions, countryResp.GetCountries())) +} + +func (h *Handler) roomFilterRegions(request *http.Request) ([]*userv1.Region, error) { + if appcode.FromContext(request.Context()) == roomFilterAllRegionsAppCode { + resp, err := h.userRegionClient.ListRegions(request.Context(), &userv1.ListRegionsRequest{ + Meta: httpkit.UserMeta(request, ""), + Status: roomFilterRegionStatusActive, + }) + if err != nil { + return nil, err + } + return resp.GetRegions(), nil + } + userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ + Meta: httpkit.UserMeta(request, ""), + UserId: auth.UserIDFromContext(request.Context()), + }) + if err != nil { + return nil, err + } + if userResp.GetUser().GetRegionId() <= 0 { + // GLOBAL 是内部兜底桶,不是客户端可选业务区域;All 仍表示该 App 原有的当前区域语义。 + return []*userv1.Region{}, nil + } + regionResp, err := h.userRegionClient.GetRegion(request.Context(), &userv1.GetRegionRequest{ + Meta: httpkit.UserMeta(request, ""), + RegionId: userResp.GetUser().GetRegionId(), + }) + if err != nil { + return nil, err + } + region := regionResp.GetRegion() + if region == nil || strings.TrimSpace(region.GetStatus()) != roomFilterRegionStatusActive { + return []*userv1.Region{}, nil + } + return []*userv1.Region{region}, nil +} + +func roomFilterCatalogDataFromProto(regions []*userv1.Region, countries []*userv1.Country) roomFilterCatalogData { + data := roomFilterCatalogData{ + // All 的 query_key/query_value 显式为空;Flutter 可用同一套 option 逻辑,并在 key 为空时不附加筛选参数。 + All: roomAllFilterData{FilterType: roomFilterTypeAll, QueryKey: "", QueryValue: ""}, + Regions: make([]roomRegionFilterData, 0, len(regions)), + Countries: make([]roomCountryFilterData, 0, len(countries)), + } + regionByCountry := make(map[string]int64) + for _, region := range regions { + if region == nil || region.GetRegionId() <= 0 || strings.TrimSpace(region.GetStatus()) != roomFilterRegionStatusActive { + continue + } + data.Regions = append(data.Regions, roomRegionFilterData{ + FilterType: roomFilterTypeRegion, + QueryKey: "region_id", + QueryValue: strconv.FormatInt(region.GetRegionId(), 10), + RegionID: region.GetRegionId(), + RegionCode: strings.TrimSpace(region.GetRegionCode()), + RegionName: strings.TrimSpace(region.GetName()), + SortOrder: region.GetSortOrder(), + }) + for _, rawCountryCode := range region.GetCountries() { + countryCode := normalizeRoomListCountryCode(rawCountryCode) + if countryCode == "" { + continue + } + if _, exists := regionByCountry[countryCode]; !exists { + // 正常数据中一个国家只能有一个 active 区域;若上游短暂异常,保留产品排序最靠前的区域以确保响应确定。 + regionByCountry[countryCode] = region.GetRegionId() + } + } + } + + for _, country := range countries { + if country == nil { + continue + } + countryCode := normalizeRoomListCountryCode(country.GetCountryCode()) + if countryCode == "" { + continue + } + parentRegionID, mapped := regionByCountry[countryCode] + if !mapped { + // 只有 enabled 国家与 active 区域映射同时成立时才是可回传筛选值,避免客户端提交无法归属业务区域的国家。 + continue + } + countryFlag := strings.TrimSpace(country.GetFlag()) + if countryFlag == "" { + countryFlag = roomProfileCountryFlag(countryCode) + } + data.Countries = append(data.Countries, roomCountryFilterData{ + FilterType: roomFilterTypeCountry, + QueryKey: "country_code", + QueryValue: countryCode, + ParentRegionID: parentRegionID, + CountryID: country.GetCountryId(), + CountryCode: countryCode, + CountryName: strings.TrimSpace(country.GetCountryName()), + CountryDisplayName: strings.TrimSpace(country.GetCountryDisplayName()), + CountryFlag: countryFlag, + SortOrder: country.GetSortOrder(), + }) + } + return data +} diff --git a/services/gateway-service/internal/transport/http/roomapi/room_handler.go b/services/gateway-service/internal/transport/http/roomapi/room_handler.go index 85c5409a..eb5f2a32 100644 --- a/services/gateway-service/internal/transport/http/roomapi/room_handler.go +++ b/services/gateway-service/internal/transport/http/roomapi/room_handler.go @@ -1,6 +1,7 @@ package roomapi import ( + "context" "encoding/base64" "encoding/json" "fmt" @@ -8,9 +9,11 @@ import ( "net/http" "strconv" "strings" + "sync" "time" "hyapp/pkg/appcode" + "hyapp/pkg/giftlimits" "hyapp/pkg/roomid" "hyapp/pkg/xerr" "hyapp/services/gateway-service/internal/auth" @@ -26,6 +29,8 @@ import ( const ( defaultRoomFeedRelationPageSize = 100 maxRoomFeedRelationScanCount = 500 + // Huwaa 首页的产品范围是全区;其他 App 继续沿用当前区域/白名单语义。 + huwaaRoomDiscoveryAppCode = "huwaa" // 房间展示资料只读取客户端能在首屏、公屏或资料卡直接渲染的当前佩戴资源。 roomAppearanceResourceAvatarFrame = "avatar_frame" @@ -78,7 +83,14 @@ type roomFeedRelationCursor struct { RoomID string `json:"room_id"` } -// listRooms 按当前登录用户的服务端 region_id 查询房间发现列表。 +// publicRoomListFilter 只表达 Flutter 从 /rooms/filters 原样回传的一种筛选。 +// RegionID 和 CountryCode 必须互斥;两者为空才表示 All。 +type publicRoomListFilter struct { + RegionID int64 + CountryCode string +} + +// listRooms 按 App 策略解析默认 All/当前区域,并校验 Flutter 回传的区域或国家筛选。 func (h *Handler) listRooms(writer http.ResponseWriter, request *http.Request) { limit, ok := parseRoomListLimit(request.URL.Query().Get("limit")) if !ok { @@ -90,6 +102,11 @@ func (h *Handler) listRooms(writer http.ResponseWriter, request *http.Request) { httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } + filter, ok := parsePublicRoomListFilter(request) + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } if h.userProfileClient == nil || h.roomQueryClient == nil || h.userRegionClient == nil || h.userCountryClient == nil { // 列表入口必须同时依赖 user-service 用户区域、区域国家、国家主数据和 room-service 读模型。 httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") @@ -106,24 +123,70 @@ func (h *Handler) listRooms(writer http.ResponseWriter, request *http.Request) { httpkit.WriteRPCError(writer, request, err) return } - countries, err := h.roomListCountriesForUser(request, userResp.GetUser()) + appCode := appcode.FromContext(request.Context()) + if appCode != huwaaRoomDiscoveryAppCode && userResp.GetUser().GetRegionId() <= 0 { + // 非 Huwaa 仍以用户区域作为隔离边界;缺失时不能猜测或退化到 GLOBAL。 + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + countries, registrationCountries, err := h.roomListCountriesForUser(request, userResp.GetUser()) if err != nil { httpkit.WriteRPCError(writer, request, err) return } - countryCode, ok := roomListCountryQuery(request, countries) - if !ok { - httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") - return + allVisibleRegions := false + filterRegionID := int64(0) + countryCode := filter.CountryCode + if appCode == huwaaRoomDiscoveryAppCode { + switch { + case filter.RegionID > 0: + regions, err := h.roomListActiveRegions(request) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + if !roomListContainsActiveRegion(regions, filter.RegionID) { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + filterRegionID = filter.RegionID + case countryCode != "": + regions, err := h.roomListActiveRegions(request) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + if !roomListCatalogContainsCountry(regions, registrationCountries, countryCode) { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + // 国家项在 active 区域中有唯一归属,但查询按国家投影直接收敛,不再叠加区域条件。 + allVisibleRegions = true + default: + // Huwaa All 是产品默认页,不依赖运营白名单。 + allVisibleRegions = true + } + } else { + if filter.RegionID > 0 || !roomListCountryAllowed(countries, countryCode) { + // 非 Huwaa App 不开放客户端跨区筛选,国家也仍限当前用户区域。 + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + allVisibleRegions = h.roomRegionWhitelistAllows(request, viewerUserID) + } + queryRegionID := userResp.GetUser().GetRegionId() + if filterRegionID > 0 { + // 滚动发布期间旧 room-service 会忽略新 FilterRegionId;同时覆盖旧 VisibleRegionId 保证首页仍查选中区域。 + queryRegionID = filterRegionID } - allVisibleRegions := h.roomRegionWhitelistAllows(request, viewerUserID) resp, err := h.roomQueryClient.ListRooms(request.Context(), &roomv1.ListRoomsRequest{ Meta: httpkit.RoomMeta(request, "", ""), ViewerUserId: viewerUserID, - VisibleRegionId: userResp.GetUser().GetRegionId(), + VisibleRegionId: queryRegionID, ViewerCountryCode: userResp.GetUser().GetCountry(), AllVisibleRegions: allVisibleRegions, + FilterRegionId: filterRegionID, Tab: tab, Cursor: request.URL.Query().Get("cursor"), Limit: limit, @@ -340,25 +403,39 @@ func roomListQuery(request *http.Request) string { return strings.TrimSpace(request.URL.Query().Get("query")) } -func (h *Handler) roomListCountriesForUser(request *http.Request, user *userv1.User) ([]roomListCountryData, error) { - if user == nil || user.GetRegionId() <= 0 { - // 当前用户区域来自 user-service 主数据;缺失时不能猜测区域国家列表。 - return nil, xerr.New(xerr.InvalidArgument, "user region is required") +func (h *Handler) roomListCountriesForUser(request *http.Request, user *userv1.User) ([]roomListCountryData, []*userv1.Country, error) { + if user == nil { + return nil, nil, xerr.New(xerr.InvalidArgument, "user region is required") + } + countryResp, err := h.userCountryClient.ListRegistrationCountries(request.Context(), &userv1.ListRegistrationCountriesRequest{ + Meta: httpkit.UserMeta(request, ""), + }) + if err != nil { + return nil, nil, err + } + if user.GetRegionId() <= 0 { + // Huwaa All 不需要用户区域;旧 countries 字段留空,不用 IP/国家猜一个区域。 + return []roomListCountryData{}, countryResp.GetCountries(), nil } regionResp, err := h.userRegionClient.GetRegion(request.Context(), &userv1.GetRegionRequest{ Meta: httpkit.UserMeta(request, ""), RegionId: user.GetRegionId(), }) if err != nil { - return nil, err + return nil, nil, err } - countryResp, err := h.userCountryClient.ListRegistrationCountries(request.Context(), &userv1.ListRegistrationCountriesRequest{ - Meta: httpkit.UserMeta(request, ""), + return roomListCountriesFromProto(user, regionResp.GetRegion(), countryResp.GetCountries()), countryResp.GetCountries(), nil +} + +func (h *Handler) roomListActiveRegions(request *http.Request) ([]*userv1.Region, error) { + resp, err := h.userRegionClient.ListRegions(request.Context(), &userv1.ListRegionsRequest{ + Meta: httpkit.UserMeta(request, ""), + Status: roomFilterRegionStatusActive, }) if err != nil { return nil, err } - return roomListCountriesFromProto(user, regionResp.GetRegion(), countryResp.GetCountries()), nil + return resp.GetRegions(), nil } func roomListCountriesFromProto(user *userv1.User, region *userv1.Region, countries []*userv1.Country) []roomListCountryData { @@ -427,21 +504,74 @@ func roomListCountryDataFromProto(code string, country *userv1.Country) roomList return item } -func roomListCountryQuery(request *http.Request, countries []roomListCountryData) (string, bool) { - raw := strings.TrimSpace(request.URL.Query().Get("country")) - if raw == "" { - raw = strings.TrimSpace(request.URL.Query().Get("country_code")) - } - code := normalizeRoomListCountryCode(raw) +func roomListCountryAllowed(countries []roomListCountryData, code string) bool { + code = normalizeRoomListCountryCode(code) if code == "" { - return "", raw == "" + return true } for _, country := range countries { if normalizeRoomListCountryCode(country.CountryCode) == code { - return code, true + return true } } - return "", false + return false +} + +func roomListContainsActiveRegion(regions []*userv1.Region, regionID int64) bool { + if regionID <= 0 { + return false + } + for _, region := range regions { + if region != nil && region.GetRegionId() == regionID { + return true + } + } + return false +} + +func roomListCatalogContainsCountry(regions []*userv1.Region, countries []*userv1.Country, countryCode string) bool { + countryCode = normalizeRoomListCountryCode(countryCode) + if countryCode == "" { + return false + } + // 和 /rooms/filters 复用同一投影规则:只有 enabled 国家 + active 区域映射同时成立才能成为可查询值。 + for _, item := range roomFilterCatalogDataFromProto(regions, countries).Countries { + if item.CountryCode == countryCode { + return true + } + } + return false +} + +func parsePublicRoomListFilter(request *http.Request) (publicRoomListFilter, bool) { + if request == nil { + return publicRoomListFilter{}, false + } + rawRegionID := strings.TrimSpace(request.URL.Query().Get("region_id")) + rawCountryCode := strings.TrimSpace(request.URL.Query().Get("country_code")) + if rawCountryCode == "" { + // country 是既有客户端别名;新 Flutter 按目录下发的 country_code 回传。 + rawCountryCode = strings.TrimSpace(request.URL.Query().Get("country")) + } + if rawRegionID != "" && rawCountryCode != "" { + return publicRoomListFilter{}, false + } + filter := publicRoomListFilter{} + if rawRegionID != "" { + regionID, err := strconv.ParseInt(rawRegionID, 10, 64) + if err != nil || regionID <= 0 { + // All 必须不传筛选参数;region_id=0 不是目录下发的合法选项。 + return publicRoomListFilter{}, false + } + filter.RegionID = regionID + } + if rawCountryCode != "" { + filter.CountryCode = normalizeRoomListCountryCode(rawCountryCode) + if filter.CountryCode == "" { + return publicRoomListFilter{}, false + } + } + return filter, true } func normalizeRoomListCountryCode(countryCode string) string { @@ -723,59 +853,7 @@ func (h *Handler) getRoomContributionRank(writer http.ResponseWriter, request *h // getRoomGiftPanel 返回房间送礼面板初始化数据。 func (h *Handler) getRoomGiftPanel(writer http.ResponseWriter, request *http.Request) { - if h.roomQueryClient == nil || h.walletClient == nil { - httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") - return - } - roomID := strings.TrimSpace(request.PathValue("room_id")) - if !roomid.ValidStringID(roomID) { - httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") - return - } - - viewerUserID := auth.UserIDFromContext(request.Context()) - snapshotResp, err := h.roomQueryClient.GetRoomSnapshot(request.Context(), &roomv1.GetRoomSnapshotRequest{ - Meta: httpkit.RoomMeta(request, roomID, ""), - RoomId: roomID, - ViewerUserId: viewerUserID, - }) - if err != nil { - httpkit.WriteRPCError(writer, request, err) - return - } - snapshot := snapshotResp.GetRoom() - balances, err := h.walletClient.GetBalances(request.Context(), &walletv1.GetBalancesRequest{ - RequestId: httpkit.RequestIDFromContext(request.Context()), - UserId: viewerUserID, - AssetTypes: []string{"COIN"}, - AppCode: appcode.FromContext(request.Context()), - }) - if err != nil { - httpkit.WriteRPCError(writer, request, err) - return - } - panelConfig, err := h.roomGiftPanelBaseConfig(request, snapshot.GetVisibleRegionId()) - if err != nil { - httpkit.WriteRPCError(writer, request, err) - return - } - giftTypes := panelConfig.GiftTypes - gifts := panelConfig.Gifts - bagGifts, err := h.roomGiftBagGifts(request, viewerUserID, gifts) - if err != nil { - httpkit.WriteRPCError(writer, request, err) - return - } - panelGifts := append([]giftConfigData{}, gifts...) - panelGifts = append(panelGifts, bagGifts...) - recipientProfiles := h.roomDisplayProfileMap(request, roomGiftRecipientUserIDs(snapshot)) - httpkit.WriteOK(writer, request, roomGiftPanelData{ - CoinBalance: coinBalanceFromProto(balances), - Recipients: roomGiftRecipients(snapshot, recipientProfiles), - Tabs: roomGiftTabs(panelGifts, giftTypes), - Gifts: panelGifts, - QuantityPresets: []int32{1, 9, 99, 999}, - }) + h.serveRoomGiftPanel(writer, request) } func (h *Handler) roomGiftPanelBaseConfig(request *http.Request, regionID int64) (roomGiftPanelConfig, error) { @@ -784,35 +862,73 @@ func (h *Handler) roomGiftPanelBaseConfig(request *http.Request, regionID int64) if err != nil { return roomGiftPanelConfig{}, err } + return h.roomGiftPanelBaseConfigForVersion(request, app, regionID, catalogVersion, cacheable) +} + +// roomGiftPanelBaseConfigForVersion 使用已并行取得的 owner 版本加载稳定配置。 +// 类型与礼物列表互不依赖,因此 cache miss 时同时查询;区域过滤仍只由 wallet-service 解释。 +func (h *Handler) roomGiftPanelBaseConfigForVersion(request *http.Request, app string, regionID int64, catalogVersion int64, cacheable bool) (roomGiftPanelConfig, error) { + requestID := httpkit.RequestIDFromContext(request.Context()) + roomID := strings.TrimSpace(request.PathValue("room_id")) if cacheable && h.giftPanelCache != nil { + cacheStartedAt := time.Now() if cached, ok := h.giftPanelCache.Get(app, regionID, catalogVersion, time.Now()); ok { + logRoomGiftPanelStage(request.Context(), requestID, roomID, "config_cache_hit", cacheStartedAt, nil, false) return cached, nil } } - giftTypeResp, err := h.walletClient.ListGiftTypeConfigs(request.Context(), &walletv1.ListGiftTypeConfigsRequest{ - RequestId: httpkit.RequestIDFromContext(request.Context()), - AppCode: app, - ActiveOnly: true, - }) - if err != nil { - return roomGiftPanelConfig{}, err + + // 任一配置 RPC 失败就取消同批 sibling,并在 Wait 后返回;这样总预算取消时不会遗留继续占连接的查询 goroutine。 + configCtx, cancelConfig := context.WithCancel(request.Context()) + configRequest := request.WithContext(configCtx) + var waitGroup sync.WaitGroup + var giftTypeResp *walletv1.ListGiftTypeConfigsResponse + var giftTypeErr error + var giftResp *walletv1.ListGiftConfigsResponse + var giftErr error + waitGroup.Add(2) + go func() { + defer waitGroup.Done() + startedAt := time.Now() + giftTypeResp, giftTypeErr = h.walletClient.ListGiftTypeConfigs(configCtx, &walletv1.ListGiftTypeConfigsRequest{ + RequestId: requestID, + AppCode: app, + ActiveOnly: true, + }) + logRoomGiftPanelStage(configRequest.Context(), requestID, roomID, "gift_types", startedAt, giftTypeErr, false) + if giftTypeErr != nil { + cancelConfig() + } + }() + go func() { + defer waitGroup.Done() + startedAt := time.Now() + giftResp, giftErr = h.walletClient.ListGiftConfigs(configCtx, &walletv1.ListGiftConfigsRequest{ + RequestId: requestID, + AppCode: app, + Page: 1, + PageSize: 500, + ActiveOnly: true, + RegionId: regionID, + FilterRegion: true, + }) + logRoomGiftPanelStage(configRequest.Context(), requestID, roomID, "gifts", startedAt, giftErr, false) + if giftErr != nil { + cancelConfig() + } + }() + waitGroup.Wait() + cancelConfig() + if giftTypeErr != nil { + return roomGiftPanelConfig{}, giftTypeErr + } + if giftErr != nil { + return roomGiftPanelConfig{}, giftErr } giftTypes := make([]giftTypeConfigData, 0, len(giftTypeResp.GetGiftTypes())) for _, item := range giftTypeResp.GetGiftTypes() { giftTypes = append(giftTypes, giftTypeFromProto(item)) } - giftResp, err := h.walletClient.ListGiftConfigs(request.Context(), &walletv1.ListGiftConfigsRequest{ - RequestId: httpkit.RequestIDFromContext(request.Context()), - AppCode: app, - Page: 1, - PageSize: 500, - ActiveOnly: true, - RegionId: regionID, - FilterRegion: true, - }) - if err != nil { - return roomGiftPanelConfig{}, err - } gifts := make([]giftConfigData, 0, len(giftResp.GetGifts())) for _, gift := range giftResp.GetGifts() { gifts = append(gifts, giftFromProto(gift)) @@ -848,15 +964,29 @@ func (h *Handler) roomGiftBagGifts(request *http.Request, viewerUserID int64, gi if viewerUserID <= 0 || h.walletClient == nil || len(gifts) == 0 { return nil, nil } - resp, err := h.walletClient.ListUserResources(request.Context(), &walletv1.ListUserResourcesRequest{ + resp, err := h.roomGiftBagResources(request, viewerUserID) + if err != nil { + return nil, err + } + return roomGiftBagGiftsFromResources(resp, gifts), nil +} + +func (h *Handler) roomGiftBagResources(request *http.Request, viewerUserID int64) (*walletv1.ListUserResourcesResponse, error) { + if viewerUserID <= 0 || h.walletClient == nil { + return &walletv1.ListUserResourcesResponse{}, nil + } + return h.walletClient.ListUserResources(request.Context(), &walletv1.ListUserResourcesRequest{ RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), UserId: viewerUserID, ResourceType: "gift", ActiveOnly: true, }) - if err != nil { - return nil, err +} + +func roomGiftBagGiftsFromResources(resp *walletv1.ListUserResourcesResponse, gifts []giftConfigData) []giftConfigData { + if resp == nil || len(gifts) == 0 { + return nil } giftByResourceID := make(map[int64]giftConfigData, len(gifts)) for _, gift := range gifts { @@ -885,7 +1015,7 @@ func (h *Handler) roomGiftBagGifts(request *http.Request, viewerUserID int64, gi gift.RemainingQuantity = remaining bagGifts = append(bagGifts, gift) } - return bagGifts, nil + return bagGifts } // getRoomRocket 返回房间火箭物料配置和当前进度。 @@ -1164,6 +1294,8 @@ func (h *Handler) joinRoom(writer http.ResponseWriter, request *http.Request) { Role string `json:"role"` Password string `json:"password"` ResponseMode string `json:"response_mode"` + // PresenceRecovery 只由房间心跳 NOT_FOUND 后的客户端恢复链路设置。 + PresenceRecovery bool `json:"presence_recovery"` } if !httpkit.Decode(writer, request, &body) { @@ -1188,8 +1320,9 @@ func (h *Handler) joinRoom(writer http.ResponseWriter, request *http.Request) { ActorRegionId: actorSnapshot.regionID, ActorDisplayProfile: actorSnapshot.displayProfile, // 座驾快照在进房命令提交前生成;room-service 只保存快照,不回查 wallet。 - EntryVehicle: h.joinRoomEntryVehicleSnapshot(request, responseMode), - ResponseMode: responseMode, + EntryVehicle: h.joinRoomEntryVehicleSnapshot(request, responseMode), + ResponseMode: responseMode, + PresenceRecovery: body.PresenceRecovery, }) if err != nil { httpkit.WriteRPCError(writer, request, err) @@ -1293,6 +1426,7 @@ func (h *Handler) joinRoomInitialData(request *http.Request, requestedRoomID str Profiles: h.roomInitialProfiles(request, snapshot, viewerUserID), IM: roomIMData{GroupID: roomIMGroupID(roomID), NeedJoinGroup: true}, RTC: h.joinRoomRTCData(roomID, viewerUserID), + EffectiveVIP: roomEffectiveVIPDataFromProto(resp.GetEffectiveVip()), ServerTimeMS: resp.GetResult().GetServerTimeMs(), } } @@ -1317,10 +1451,23 @@ func (h *Handler) joinRoomInitialLiteData(request *http.Request, requestedRoomID Profiles: h.roomInitialBasicProfiles(request, snapshot, viewerUserID), IM: roomIMData{GroupID: roomIMGroupID(roomID), NeedJoinGroup: true}, RTC: h.joinRoomRTCData(roomID, viewerUserID), + EffectiveVIP: roomEffectiveVIPDataFromProto(resp.GetEffectiveVip()), ServerTimeMS: resp.GetResult().GetServerTimeMs(), } } +func roomEffectiveVIPDataFromProto(snapshot *roomv1.RoomEffectiveVipSnapshot) roomEffectiveVIPData { + if snapshot == nil { + return roomEffectiveVIPData{} + } + return roomEffectiveVIPData{ + ProgramType: snapshot.GetProgramType(), + Level: snapshot.GetLevel(), + Name: snapshot.GetName(), + RoomEntryNoticeEnabled: snapshot.GetRoomEntryNoticeEnabled(), + } +} + func (h *Handler) joinRoomRTCData(roomID string, viewerUserID int64) roomRTCData { rtc := roomRTCData{NeedToken: true} token, err := h.generateTencentRTCToken(viewerUserID, roomID) @@ -1744,38 +1891,107 @@ func (h *Handler) sendGift(writer http.ResponseWriter, request *http.Request) { h.handleSendGift(writer, request, "") } -// sendGiftBatch 是新 Flutter 的多人送礼入口。 -// 它不改变账务和房间事实,只通过 display_mode=batch 要求 room-service 合并房间展示 IM。 +// sendGiftBatch 仅保留给已发布旧客户端的 V1 多人送礼入口。 +// 它不改变账务和房间事实,只通过 display_mode=batch 要求 room-service 合并房间展示 IM; +// 当前 Flutter 无论单人或多人都固定调用 sendGiftV2。 func (h *Handler) sendGiftBatch(writer http.ResponseWriter, request *http.Request) { h.handleSendGift(writer, request, "batch") } +// sendGiftV2 unifies single/multi-target gift batches. It preserves every V1 response field and adds +// a nested V2 settlement result, so old model parsing remains valid while new Flutter can reject stale +// responses and replay the exact committed result after a timeout. +func (h *Handler) sendGiftV2(writer http.ResponseWriter, request *http.Request) { + h.handleSendGift(writer, request, "auto_v2") +} + func (h *Handler) handleSendGift(writer http.ResponseWriter, request *http.Request, displayMode string) { + protocolV2 := displayMode == "auto_v2" var body struct { - RoomID string `json:"room_id"` - CommandID string `json:"command_id"` - TargetType string `json:"target_type"` - TargetUserID int64 `json:"target_user_id"` - TargetUserIDs []int64 `json:"target_user_ids"` - GiftID string `json:"gift_id"` - GiftCount int32 `json:"gift_count"` - PoolID string `json:"pool_id"` - EntitlementID string `json:"entitlement_id"` - Source string `json:"source"` + RoomID string `json:"room_id"` + CommandID string `json:"command_id"` + TargetType string `json:"target_type"` + TargetUserID int64 `json:"target_user_id"` + TargetUserIDs []int64 `json:"target_user_ids"` + GiftID string `json:"gift_id"` + GiftCount int32 `json:"gift_count"` + PoolID string `json:"pool_id"` + EntitlementID string `json:"entitlement_id"` + Source string `json:"source"` + ComboSessionID string `json:"combo_session_id"` + BatchSeq int64 `json:"batch_seq"` } if !httpkit.Decode(writer, request, &body) { return } + roomID := strings.TrimSpace(body.RoomID) + if roomID == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } targetType := strings.TrimSpace(body.TargetType) if targetType == "" { targetType = "user" } - targetUserIDs := normalizeGiftTargetUserIDs(body.TargetUserID, body.TargetUserIDs) - if targetType == "user" && len(targetUserIDs) == 0 { + if targetType != "user" { + // room-service 当前协议只开放 user;未知类型必须在进入 limiter 前明确 4xx, + // 不能用零目标请求污染容量 key,或因 limiter 输入不完整退化成 502。 httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } + targetUserIDs := normalizeGiftTargetUserIDs(body.TargetUserID, body.TargetUserIDs) + if len(targetUserIDs) == 0 { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + if len(targetUserIDs) > h.giftMaxTargets { + // 权重超过所有桶容量时永远不可能靠等待恢复;在进入 Redis 前明确拒绝为 4xx, + // 同时用去重后的目标数判定,保留旧客户端重复提交同一 target 的兼容性。 + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + if displayMode == "auto_v2" { + // RoomGiftBatchSent is only the merged multi-recipient display event. A single-recipient V2 + // micro-batch still gets V2 settlement metadata but must not enter the legacy multi-target path. + if len(targetUserIDs) > 1 { + displayMode = "batch" + } else { + displayMode = "" + } + } + commandID := strings.TrimSpace(body.CommandID) + comboSessionID := strings.TrimSpace(body.ComboSessionID) + if !giftlimits.ValidCommandID(commandID) || strings.TrimSpace(body.GiftID) == "" || body.GiftCount <= 0 || int(body.GiftCount) > h.giftMaxCount { + // command_id is the financial idempotency key, not a free-form trace field. Bound it before + // Redis/MySQL keys are built; gift_count also gets a transport cap before any profile RPC. + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + if protocolV2 && (comboSessionID == "" || len(comboSessionID) > giftlimits.MaxCommandIDBytes || body.BatchSeq <= 0) { + // V2 batches expose the session/sequence used to derive command_id. Both fields are persisted + // in the idempotency payload, so a retry cannot silently attach the same key to another batch. + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + totalGiftUnits, hardLimitOK := giftlimits.WorkUnits(int64(body.GiftCount), len(targetUserIDs)) + if !hardLimitOK || totalGiftUnits > int64(h.giftMaxUnits) { + // A per-count limit alone still permits 999*30 lucky draws. The product quantity remains + // available for a small recipient set while explosive fan-out is rejected deterministically. + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + lease, allowed := h.acquireGiftCapacity(writer, request, roomID, int(totalGiftUnits)) + if !allowed { + return + } + defer h.releaseGiftCapacity(lease) + // profile、host 和最终 room SendGift 共用一个总 deadline,不能让三个串行 RPC 各自 + // 获得完整 gRPC 默认超时。in_flight lease 在配置校验中严格大于此预算并额外留 2s。 + giftCtx, cancelGift := context.WithTimeout(request.Context(), h.giftRequestTimeout) + defer cancelGift() + request = request.WithContext(giftCtx) + userSnapshots, err := h.resolveGiftUserSnapshots(request, targetUserIDs) if err != nil { httpkit.WriteRPCError(writer, request, err) @@ -1789,7 +2005,7 @@ func (h *Handler) handleSendGift(writer http.ResponseWriter, request *http.Reque firstTargetScope := firstGiftTargetHostScope(targetHostScopes, firstUserID(targetUserIDs)) resp, err := h.roomClient.SendGift(request.Context(), &roomv1.SendGiftRequest{ - Meta: httpkit.RoomMeta(request, body.RoomID, body.CommandID), + Meta: httpkit.RoomMeta(request, roomID, commandID), TargetType: targetType, TargetUserIds: targetUserIDs, TargetUserId: firstUserID(targetUserIDs), @@ -1807,10 +2023,37 @@ func (h *Handler) handleSendGift(writer http.ResponseWriter, request *http.Reque SenderDisplayProfile: userSnapshots.senderDisplayProfile, TargetDisplayProfiles: userSnapshots.targetDisplayProfiles, DisplayMode: strings.TrimSpace(displayMode), + ComboSessionId: comboSessionID, + BatchSeq: body.BatchSeq, }) + if err == nil && !protocolV2 { + // room-service 对所有送礼命令都持久化 typed V2 结果,保证内部 Saga 和幂等重放只有 + // 一套实现;但旧 HTTP 路由的外部 JSON 必须保持原形。这里重新组装所有 V1 顶层字段, + // 只剥离内部追加的 v2,既不修改 gRPC 返回对象,也不影响 V2 路由返回完整结算结果。 + resp = legacySendGiftHTTPResponse(resp) + } httpkit.Write(writer, request, resp, err) } +func legacySendGiftHTTPResponse(resp *roomv1.SendGiftResponse) *roomv1.SendGiftResponse { + if resp == nil { + return nil + } + return &roomv1.SendGiftResponse{ + Result: resp.GetResult(), + BillingReceiptId: resp.GetBillingReceiptId(), + RoomHeat: resp.GetRoomHeat(), + GiftRank: resp.GetGiftRank(), + Room: resp.GetRoom(), + Rocket: resp.GetRocket(), + LuckyGift: resp.GetLuckyGift(), + LuckyGifts: resp.GetLuckyGifts(), + CoinBalanceAfter: resp.GetCoinBalanceAfter(), + GiftIncomeBalanceAfter: resp.GetGiftIncomeBalanceAfter(), + BatchDisplay: resp.GetBatchDisplay(), + } +} + type roomActorJoinSnapshot struct { countryID int64 regionID int64 diff --git a/services/gateway-service/internal/transport/http/roomapi/room_view.go b/services/gateway-service/internal/transport/http/roomapi/room_view.go index fb7972c6..c96f3553 100644 --- a/services/gateway-service/internal/transport/http/roomapi/room_view.go +++ b/services/gateway-service/internal/transport/http/roomapi/room_view.go @@ -5,6 +5,7 @@ import ( "hyapp/pkg/imgroup" "hyapp/pkg/tencentrtc" + "hyapp/services/gateway-service/internal/appconfig" roomv1 "hyapp.local/api/proto/room/v1" walletv1 "hyapp.local/api/proto/wallet/v1" @@ -70,6 +71,7 @@ type joinRoomData struct { Profiles []roomDisplayProfileData `json:"profiles"` IM roomIMData `json:"im"` RTC roomRTCData `json:"rtc"` + EffectiveVIP roomEffectiveVIPData `json:"effective_vip"` ServerTimeMS int64 `json:"server_time_ms"` } @@ -81,6 +83,7 @@ type joinRoomLiteData struct { Profiles []roomBasicProfileData `json:"profiles"` IM roomIMData `json:"im"` RTC roomRTCData `json:"rtc"` + EffectiveVIP roomEffectiveVIPData `json:"effective_vip"` ServerTimeMS int64 `json:"server_time_ms"` } @@ -98,6 +101,15 @@ type roomDetailData struct { ServerTimeMS int64 `json:"server_time_ms"` } +// roomEffectiveVIPData 是进房瞬间由 room-service 固化的轻量展示事实。 +// 完整权益仍以 wallet /vip/me 为准;这里仅用于当前用户首屏和房内进场效果立即渲染。 +type roomEffectiveVIPData struct { + ProgramType string `json:"program_type"` + Level int32 `json:"level"` + Name string `json:"name"` + RoomEntryNoticeEnabled bool `json:"room_entry_notice_enabled"` +} + type roomOnlineUsersData struct { Items []roomOnlineUserData `json:"items"` Total int64 `json:"total"` @@ -220,6 +232,9 @@ type roomGiftPanelData struct { Tabs []roomGiftTabData `json:"tabs"` Gifts []giftConfigData `json:"gifts"` QuantityPresets []int32 `json:"quantity_presets"` + // ComboConfig is additive: old Flutter models ignore it, while V2 clients receive batching, + // concurrency, retry and rollout policy in the same response that opens the gift panel. + ComboConfig appconfig.GiftComboConfig `json:"combo_config"` } type roomRocketData struct { diff --git a/services/gateway-service/internal/transport/http/router.go b/services/gateway-service/internal/transport/http/router.go index ada28c23..edc512b4 100644 --- a/services/gateway-service/internal/transport/http/router.go +++ b/services/gateway-service/internal/transport/http/router.go @@ -23,18 +23,24 @@ import ( // 路径注册集中在 httproutes,gateway 依赖装配和鉴权 wrapper 仍留在 transport 入口层。 func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { roomAPI := roomapi.New(roomapi.Config{ - RoomClient: h.roomClient, - RoomGuardClient: h.roomGuardClient, - RoomQueryClient: h.roomQueryClient, - UserProfileClient: h.userProfileClient, - UserCountryClient: h.userCountryClient, - UserRegionClient: h.userRegionClient, - UserSocialClient: h.userSocialClient, - UserHostClient: h.userHostClient, - AppConfigReader: h.appConfigReader, - WalletClient: h.walletClient, - GrowthLevelClient: h.growthLevelClient, - AchievementClient: h.achievementClient, + RoomClient: h.roomClient, + RoomGuardClient: h.roomGuardClient, + RoomQueryClient: h.roomQueryClient, + UserProfileClient: h.userProfileClient, + UserCountryClient: h.userCountryClient, + UserRegionClient: h.userRegionClient, + UserSocialClient: h.userSocialClient, + UserHostClient: h.userHostClient, + AppConfigReader: h.appConfigReader, + WalletClient: h.walletClient, + GrowthLevelClient: h.growthLevelClient, + AchievementClient: h.achievementClient, + GiftCapacityEnabled: h.giftCapacityEnabled, + GiftCapacityLimiter: h.giftCapacityLimiter, + GiftRequestTimeout: h.giftRequestTimeout, + GiftMaxTargets: h.giftMaxTargets, + GiftMaxCount: h.giftMaxCount, + GiftMaxUnits: h.giftMaxUnits, RTCTokenConfig: tencentrtc.TokenConfig{ Enabled: h.tencentRTC.Enabled, SDKAppID: h.tencentRTC.SDKAppID, @@ -51,6 +57,7 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { UserIdentityClient: h.userIdentityClient, UserProfileClient: h.userProfileClient, UserHostClient: h.userHostClient, + BroadcastClient: h.broadcastClient, IMAccountImporter: h.tencentIMImporter, IMPublisher: h.tencentIMPublisher, IMGroupIDPrefix: h.tencentIM.GroupIDPrefix, @@ -114,6 +121,7 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { UserHostAdminClient: h.userHostAdminClient, UserProfileClient: h.userProfileClient, GrowthLevelClient: h.growthLevelClient, + BroadcastClient: h.broadcastClient, }) appAPI := appapi.New(appapi.Config{ AppConfigReader: h.appConfigReader, @@ -127,6 +135,7 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { RoomClient: h.roomClient, RoomGuardClient: h.roomGuardClient, UserProfileClient: h.userProfileClient, + UserHostClient: h.userHostClient, TencentIM: callbackapi.TencentIMConfig{ SDKAppID: h.tencentIM.SDKAppID, AdminIdentifier: h.tencentIM.AdminIdentifier, diff --git a/services/gateway-service/internal/transport/http/userapi/agency_center_handler.go b/services/gateway-service/internal/transport/http/userapi/agency_center_handler.go index 06828e32..86cecdcb 100644 --- a/services/gateway-service/internal/transport/http/userapi/agency_center_handler.go +++ b/services/gateway-service/internal/transport/http/userapi/agency_center_handler.go @@ -33,6 +33,7 @@ type agencyCenterUserData struct { DisplayUserID string `json:"display_user_id"` Username string `json:"username"` Avatar string `json:"avatar,omitempty"` + Country string `json:"country,omitempty"` } type agencyCenterHostData struct { @@ -465,6 +466,7 @@ func agencyCenterUserFromProto(user *userv1.User) agencyCenterUserData { DisplayUserID: user.GetDisplayUserId(), Username: user.GetUsername(), Avatar: user.GetAvatar(), + Country: firstBDLeaderNonBlank(user.GetCountryDisplayName(), user.GetCountryName(), user.GetCountry()), } } diff --git a/services/gateway-service/internal/transport/http/userapi/agency_center_stats_handler.go b/services/gateway-service/internal/transport/http/userapi/agency_center_stats_handler.go new file mode 100644 index 00000000..a290c590 --- /dev/null +++ b/services/gateway-service/internal/transport/http/userapi/agency_center_stats_handler.go @@ -0,0 +1,165 @@ +package userapi + +import ( + "context" + "net/http" + "sync" + + userv1 "hyapp.local/api/proto/user/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + "hyapp/services/gateway-service/internal/transport/http/httpkit" +) + +type agencyRevenueStatsClient interface { + GetHostRevenueStats(context.Context, *walletv1.GetHostRevenueStatsRequest) (*walletv1.GetHostRevenueStatsResponse, error) + GetAgencyPointShareStats(context.Context, *walletv1.GetAgencyPointShareStatsRequest) (*walletv1.GetAgencyPointShareStatsResponse, error) + GetAgencyHostGiftStats(context.Context, *walletv1.GetAgencyHostGiftStatsRequest) (*walletv1.GetAgencyHostGiftStatsResponse, error) +} + +type agencyCenterStatsSummaryData struct { + TotalEarnings int64 `json:"total_earnings"` + GiftIncome int64 `json:"gift_income"` + ShareIncome int64 `json:"share_income"` + TotalHosts int `json:"total_hosts"` + GiftedHostCount int64 `json:"gifted_host_count"` +} + +type agencyCenterHostStatsData struct { + MembershipID string `json:"membership_id"` + HostUserID string `json:"host_user_id"` + DisplayUserID string `json:"display_user_id"` + Username string `json:"username"` + Avatar string `json:"avatar,omitempty"` + JoinedAtMS int64 `json:"joined_at_ms"` + DiamondEarnings int64 `json:"diamond_earnings"` + DiamondExchanged int64 `json:"diamond_exchanged"` + GiftSenders int64 `json:"gift_senders"` + OnlineDurationMS int64 `json:"online_duration_ms"` + ValidMicDurationMS int64 `json:"valid_mic_duration_ms"` + ValidMicDays int64 `json:"valid_mic_days"` + Removable bool `json:"removable"` +} + +// getAgencyCenterStats 只做跨 owner 编排:成员关系/互动来自 user-service,金额和分成来自 wallet-service。 +// 每个主播的两次 RPC 使用有界并发,避免大公会串行超时,也避免无上限 goroutine 压垮下游。 +func (h *Handler) getAgencyCenterStats(writer http.ResponseWriter, request *http.Request) { + agency, ok := h.resolveCurrentOwnerAgency(writer, request) + if !ok { + return + } + revenueClient, revenueOK := h.walletClient.(agencyRevenueStatsClient) + engagementClient, engagementOK := h.userHostClient.(hostEngagementStatsClient) + if !revenueOK || !engagementOK { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + startDate, endDate, startAtMS, endAtMS, ok := parseHostCenterStatsRange(request) + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid date range") + return + } + + membersResp, err := h.userHostClient.GetAgencyMembers(request.Context(), &userv1.GetAgencyMembersRequest{ + Meta: httpkit.UserMeta(request, ""), AgencyId: agency.GetAgencyId(), Status: membershipStatusActive, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + memberships := make([]*userv1.AgencyMembership, 0, len(membersResp.GetMemberships())) + userIDs := make([]int64, 0, len(membersResp.GetMemberships())) + for _, membership := range membersResp.GetMemberships() { + memberships = append(memberships, membership) + userIDs = append(userIDs, membership.GetHostUserId()) + } + profiles, err := h.userProfiles(request, userIDs) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + requestID := httpkit.RequestIDFromContext(request.Context()) + appCode := appcode.FromContext(request.Context()) + shareResp, err := revenueClient.GetAgencyPointShareStats(request.Context(), &walletv1.GetAgencyPointShareStatsRequest{ + RequestId: requestID, AppCode: appCode, AgencyOwnerUserId: agency.GetOwnerUserId(), StartAtMs: startAtMS, EndAtMs: endAtMS, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + giftResp, err := revenueClient.GetAgencyHostGiftStats(request.Context(), &walletv1.GetAgencyHostGiftStatsRequest{ + RequestId: requestID, AppCode: appCode, HostUserIds: userIDs, StartAtMs: startAtMS, EndAtMs: endAtMS, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + + items := make([]agencyCenterHostStatsData, len(memberships)) + ctx, cancel := context.WithCancel(request.Context()) + defer cancel() + semaphore := make(chan struct{}, 8) + var wait sync.WaitGroup + var firstErr error + var errorMu sync.Mutex + for index, membership := range memberships { + index, membership := index, membership + wait.Add(1) + go func() { + defer wait.Done() + semaphore <- struct{}{} + defer func() { <-semaphore }() + revenue, callErr := revenueClient.GetHostRevenueStats(ctx, &walletv1.GetHostRevenueStatsRequest{ + RequestId: requestID, AppCode: appCode, HostUserId: membership.GetHostUserId(), StartAtMs: startAtMS, EndAtMs: endAtMS, + }) + if callErr == nil { + var engagement *userv1.GetHostEngagementStatsResponse + engagement, callErr = engagementClient.GetHostEngagementStats(ctx, &userv1.GetHostEngagementStatsRequest{ + Meta: httpkit.UserMeta(request, ""), HostUserId: membership.GetHostUserId(), StartAtMs: startAtMS, EndAtMs: endAtMS, + }) + if callErr == nil { + profile := profiles[membership.GetHostUserId()] + items[index] = agencyCenterHostStatsData{ + MembershipID: int64String(membership.GetMembershipId()), HostUserID: int64String(membership.GetHostUserId()), + DisplayUserID: profile.DisplayUserID, Username: profile.Username, Avatar: profile.Avatar, JoinedAtMS: membership.GetJoinedAtMs(), + DiamondEarnings: revenue.GetStats().GetDiamondEarnings(), DiamondExchanged: revenue.GetStats().GetDiamondExchanged(), GiftSenders: revenue.GetStats().GetGiftSenders(), + OnlineDurationMS: engagement.GetStats().GetOnlineDurationMs(), ValidMicDurationMS: engagement.GetStats().GetValidMicDurationMs(), ValidMicDays: engagement.GetStats().GetValidMicDays(), + Removable: membership.GetHostUserId() != agency.GetOwnerUserId() && membership.GetMembershipType() != membershipTypeOwner, + } + } + } + if callErr != nil { + errorMu.Lock() + if firstErr == nil { + firstErr = callErr + cancel() + } + errorMu.Unlock() + } + }() + } + wait.Wait() + if firstErr != nil { + httpkit.WriteRPCError(writer, request, firstErr) + return + } + shareIncome := shareResp.GetStats().GetShareIncome() + // 公会长同时是主播时,其 POINT 流水包含 Agency 分成;主播行只展示个人收益,必须扣除同区间分成。 + for index := range items { + if items[index].HostUserID == int64String(agency.GetOwnerUserId()) { + items[index].DiamondEarnings -= shareIncome + if items[index].DiamondEarnings < 0 { + items[index].DiamondEarnings = 0 + } + } + } + giftIncome := giftResp.GetStats().GetGiftIncome() + httpkit.WriteOK(writer, request, map[string]any{ + "range": hostCenterStatsRangeData{StartDate: startDate, EndDate: endDate, Timezone: "UTC"}, + "summary": agencyCenterStatsSummaryData{ + TotalEarnings: giftIncome + shareIncome, GiftIncome: giftIncome, ShareIncome: shareIncome, + TotalHosts: len(items), GiftedHostCount: giftResp.GetStats().GetGiftedHostCount(), + }, + "hosts": items, + }) +} diff --git a/services/gateway-service/internal/transport/http/userapi/bd_center_stats_handler.go b/services/gateway-service/internal/transport/http/userapi/bd_center_stats_handler.go new file mode 100644 index 00000000..2613f3df --- /dev/null +++ b/services/gateway-service/internal/transport/http/userapi/bd_center_stats_handler.go @@ -0,0 +1,117 @@ +package userapi + +import ( + "context" + "net/http" + "sync" + + userv1 "hyapp.local/api/proto/user/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + "hyapp/services/gateway-service/internal/auth" + "hyapp/services/gateway-service/internal/transport/http/httpkit" +) + +type agencyHostGiftStatsClient interface { + GetAgencyHostGiftStats(context.Context, *walletv1.GetAgencyHostGiftStatsRequest) (*walletv1.GetAgencyHostGiftStatsResponse, error) +} + +type bdCenterAgencyStatsData struct { + Agency hostAgencyData `json:"agency"` + Owner agencyCenterUserData `json:"owner"` + Country string `json:"country"` + TotalHosts int `json:"total_hosts"` + GiftedHostCount int64 `json:"gifted_host_count"` + DiamondEarnings int64 `json:"diamond_earnings"` + JoinedAtMS int64 `json:"joined_at_ms"` +} + +// getBDCenterStats 返回 BD 直属 Agency 的收礼统计;工资和 Agency 分成不属于设计稿口径,刻意不返回。 +func (h *Handler) getBDCenterStats(writer http.ResponseWriter, request *http.Request) { + userID := auth.UserIDFromContext(request.Context()) + if _, ok := h.resolveActiveBD(writer, request, userID); !ok { + return + } + statsClient, ok := h.walletClient.(agencyHostGiftStatsClient) + if !ok { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + startDate, endDate, startAtMS, endAtMS, ok := parseHostCenterStatsRange(request) + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid date range") + return + } + agenciesResp, err := h.userHostClient.ListBDAgencies(request.Context(), &userv1.ListBDAgenciesRequest{ + Meta: httpkit.UserMeta(request, ""), BdUserId: userID, Status: agencyStatusActive, PageSize: 100, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + ownerIDs := make([]int64, 0, len(agenciesResp.GetAgencies())) + for _, agency := range agenciesResp.GetAgencies() { + ownerIDs = append(ownerIDs, agency.GetOwnerUserId()) + } + owners, err := h.userProfiles(request, ownerIDs) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + + items := make([]bdCenterAgencyStatsData, len(agenciesResp.GetAgencies())) + ctx, cancel := context.WithCancel(request.Context()) + defer cancel() + semaphore := make(chan struct{}, 8) + var wait sync.WaitGroup + var firstErr error + var errorMu sync.Mutex + for index, agency := range agenciesResp.GetAgencies() { + index, agency := index, agency + wait.Add(1) + go func() { + defer wait.Done() + semaphore <- struct{}{} + defer func() { <-semaphore }() + members, callErr := h.userHostClient.GetAgencyMembers(ctx, &userv1.GetAgencyMembersRequest{ + Meta: httpkit.UserMeta(request, ""), AgencyId: agency.GetAgencyId(), Status: membershipStatusActive, + }) + hostIDs := make([]int64, 0, len(members.GetMemberships())) + if callErr == nil { + for _, membership := range members.GetMemberships() { + hostIDs = append(hostIDs, membership.GetHostUserId()) + } + var stats *walletv1.GetAgencyHostGiftStatsResponse + stats, callErr = statsClient.GetAgencyHostGiftStats(ctx, &walletv1.GetAgencyHostGiftStatsRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), + HostUserIds: hostIDs, StartAtMs: startAtMS, EndAtMs: endAtMS, + }) + if callErr == nil { + owner := owners[agency.GetOwnerUserId()] + items[index] = bdCenterAgencyStatsData{ + Agency: hostAgencyFromProto(agency), Owner: owner, Country: owner.Country, + TotalHosts: len(hostIDs), GiftedHostCount: stats.GetStats().GetGiftedHostCount(), + DiamondEarnings: stats.GetStats().GetGiftIncome(), JoinedAtMS: agency.GetCreatedAtMs(), + } + } + } + if callErr != nil { + errorMu.Lock() + if firstErr == nil { + firstErr = callErr + cancel() + } + errorMu.Unlock() + } + }() + } + wait.Wait() + if firstErr != nil { + httpkit.WriteRPCError(writer, request, firstErr) + return + } + httpkit.WriteOK(writer, request, map[string]any{ + "range": hostCenterStatsRangeData{StartDate: startDate, EndDate: endDate, Timezone: "UTC"}, + "items": items, "total": len(items), + }) +} diff --git a/services/gateway-service/internal/transport/http/userapi/handler.go b/services/gateway-service/internal/transport/http/userapi/handler.go index 4007f6b4..a12f296e 100644 --- a/services/gateway-service/internal/transport/http/userapi/handler.go +++ b/services/gateway-service/internal/transport/http/userapi/handler.go @@ -73,8 +73,10 @@ func (h *Handler) UserHandlers() httproutes.UserHandlers { ListAgencyRooms: h.listAgencyRooms, UpdateAgencyProfile: h.updateAgencyProfile, GetHostCenterAgency: h.getHostCenterAgency, + GetHostCenterStats: h.getHostCenterStats, GetHostCenterPlatformPolicy: h.getHostCenterPlatformPolicy, GetAgencyCenterOverview: h.getAgencyCenterOverview, + GetAgencyCenterStats: h.getAgencyCenterStats, GetAgencyCenterPlatformPolicy: h.getAgencyCenterPlatformPolicy, ListAgencyCenterHosts: h.listAgencyCenterHosts, ListAgencyCenterApplications: h.listAgencyCenterApplications, @@ -87,6 +89,7 @@ func (h *Handler) UserHandlers() httproutes.UserHandlers { InviteBDLeaderBD: h.inviteBDLeaderBD, InviteBDLeaderAgency: h.inviteBDLeaderAgency, GetBDCenterOverview: h.getBDCenterOverview, + GetBDCenterStats: h.getBDCenterStats, ListBDCenterAgencies: h.listBDCenterAgencies, InviteBDAgency: h.inviteBDAgency, ListMyRoleInvitations: h.listMyRoleInvitations, diff --git a/services/gateway-service/internal/transport/http/userapi/host_center_agency_handler.go b/services/gateway-service/internal/transport/http/userapi/host_center_agency_handler.go index 004f53f4..7aeb1fc8 100644 --- a/services/gateway-service/internal/transport/http/userapi/host_center_agency_handler.go +++ b/services/gateway-service/internal/transport/http/userapi/host_center_agency_handler.go @@ -8,10 +8,11 @@ import ( ) type hostCenterAgencyData struct { - AgencyID string `json:"agency_id"` - ShortID string `json:"short_id"` - Name string `json:"name"` - Avatar string `json:"avatar,omitempty"` + AgencyID string `json:"agency_id"` + ShortID string `json:"short_id"` + Name string `json:"name"` + Avatar string `json:"avatar,omitempty"` + JoinedAtMS int64 `json:"joined_at_ms,omitempty"` } func (h *Handler) getHostCenterAgency(writer http.ResponseWriter, request *http.Request) { @@ -49,6 +50,21 @@ func (h *Handler) getHostCenterAgency(writer http.ResponseWriter, request *http. Name: agency.GetName(), Avatar: agency.GetAvatar(), } + // 加入时间来自当前 membership 事实,不使用 host 首次成为主播时间;主播换公会后两者语义不同。 + membersResp, err := h.userHostClient.GetAgencyMembers(request.Context(), &userv1.GetAgencyMembersRequest{ + Meta: httpkit.UserMeta(request, ""), + AgencyId: agency.GetAgencyId(), + Status: "active", + }) + if err == nil { + // 加入时间是新增展示字段;该补充查询失败时保留旧响应,不能让 Lalu 历史页面因非关键字段不可用而整体失败。 + for _, membership := range membersResp.GetMemberships() { + if membership.GetMembershipId() == profile.GetCurrentMembershipId() && membership.GetHostUserId() == profile.GetUserId() { + data.JoinedAtMS = membership.GetJoinedAtMs() + break + } + } + } if agency.GetOwnerUserId() > 0 { profiles, err := h.userProfiles(request, []int64{agency.GetOwnerUserId()}) if err != nil { diff --git a/services/gateway-service/internal/transport/http/userapi/host_center_stats_handler.go b/services/gateway-service/internal/transport/http/userapi/host_center_stats_handler.go new file mode 100644 index 00000000..228ecde2 --- /dev/null +++ b/services/gateway-service/internal/transport/http/userapi/host_center_stats_handler.go @@ -0,0 +1,114 @@ +package userapi + +import ( + "context" + "net/http" + "strings" + "time" + + userv1 "hyapp.local/api/proto/user/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + "hyapp/services/gateway-service/internal/transport/http/httpkit" +) + +type hostEngagementStatsClient interface { + GetHostEngagementStats(context.Context, *userv1.GetHostEngagementStatsRequest) (*userv1.GetHostEngagementStatsResponse, error) +} + +type hostRevenueStatsClient interface { + GetHostRevenueStats(context.Context, *walletv1.GetHostRevenueStatsRequest) (*walletv1.GetHostRevenueStatsResponse, error) +} + +const hostCenterMaxStatsDays = 366 + +type hostCenterStatsRangeData struct { + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + Timezone string `json:"timezone"` +} + +type hostCenterStatsMetricsData struct { + DiamondEarnings int64 `json:"diamond_earnings"` + DiamondExchanged int64 `json:"diamond_exchanged"` + GiftSenders int64 `json:"gift_senders"` + OnlineDurationMS int64 `json:"online_duration_ms"` + ValidMicDurationMS int64 `json:"valid_mic_duration_ms"` + ValidMicDays int64 `json:"valid_mic_days"` + PrivateMessageSenders int64 `json:"private_message_senders"` + NewFollowers int64 `json:"new_followers"` +} + +// getHostCenterStats 只编排 user/wallet 两个 owner 的只读聚合,不在 gateway 重算账务或在线时长。 +func (h *Handler) getHostCenterStats(writer http.ResponseWriter, request *http.Request) { + profile, ok := h.resolveCurrentActiveHostProfile(writer, request) + if !ok { + return + } + engagementClient, engagementOK := h.userHostClient.(hostEngagementStatsClient) + revenueClient, revenueOK := h.walletClient.(hostRevenueStatsClient) + if !engagementOK || !revenueOK { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + startDate, endDate, startAtMS, endAtMS, ok := parseHostCenterStatsRange(request) + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid date range") + return + } + + engagementResp, err := engagementClient.GetHostEngagementStats(request.Context(), &userv1.GetHostEngagementStatsRequest{ + Meta: httpkit.UserMeta(request, ""), + HostUserId: profile.GetUserId(), + StartAtMs: startAtMS, + EndAtMs: endAtMS, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + revenueResp, err := revenueClient.GetHostRevenueStats(request.Context(), &walletv1.GetHostRevenueStatsRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + AppCode: appcode.FromContext(request.Context()), + HostUserId: profile.GetUserId(), + StartAtMs: startAtMS, + EndAtMs: endAtMS, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + engagement := engagementResp.GetStats() + revenue := revenueResp.GetStats() + httpkit.WriteOK(writer, request, map[string]any{ + "range": hostCenterStatsRangeData{StartDate: startDate, EndDate: endDate, Timezone: "UTC"}, + "metrics": hostCenterStatsMetricsData{ + DiamondEarnings: revenue.GetDiamondEarnings(), + DiamondExchanged: revenue.GetDiamondExchanged(), + GiftSenders: revenue.GetGiftSenders(), + OnlineDurationMS: engagement.GetOnlineDurationMs(), + ValidMicDurationMS: engagement.GetValidMicDurationMs(), + ValidMicDays: engagement.GetValidMicDays(), + PrivateMessageSenders: engagement.GetPrivateMessageSenders(), + NewFollowers: engagement.GetNewFollowers(), + }, + }) +} + +func parseHostCenterStatsRange(request *http.Request) (string, string, int64, int64, bool) { + startText := strings.TrimSpace(request.URL.Query().Get("start_date")) + endText := strings.TrimSpace(request.URL.Query().Get("end_date")) + if startText == "" || endText == "" { + return "", "", 0, 0, false + } + start, startErr := time.Parse("2006-01-02", startText) + end, endErr := time.Parse("2006-01-02", endText) + if startErr != nil || endErr != nil || end.Before(start) { + return "", "", 0, 0, false + } + endExclusive := end.Add(24 * time.Hour) + if int(endExclusive.Sub(start)/(24*time.Hour)) > hostCenterMaxStatsDays { + return "", "", 0, 0, false + } + return startText, endText, start.UnixMilli(), endExclusive.UnixMilli(), true +} diff --git a/services/gateway-service/internal/transport/http/userapi/host_center_stats_handler_test.go b/services/gateway-service/internal/transport/http/userapi/host_center_stats_handler_test.go new file mode 100644 index 00000000..321643fb --- /dev/null +++ b/services/gateway-service/internal/transport/http/userapi/host_center_stats_handler_test.go @@ -0,0 +1,30 @@ +package userapi + +import ( + "net/http/httptest" + "testing" +) + +func TestParseHostCenterStatsRangeUsesInclusiveNaturalDays(t *testing.T) { + request := httptest.NewRequest("GET", "/api/v1/host-center/stats?start_date=2026-07-06&end_date=2026-07-12", nil) + startDate, endDate, startAtMS, endAtMS, ok := parseHostCenterStatsRange(request) + if !ok || startDate != "2026-07-06" || endDate != "2026-07-12" { + t.Fatalf("range parse mismatch: ok=%t start=%s end=%s", ok, startDate, endDate) + } + if got := endAtMS - startAtMS; got != 7*24*60*60*1000 { + t.Fatalf("end must be exclusive after seven inclusive days, got %d", got) + } +} + +func TestParseHostCenterStatsRangeRejectsInvalidOrOversizedRanges(t *testing.T) { + for _, rawURL := range []string{ + "/api/v1/host-center/stats?start_date=2026-07-12&end_date=2026-07-01", + "/api/v1/host-center/stats?start_date=2026-07-12", + "/api/v1/host-center/stats?start_date=2025-01-01&end_date=2026-07-12", + } { + request := httptest.NewRequest("GET", rawURL, nil) + if _, _, _, _, ok := parseHostCenterStatsRange(request); ok { + t.Fatalf("expected invalid range for %s", rawURL) + } + } +} diff --git a/services/gateway-service/internal/transport/http/vip_handler_test.go b/services/gateway-service/internal/transport/http/vip_handler_test.go new file mode 100644 index 00000000..06ed461b --- /dev/null +++ b/services/gateway-service/internal/transport/http/vip_handler_test.go @@ -0,0 +1,674 @@ +package http + +import ( + "bytes" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" + + userv1 "hyapp.local/api/proto/user/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/services/gateway-service/internal/auth" + "hyapp/services/gateway-service/internal/transport/http/httpkit" +) + +func newVIPTestRouter(walletClient *fakeWalletClient) http.Handler { + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + handler.SetWalletClient(walletClient) + return handler.Routes(auth.NewVerifier("secret")) +} + +func TestTriggerVIPOnlineNoticeChecksBenefitAndPublishesTrustedGlobalPayload(t *testing.T) { + walletClient := &fakeWalletClient{ + benefitCheckResp: &walletv1.CheckVipBenefitResponse{ + Allowed: true, + Benefit: &walletv1.VipBenefit{ + BenefitCode: "online_global_notice", + MetadataJson: `{"message":"欢迎进入Fami,祝你有美好的一天"}`, + }, + State: &walletv1.VipState{EffectiveVip: &walletv1.UserVip{ + UserId: 42, Level: 9, Name: "VIP9", Active: true, ProgramType: "tiered_privilege_v1", + }}, + EvaluatedAtMs: 1_800_000_000_000, + }, + batchEquippedResp: &walletv1.BatchGetUserEquippedResourcesResponse{Users: []*walletv1.UserEquippedResources{ + {UserId: 99, Resources: []*walletv1.UserResourceEntitlement{{Resource: &walletv1.Resource{ + ResourceId: 9000, ResourceCode: "other_user_frame", ResourceType: "avatar_frame", AssetUrl: "https://cdn.example/wrong-user.png", + }}}}, + {UserId: 42, Resources: []*walletv1.UserResourceEntitlement{ + {Resource: &walletv1.Resource{ResourceId: 7000, ResourceCode: "not_a_frame", ResourceType: "vehicle", AssetUrl: "https://cdn.example/car.svga"}}, + {Resource: &walletv1.Resource{ + ResourceId: 7001, ResourceCode: "vip9_gold_frame", ResourceType: "avatar_frame", + AssetUrl: "https://cdn.example/frame.svga", PreviewUrl: "https://cdn.example/frame.png", AnimationUrl: "https://cdn.example/frame.json", + }}, + }}, + }}, + } + profileClient := &fakeUserProfileClient{usersByID: map[int64]*userv1.User{42: { + UserId: 42, + Username: "I am a Rich", + Avatar: "https://cdn.example/vip9.png", + DisplayUserId: "100042", + PrettyDisplayUserId: "251145", + }}} + broadcastClient := &fakeBroadcastClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient) + handler.SetWalletClient(walletClient) + handler.SetBroadcastClient(broadcastClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/vip/online-notice", bytes.NewReader([]byte(`{"command_id":"process-launch-1"}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "fami")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if req := walletClient.lastBenefitCheck; req == nil || req.GetAppCode() != "fami" || req.GetUserId() != 42 || req.GetBenefitCode() != "online_global_notice" || req.GetRequestId() != recorder.Header().Get("X-Request-ID") { + t.Fatalf("CheckVipBenefit request mismatch: %+v", req) + } + if req := walletClient.lastBatchEquipped; req == nil || req.GetAppCode() != "fami" || req.GetRequestId() != recorder.Header().Get("X-Request-ID") || len(req.GetUserIds()) != 1 || req.GetUserIds()[0] != 42 || len(req.GetResourceTypes()) != 1 || req.GetResourceTypes()[0] != "avatar_frame" { + t.Fatalf("BatchGetUserEquippedResources request mismatch: %+v", req) + } + if req := profileClient.lastGet; req == nil || req.GetUserId() != 42 || req.GetMeta().GetAppCode() != "fami" { + t.Fatalf("GetUser request mismatch: %+v", req) + } + publish := broadcastClient.lastGlobal + if publish == nil || publish.GetMeta().GetAppCode() != "fami" || publish.GetBroadcastType() != "vip_online_notice" || !strings.HasPrefix(publish.GetEventId(), "vip_online_notice:42:") { + t.Fatalf("PublishGlobalBroadcast request mismatch: %+v", publish) + } + var payload map[string]any + if err := json.Unmarshal([]byte(publish.GetPayloadJson()), &payload); err != nil { + t.Fatalf("decode broadcast payload: %v", err) + } + if payload["user_id"] != float64(42) || payload["nickname"] != "I am a Rich" || payload["avatar"] != "https://cdn.example/vip9.png" || payload["display_user_id"] != "251145" || payload["vip_level"] != float64(9) || payload["vip_name"] != "VIP9" || payload["message"] != "欢迎进入Fami,祝你有美好的一天" { + t.Fatalf("broadcast payload mismatch: %+v", payload) + } + if payload["avatar_frame_url"] != "https://cdn.example/frame.svga" || payload["avatar_frame_resource_id"] != float64(7001) || payload["avatar_frame_code"] != "vip9_gold_frame" || payload["avatar_frame_asset_url"] != "https://cdn.example/frame.svga" || payload["avatar_frame_preview_url"] != "https://cdn.example/frame.png" || payload["avatar_frame_animation_url"] != "https://cdn.example/frame.json" { + t.Fatalf("avatar frame payload mismatch: %+v", payload) + } + if _, leaked := payload["command_id"]; leaked || strings.Contains(publish.GetPayloadJson(), "process-launch-1") { + t.Fatalf("client command must not be broadcast: %s", publish.GetPayloadJson()) + } + var envelope struct { + Code string `json:"code"` + Data struct { + EventID string `json:"event_id"` + GroupID string `json:"group_id"` + Status string `json:"status"` + Created bool `json:"created"` + EvaluatedAtMS int64 `json:"evaluated_at_ms"` + } `json:"data"` + } + if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { + t.Fatalf("decode response failed: %v", err) + } + if envelope.Code != httpkit.CodeOK || envelope.Data.EventID != publish.GetEventId() || envelope.Data.GroupID != "hy_fami_bc_g_v2" || envelope.Data.Status != "pending" || !envelope.Data.Created || envelope.Data.EvaluatedAtMS != 1_800_000_000_000 { + t.Fatalf("response mismatch: %+v", envelope) + } +} + +func TestTriggerVIPOnlineNoticeWithoutEquippedFramePublishesExplicitEmptyFields(t *testing.T) { + walletClient := &fakeWalletClient{ + benefitCheckResp: &walletv1.CheckVipBenefitResponse{ + Allowed: true, + Benefit: &walletv1.VipBenefit{BenefitCode: "online_global_notice"}, + State: &walletv1.VipState{EffectiveVip: &walletv1.UserVip{UserId: 42, Level: 9, Name: "VIP9", Active: true}}, + }, + batchEquippedResp: &walletv1.BatchGetUserEquippedResourcesResponse{}, + } + profileClient := &fakeUserProfileClient{usersByID: map[int64]*userv1.User{42: {UserId: 42, Username: "No Frame"}}} + broadcastClient := &fakeBroadcastClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient) + handler.SetWalletClient(walletClient) + handler.SetBroadcastClient(broadcastClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/vip/online-notice", bytes.NewReader([]byte(`{"command_id":"process-launch-no-frame"}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "fami")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK || broadcastClient.lastGlobal == nil { + t.Fatalf("no-frame request must still broadcast: status=%d body=%s", recorder.Code, recorder.Body.String()) + } + var payload map[string]any + if err := json.Unmarshal([]byte(broadcastClient.lastGlobal.GetPayloadJson()), &payload); err != nil { + t.Fatalf("decode broadcast payload: %v", err) + } + if payload["avatar_frame_resource_id"] != float64(0) || payload["avatar_frame_url"] != "" || payload["avatar_frame_code"] != "" || payload["avatar_frame_asset_url"] != "" || payload["avatar_frame_preview_url"] != "" || payload["avatar_frame_animation_url"] != "" { + t.Fatalf("empty avatar frame fields mismatch: %+v", payload) + } +} + +func TestTriggerVIPOnlineNoticeEquippedResourceFailureDoesNotBroadcast(t *testing.T) { + walletClient := &fakeWalletClient{ + benefitCheckResp: &walletv1.CheckVipBenefitResponse{Allowed: true}, + err: errors.New("wallet equipped resources unavailable"), + } + profileClient := &fakeUserProfileClient{} + broadcastClient := &fakeBroadcastClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient) + handler.SetWalletClient(walletClient) + handler.SetBroadcastClient(broadcastClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/vip/online-notice", bytes.NewReader([]byte(`{"command_id":"process-launch-frame-rpc-error"}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "fami")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusBadGateway || walletClient.lastBatchEquipped == nil || profileClient.lastGet != nil || broadcastClient.lastGlobal != nil { + t.Fatalf("equipped-resource failure must stop before profile/broadcast: status=%d equipped=%+v profile=%+v broadcast=%+v body=%s", recorder.Code, walletClient.lastBatchEquipped, profileClient.lastGet, broadcastClient.lastGlobal, recorder.Body.String()) + } +} + +func TestTriggerVIPOnlineNoticeDenialDoesNotReadProfileOrBroadcast(t *testing.T) { + walletClient := &fakeWalletClient{benefitCheckResp: &walletv1.CheckVipBenefitResponse{Allowed: false, DenialReason: "benefit_not_enabled"}} + profileClient := &fakeUserProfileClient{} + broadcastClient := &fakeBroadcastClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient) + handler.SetWalletClient(walletClient) + handler.SetBroadcastClient(broadcastClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/vip/online-notice", bytes.NewReader([]byte(`{"command_id":"process-launch-denied"}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "fami")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusForbidden || profileClient.lastGet != nil || broadcastClient.lastGlobal != nil { + t.Fatalf("denied request leaked into downstream calls: status=%d profile=%+v broadcast=%+v body=%s", recorder.Code, profileClient.lastGet, broadcastClient.lastGlobal, recorder.Body.String()) + } +} + +func TestTriggerVIPOnlineNoticeUserSettingDenialDoesNotReadProfileOrBroadcast(t *testing.T) { + walletClient := &fakeWalletClient{benefitCheckResp: &walletv1.CheckVipBenefitResponse{ + Allowed: false, DenialReason: "user_setting_disabled", + }} + profileClient := &fakeUserProfileClient{} + broadcastClient := &fakeBroadcastClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient) + handler.SetWalletClient(walletClient) + handler.SetBroadcastClient(broadcastClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/vip/online-notice", bytes.NewReader([]byte(`{"command_id":"setting-disabled"}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "fami")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusForbidden || profileClient.lastGet != nil || broadcastClient.lastGlobal != nil { + t.Fatalf("user setting denial leaked into downstream calls: status=%d profile=%+v broadcast=%+v body=%s", recorder.Code, profileClient.lastGet, broadcastClient.lastGlobal, recorder.Body.String()) + } +} + +func TestVIPSettingsHTTPContractPreservesExplicitFalseAndAuthScope(t *testing.T) { + walletClient := &fakeWalletClient{ + myVipResp: &walletv1.GetMyVipResponse{State: &walletv1.VipState{ + EvaluatedAtMs: 1_800_000_000_000, + UserSettings: &walletv1.VipUserSettings{ + AppCode: "fami", UserId: 42, RoomEntryNoticeEnabled: true, + OnlineGlobalNoticeEnabled: false, UpdatedAtMs: 1_799_999_999_000, + }, + }}, + updateVIPSettingsResp: &walletv1.UpdateMyVipSettingsResponse{ + Settings: &walletv1.VipUserSettings{ + AppCode: "fami", UserId: 42, RoomEntryNoticeEnabled: false, + OnlineGlobalNoticeEnabled: false, UpdatedAtMs: 1_800_000_000_100, + }, + ServerTimeMs: 1_800_000_000_200, + }, + } + router := newVIPTestRouter(walletClient) + token := "Bearer " + signGatewayTokenWithAppCode(t, "secret", 42, "fami") + + getRequest := httptest.NewRequest(http.MethodGet, "/api/v1/vip/settings", nil) + getRequest.Header.Set("Authorization", token) + getRecorder := httptest.NewRecorder() + router.ServeHTTP(getRecorder, getRequest) + if getRecorder.Code != http.StatusOK { + t.Fatalf("GET settings status mismatch: got=%d body=%s", getRecorder.Code, getRecorder.Body.String()) + } + if req := walletClient.lastMyVip; req == nil || req.GetAppCode() != "fami" || req.GetUserId() != 42 || req.GetRequestId() != getRecorder.Header().Get("X-Request-ID") { + t.Fatalf("GET settings must use authenticated scope: %+v", req) + } + var getEnvelope struct { + Data struct { + Settings struct { + AppCode string `json:"app_code"` + UserID int64 `json:"user_id"` + RoomEntryNoticeEnabled bool `json:"room_entry_notice_enabled"` + OnlineGlobalNoticeEnabled bool `json:"online_global_notice_enabled"` + } `json:"settings"` + EvaluatedAtMS int64 `json:"evaluated_at_ms"` + } `json:"data"` + } + if err := json.NewDecoder(getRecorder.Body).Decode(&getEnvelope); err != nil { + t.Fatalf("decode GET settings response failed: %v", err) + } + if getEnvelope.Data.Settings.AppCode != "fami" || getEnvelope.Data.Settings.UserID != 42 || !getEnvelope.Data.Settings.RoomEntryNoticeEnabled || getEnvelope.Data.Settings.OnlineGlobalNoticeEnabled || getEnvelope.Data.EvaluatedAtMS != 1_800_000_000_000 { + t.Fatalf("GET settings response mismatch: %+v", getEnvelope.Data) + } + + patchRequest := httptest.NewRequest(http.MethodPatch, "/api/v1/vip/settings", bytes.NewReader([]byte(`{"room_entry_notice_enabled":false}`))) + patchRequest.Header.Set("Authorization", token) + patchRecorder := httptest.NewRecorder() + router.ServeHTTP(patchRecorder, patchRequest) + if patchRecorder.Code != http.StatusOK { + t.Fatalf("PATCH settings status mismatch: got=%d body=%s", patchRecorder.Code, patchRecorder.Body.String()) + } + patch := walletClient.lastUpdateVIPSettings + if patch == nil || patch.GetAppCode() != "fami" || patch.GetUserId() != 42 || patch.RoomEntryNoticeEnabled == nil || patch.GetRoomEntryNoticeEnabled() || patch.OnlineGlobalNoticeEnabled != nil || patch.GetRequestId() != patchRecorder.Header().Get("X-Request-ID") { + t.Fatalf("PATCH settings lost explicit false or auth scope: %+v", patch) + } + + // /vip/me 复用同一 DTO 转换,必须把 state.user_settings 一并透出。 + meRequest := httptest.NewRequest(http.MethodGet, "/api/v1/vip/me", nil) + meRequest.Header.Set("Authorization", token) + meRecorder := httptest.NewRecorder() + router.ServeHTTP(meRecorder, meRequest) + if meRecorder.Code != http.StatusOK || !strings.Contains(meRecorder.Body.String(), `"user_settings"`) || !strings.Contains(meRecorder.Body.String(), `"online_global_notice_enabled":false`) { + t.Fatalf("/vip/me missing user settings: status=%d body=%s", meRecorder.Code, meRecorder.Body.String()) + } +} + +func TestVIPSettingsEmptyPatchRejectedBeforeWallet(t *testing.T) { + walletClient := &fakeWalletClient{} + router := newVIPTestRouter(walletClient) + request := httptest.NewRequest(http.MethodPatch, "/api/v1/vip/settings", bytes.NewReader([]byte(`{}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "fami")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusBadRequest || walletClient.lastUpdateVIPSettings != nil { + t.Fatalf("empty settings PATCH must fail before wallet: status=%d req=%+v body=%s", recorder.Code, walletClient.lastUpdateVIPSettings, recorder.Body.String()) + } +} + +func TestTriggerVIPOnlineNoticeValidatesCommandID(t *testing.T) { + walletClient := &fakeWalletClient{} + profileClient := &fakeUserProfileClient{} + broadcastClient := &fakeBroadcastClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient) + handler.SetWalletClient(walletClient) + handler.SetBroadcastClient(broadcastClient) + router := handler.Routes(auth.NewVerifier("secret")) + for name, body := range map[string]string{ + "empty": `{"command_id":""}`, + "too_long": `{"command_id":"` + strings.Repeat("x", 129) + `"}`, + } { + t.Run(name, func(t *testing.T) { + request := httptest.NewRequest(http.MethodPost, "/api/v1/vip/online-notice", bytes.NewReader([]byte(body))) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "fami")) + recorder := httptest.NewRecorder() + router.ServeHTTP(recorder, request) + if recorder.Code != http.StatusBadRequest { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + }) + } + if walletClient.lastBenefitCheck != nil || profileClient.lastGet != nil || broadcastClient.lastGlobal != nil { + t.Fatal("invalid command must be rejected before downstream calls") + } +} + +func TestVIPCoinRebateHTTPContractUsesAuthenticatedUser(t *testing.T) { + rebate := &walletv1.VipDailyCoinRebate{ + RebateId: "vip_rebate_01", UserId: 42, TaskDay: "2027-01-15", VipLevel: 9, VipName: "VIP9", + CoinAmount: 100000, Status: "claimable", AvailableAtMs: 1800000000000, ExpiresAtMs: 1800086400000, + ConfigVersion: 7, CreatedAtMs: 1800000000100, UpdatedAtMs: 1800000000100, + } + walletClient := &fakeWalletClient{ + currentVIPRebateResp: &walletv1.GetMyCurrentVipDailyCoinRebateResponse{Found: true, Rebate: rebate, ServerTimeMs: 1800000000200}, + vipRebateStatusesResp: &walletv1.ListMyVipDailyCoinRebateStatusesResponse{Rebates: []*walletv1.VipDailyCoinRebate{rebate}, Total: 1, ServerTimeMs: 1800000000300}, + claimVIPRebateResp: &walletv1.ClaimVipDailyCoinRebateResponse{ + Rebate: &walletv1.VipDailyCoinRebate{ + RebateId: rebate.GetRebateId(), UserId: 42, TaskDay: rebate.GetTaskDay(), VipLevel: 9, VipName: "VIP9", + CoinAmount: 100000, Status: "claimed", AvailableAtMs: rebate.GetAvailableAtMs(), ExpiresAtMs: rebate.GetExpiresAtMs(), + ConfigVersion: 7, ClaimedAtMs: 1800000000400, WalletTransactionId: "wtx-rebate-1", + }, + TransactionId: "wtx-rebate-1", + CoinBalance: &walletv1.AssetBalance{AssetType: "COIN", AvailableAmount: 500000, Version: 3}, + ServerTimeMs: 1800000000400, + }, + } + router := newVIPTestRouter(walletClient) + token := "Bearer " + signGatewayTokenWithAppCode(t, "secret", 42, "fami") + + currentRequest := httptest.NewRequest(http.MethodGet, "/api/v1/vip/coin-rebates/current", nil) + currentRequest.Header.Set("Authorization", token) + currentRecorder := httptest.NewRecorder() + router.ServeHTTP(currentRecorder, currentRequest) + if currentRecorder.Code != http.StatusOK { + t.Fatalf("current status mismatch: got %d body=%s", currentRecorder.Code, currentRecorder.Body.String()) + } + if req := walletClient.lastCurrentVIPRebate; req == nil || req.GetAppCode() != "fami" || req.GetUserId() != 42 || req.GetRequestId() != currentRecorder.Header().Get("X-Request-ID") { + t.Fatalf("current rebate request mismatch: %+v", req) + } + var currentEnvelope struct { + Data struct { + Found bool `json:"found"` + Rebate struct { + RebateID string `json:"rebate_id"` + UserID string `json:"user_id"` + CoinAmount int64 `json:"coin_amount"` + Status string `json:"status"` + } `json:"rebate"` + } `json:"data"` + } + if err := json.NewDecoder(currentRecorder.Body).Decode(¤tEnvelope); err != nil || !currentEnvelope.Data.Found || currentEnvelope.Data.Rebate.RebateID != rebate.GetRebateId() || currentEnvelope.Data.Rebate.UserID != "42" || currentEnvelope.Data.Rebate.CoinAmount != 100000 || currentEnvelope.Data.Rebate.Status != "claimable" { + t.Fatalf("current rebate response mismatch: envelope=%+v err=%v", currentEnvelope, err) + } + + statusesRequest := httptest.NewRequest(http.MethodPost, "/api/v1/vip/coin-rebates/statuses", bytes.NewReader([]byte(`{"rebate_ids":["vip_rebate_01"]}`))) + statusesRequest.Header.Set("Authorization", token) + statusesRecorder := httptest.NewRecorder() + router.ServeHTTP(statusesRecorder, statusesRequest) + if statusesRecorder.Code != http.StatusOK { + t.Fatalf("statuses mismatch: got %d body=%s", statusesRecorder.Code, statusesRecorder.Body.String()) + } + if req := walletClient.lastVIPRebateStatuses; req == nil || req.GetAppCode() != "fami" || req.GetUserId() != 42 || len(req.GetRebateIds()) != 1 || req.GetRebateIds()[0] != "vip_rebate_01" { + t.Fatalf("rebate statuses request mismatch: %+v", req) + } + + claimRequest := httptest.NewRequest(http.MethodPost, "/api/v1/vip/coin-rebates/vip_rebate_01/claim", bytes.NewReader([]byte(`{"command_id":"claim-rebate-01"}`))) + claimRequest.Header.Set("Authorization", token) + claimRecorder := httptest.NewRecorder() + router.ServeHTTP(claimRecorder, claimRequest) + if claimRecorder.Code != http.StatusOK { + t.Fatalf("claim mismatch: got %d body=%s", claimRecorder.Code, claimRecorder.Body.String()) + } + if req := walletClient.lastClaimVIPRebate; req == nil || req.GetAppCode() != "fami" || req.GetUserId() != 42 || req.GetRebateId() != "vip_rebate_01" || req.GetCommandId() != "claim-rebate-01" { + t.Fatalf("claim rebate request mismatch: %+v", req) + } + var claimEnvelope struct { + Data struct { + TransactionID string `json:"transaction_id"` + Rebate struct { + Status string `json:"status"` + } `json:"rebate"` + CoinBalance struct { + AvailableAmount int64 `json:"available_amount"` + } `json:"coin_balance"` + } `json:"data"` + } + if err := json.NewDecoder(claimRecorder.Body).Decode(&claimEnvelope); err != nil || claimEnvelope.Data.TransactionID != "wtx-rebate-1" || claimEnvelope.Data.Rebate.Status != "claimed" || claimEnvelope.Data.CoinBalance.AvailableAmount != 500000 { + t.Fatalf("claim response mismatch: envelope=%+v err=%v", claimEnvelope, err) + } +} + +func TestVIPCoinRebateClaimRejectsClientControlledInvalidKeys(t *testing.T) { + walletClient := &fakeWalletClient{} + router := newVIPTestRouter(walletClient) + request := httptest.NewRequest(http.MethodPost, "/api/v1/vip/coin-rebates/rebate-1/claim", bytes.NewReader([]byte(`{"command_id":""}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "fami")) + recorder := httptest.NewRecorder() + router.ServeHTTP(recorder, request) + if recorder.Code != http.StatusBadRequest || walletClient.lastClaimVIPRebate != nil { + t.Fatalf("invalid claim must stop before wallet: status=%d req=%+v body=%s", recorder.Code, walletClient.lastClaimVIPRebate, recorder.Body.String()) + } +} + +func TestGetMyVIPKeepsLegacyFieldsAndAddsEffectiveState(t *testing.T) { + config := &walletv1.VipProgramConfig{ + AppCode: "fami", + ProgramType: "tiered_privilege_v1", + LevelCount: 9, + SameLevelExpiryPolicy: "extend_remaining", + UpgradeExpiryPolicy: "replace_from_now", + DowngradePurchasePolicy: "reject", + BenefitInheritancePolicy: "target_only", + GrantMode: "trial_card", + TrialCardEnabled: true, + Status: "active", + ConfigVersion: 3, + } + state := &walletv1.VipState{ + PaidVip: &walletv1.UserVip{UserId: 42, Level: 3, Name: "VIP3", Active: true, ExpiresAtMs: 2_000, ProgramType: config.GetProgramType(), ConfigVersion: 3}, + EquippedTrialCard: &walletv1.VipTrialCard{ + TrialCardId: "card-4", EntitlementId: "ent-4", UserId: 42, Level: 4, Name: "VIP4体验卡", Equipped: true, ExpiresAtMs: 3_000, + }, + EffectiveVip: &walletv1.UserVip{UserId: 42, Level: 4, Name: "VIP4", Active: true, ExpiresAtMs: 3_000, ProgramType: config.GetProgramType(), ConfigVersion: 3}, + EffectiveSource: "trial", + EffectiveBenefits: []*walletv1.VipBenefit{{BenefitCode: "room_entry_notice", Name: "进房通知", TrialEnabled: true, Status: "active"}}, + EvaluatedAtMs: 1_500, + ProgramConfig: config, + } + walletClient := &fakeWalletClient{myVipResp: &walletv1.GetMyVipResponse{Vip: state.GetEffectiveVip(), State: state}} + router := newVIPTestRouter(walletClient) + request := httptest.NewRequest(http.MethodGet, "/api/v1/vip/me", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "fami")) + request.Header.Set("X-Request-ID", "req-vip-me") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + generatedRequestID := recorder.Header().Get("X-Request-ID") + if req := walletClient.lastMyVip; req == nil || req.GetAppCode() != "fami" || req.GetUserId() != 42 || req.GetRequestId() != generatedRequestID { + t.Fatalf("GetMyVip request mismatch: %+v", req) + } + var envelope struct { + Code string `json:"code"` + Data struct { + Level int32 `json:"level"` + ConfigVersion int64 `json:"config_version"` + ProgramConfig struct { + ProgramType string `json:"program_type"` + TrialCardEnabled bool `json:"trial_card_enabled"` + } `json:"program_config"` + State struct { + EffectiveSource string `json:"effective_source"` + PaidVIP struct { + Level int32 `json:"level"` + } `json:"paid_vip"` + EffectiveVIP struct { + Level int32 `json:"level"` + } `json:"effective_vip"` + EquippedTrialCard struct { + EntitlementID string `json:"entitlement_id"` + } `json:"equipped_trial_card"` + EffectiveBenefits []struct { + BenefitCode string `json:"benefit_code"` + } `json:"effective_benefits"` + } `json:"state"` + } `json:"data"` + } + if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { + t.Fatalf("decode response failed: %v", err) + } + if envelope.Code != httpkit.CodeOK || envelope.Data.Level != 4 || envelope.Data.ConfigVersion != 3 { + t.Fatalf("legacy VIP fields mismatch: %+v", envelope.Data) + } + if envelope.Data.ProgramConfig.ProgramType != "tiered_privilege_v1" || !envelope.Data.ProgramConfig.TrialCardEnabled { + t.Fatalf("program config mismatch: %+v", envelope.Data.ProgramConfig) + } + if envelope.Data.State.PaidVIP.Level != 3 || envelope.Data.State.EffectiveVIP.Level != 4 || envelope.Data.State.EffectiveSource != "trial" || envelope.Data.State.EquippedTrialCard.EntitlementID != "ent-4" { + t.Fatalf("effective state mismatch: %+v", envelope.Data.State) + } + if len(envelope.Data.State.EffectiveBenefits) != 1 || envelope.Data.State.EffectiveBenefits[0].BenefitCode != "room_entry_notice" { + t.Fatalf("effective benefits mismatch: %+v", envelope.Data.State.EffectiveBenefits) + } +} + +func TestListVIPPackagesAddsProgramStateAndLevelBenefits(t *testing.T) { + config := &walletv1.VipProgramConfig{AppCode: "fami", ProgramType: "tiered_privilege_v1", LevelCount: 9, ConfigVersion: 7, TrialCardEnabled: true} + walletClient := &fakeWalletClient{vipPackagesResp: &walletv1.ListVipPackagesResponse{ + CurrentVip: &walletv1.UserVip{UserId: 42, Level: 2, Active: true}, + Packages: []*walletv1.VipLevel{{ + Level: 3, Name: "VIP3", PriceCoin: 300, DurationMs: 2_592_000_000, CanPurchase: true, ConfigVersion: 7, + Benefits: []*walletv1.VipBenefit{{BenefitCode: "voice_wave", Name: "语音波纹", ExecutionScope: "room", TrialEnabled: true}}, + }}, + State: &walletv1.VipState{EffectiveVip: &walletv1.UserVip{UserId: 42, Level: 2, Active: true}, EffectiveSource: "paid", ProgramConfig: config}, + ProgramConfig: config, + }} + router := newVIPTestRouter(walletClient) + request := httptest.NewRequest(http.MethodGet, "/api/v1/vip/packages", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "fami")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if req := walletClient.lastVipPackages; req == nil || req.GetAppCode() != "fami" || req.GetUserId() != 42 || req.GetRequestId() != recorder.Header().Get("X-Request-ID") { + t.Fatalf("ListVipPackages request mismatch: %+v", req) + } + var envelope struct { + Data struct { + CurrentVIP struct { + Level int32 `json:"level"` + } `json:"current_vip"` + ProgramConfig struct { + LevelCount int32 `json:"level_count"` + } `json:"program_config"` + State struct { + EffectiveSource string `json:"effective_source"` + } `json:"state"` + Packages []struct { + ConfigVersion int64 `json:"config_version"` + Benefits []struct { + BenefitCode string `json:"benefit_code"` + } `json:"benefits"` + } `json:"packages"` + } `json:"data"` + } + if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { + t.Fatalf("decode response failed: %v", err) + } + if envelope.Data.CurrentVIP.Level != 2 || envelope.Data.ProgramConfig.LevelCount != 9 || envelope.Data.State.EffectiveSource != "paid" || len(envelope.Data.Packages) != 1 { + t.Fatalf("packages response mismatch: %+v", envelope.Data) + } + if envelope.Data.Packages[0].ConfigVersion != 7 || len(envelope.Data.Packages[0].Benefits) != 1 || envelope.Data.Packages[0].Benefits[0].BenefitCode != "voice_wave" { + t.Fatalf("package benefits mismatch: %+v", envelope.Data.Packages) + } +} + +func TestPurchaseVIPKeepsLegacyResultAndAddsState(t *testing.T) { + config := &walletv1.VipProgramConfig{AppCode: "fami", ProgramType: "tiered_privilege_v1", UpgradeExpiryPolicy: "replace_from_now", ConfigVersion: 8} + walletClient := &fakeWalletClient{purchaseVipResp: &walletv1.PurchaseVipResponse{ + OrderId: "vip-order-1", TransactionId: "vip-tx-1", Vip: &walletv1.UserVip{UserId: 42, Level: 4, Active: true, ExpiresAtMs: 4_000}, CoinSpent: 400, CoinBalanceAfter: 600, + State: &walletv1.VipState{PaidVip: &walletv1.UserVip{UserId: 42, Level: 4, Active: true}, EffectiveVip: &walletv1.UserVip{UserId: 42, Level: 4, Active: true}, EffectiveSource: "paid", ProgramConfig: config}, + }} + router := newVIPTestRouter(walletClient) + request := httptest.NewRequest(http.MethodPost, "/api/v1/vip/purchase", bytes.NewReader([]byte(`{"command_id":"vip-buy-4","level":4}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "fami")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if req := walletClient.lastPurchaseVip; req == nil || req.GetCommandId() != "vip-buy-4" || req.GetAppCode() != "fami" || req.GetUserId() != 42 || req.GetLevel() != 4 { + t.Fatalf("PurchaseVip request mismatch: %+v", req) + } + var envelope struct { + Data struct { + OrderID string `json:"order_id"` + TransactionID string `json:"transaction_id"` + VIP struct { + Level int32 `json:"level"` + } `json:"vip"` + ProgramConfig struct { + UpgradeExpiryPolicy string `json:"upgrade_expiry_policy"` + } `json:"program_config"` + State struct { + EffectiveSource string `json:"effective_source"` + } `json:"state"` + } `json:"data"` + } + if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { + t.Fatalf("decode response failed: %v", err) + } + if envelope.Data.OrderID != "vip-order-1" || envelope.Data.TransactionID != "vip-tx-1" || envelope.Data.VIP.Level != 4 || envelope.Data.ProgramConfig.UpgradeExpiryPolicy != "replace_from_now" || envelope.Data.State.EffectiveSource != "paid" { + t.Fatalf("purchase response mismatch: %+v", envelope.Data) + } +} + +func TestEquipVIPTrialCardUsesAuthenticatedEntitlement(t *testing.T) { + config := &walletv1.VipProgramConfig{AppCode: "fami", ProgramType: "tiered_privilege_v1", TrialCardEnabled: true} + walletClient := &fakeWalletClient{trialEquipResp: &walletv1.EquipVipTrialCardResponse{ + TrialCard: &walletv1.VipTrialCard{TrialCardId: "card-9", EntitlementId: "ent-card-9", UserId: 42, Level: 9, Equipped: true, ExpiresAtMs: 9_000}, + State: &walletv1.VipState{EquippedTrialCard: &walletv1.VipTrialCard{EntitlementId: "ent-card-9", Level: 9, Equipped: true}, EffectiveVip: &walletv1.UserVip{UserId: 42, Level: 9, Active: true}, EffectiveSource: "trial", ProgramConfig: config}, + ServerTimeMs: 1_800, + }} + router := newVIPTestRouter(walletClient) + request := httptest.NewRequest(http.MethodPost, "/api/v1/vip/trial-cards/ent-card-9/equip", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "fami")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if req := walletClient.lastTrialEquip; req == nil || req.GetRequestId() != recorder.Header().Get("X-Request-ID") || req.GetAppCode() != "fami" || req.GetUserId() != 42 || req.GetEntitlementId() != "ent-card-9" { + t.Fatalf("EquipVipTrialCard request mismatch: %+v", req) + } + var envelope struct { + Data struct { + TrialCard struct { + EntitlementID string `json:"entitlement_id"` + } `json:"trial_card"` + State struct { + EffectiveSource string `json:"effective_source"` + } `json:"state"` + ServerTimeMS int64 `json:"server_time_ms"` + } `json:"data"` + } + if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { + t.Fatalf("decode response failed: %v", err) + } + if envelope.Data.TrialCard.EntitlementID != "ent-card-9" || envelope.Data.State.EffectiveSource != "trial" || envelope.Data.ServerTimeMS != 1_800 { + t.Fatalf("equip response mismatch: %+v", envelope.Data) + } +} + +func TestUnequipVIPTrialCardRestoresPaidState(t *testing.T) { + config := &walletv1.VipProgramConfig{AppCode: "fami", ProgramType: "tiered_privilege_v1", TrialCardEnabled: true} + walletClient := &fakeWalletClient{trialUnequipResp: &walletv1.UnequipVipTrialCardResponse{ + Unequipped: true, + State: &walletv1.VipState{PaidVip: &walletv1.UserVip{UserId: 42, Level: 3, Active: true}, EffectiveVip: &walletv1.UserVip{UserId: 42, Level: 3, Active: true}, EffectiveSource: "paid", ProgramConfig: config}, + ServerTimeMs: 1_900, + }} + router := newVIPTestRouter(walletClient) + request := httptest.NewRequest(http.MethodDelete, "/api/v1/vip/trial-cards/equipped", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "fami")) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if req := walletClient.lastTrialUnequip; req == nil || req.GetRequestId() != recorder.Header().Get("X-Request-ID") || req.GetAppCode() != "fami" || req.GetUserId() != 42 { + t.Fatalf("UnequipVipTrialCard request mismatch: %+v", req) + } + var envelope struct { + Data struct { + Unequipped bool `json:"unequipped"` + State struct { + EffectiveSource string `json:"effective_source"` + EffectiveVIP struct { + Level int32 `json:"level"` + } `json:"effective_vip"` + } `json:"state"` + } `json:"data"` + } + if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { + t.Fatalf("decode response failed: %v", err) + } + if !envelope.Data.Unequipped || envelope.Data.State.EffectiveSource != "paid" || envelope.Data.State.EffectiveVIP.Level != 3 { + t.Fatalf("unequip response mismatch: %+v", envelope.Data) + } +} diff --git a/services/gateway-service/internal/transport/http/walletapi/app_wallet_handler.go b/services/gateway-service/internal/transport/http/walletapi/app_wallet_handler.go index 54a43df8..12a595e1 100644 --- a/services/gateway-service/internal/transport/http/walletapi/app_wallet_handler.go +++ b/services/gateway-service/internal/transport/http/walletapi/app_wallet_handler.go @@ -1,6 +1,7 @@ package walletapi import ( + "encoding/json" "hyapp/services/gateway-service/internal/transport/http/httpkit" "net/http" "strings" @@ -137,6 +138,43 @@ type vipPurchaseRequestBody struct { Level int32 `json:"level"` } +// vipMeData 匿名嵌入旧 DTO 以保留 data.level/name/active 等历史字段,新增结构化 +// state 后客户端可以逐版本迁移,不会因一次协议升级失去当前会员展示。 +type vipMeData struct { + OverviewVIPData overviewVIPData `json:"-"` + ProgramConfig vipProgramConfigData `json:"program_config"` + State vipStateData `json:"state"` +} + +// MarshalJSON 明确把旧字段保持在 data 根层,避免嵌套成 overview_vip_data 而破坏 +// 已发布 Flutter 解析;新字段只做向后兼容的追加。 +func (data vipMeData) MarshalJSON() ([]byte, error) { + type wire struct { + Level int32 `json:"level"` + Name string `json:"name"` + Active bool `json:"active"` + StartedAtMS int64 `json:"started_at_ms"` + ExpiresAtMS int64 `json:"expires_at_ms"` + ProgramType string `json:"program_type,omitempty"` + ConfigVersion int64 `json:"config_version,omitempty"` + Unavailable bool `json:"unavailable,omitempty"` + ProgramConfig vipProgramConfigData `json:"program_config"` + State vipStateData `json:"state"` + } + return json.Marshal(wire{ + Level: data.OverviewVIPData.Level, + Name: data.OverviewVIPData.Name, + Active: data.OverviewVIPData.Active, + StartedAtMS: data.OverviewVIPData.StartedAtMS, + ExpiresAtMS: data.OverviewVIPData.ExpiresAtMS, + ProgramType: data.OverviewVIPData.ProgramType, + ConfigVersion: data.OverviewVIPData.ConfigVersion, + Unavailable: data.OverviewVIPData.Unavailable, + ProgramConfig: data.ProgramConfig, + State: data.State, + }) +} + type vipPackageData struct { Level int32 `json:"level"` Name string `json:"name"` @@ -151,6 +189,84 @@ type vipPackageData struct { RequiredRechargeCoinAmount int64 `json:"required_recharge_coin_amount"` UserRechargeCoinAmount int64 `json:"user_recharge_coin_amount"` PurchaseLockedReason string `json:"purchase_locked_reason"` + Benefits []vipBenefitData `json:"benefits"` + ConfigVersion int64 `json:"config_version"` +} + +// vipProgramConfigData 原样暴露 wallet-service 已判定的 App 级策略;客户端只据此展示, +// 购买有效期、降级限制和体验卡开关始终由 wallet-service 在事务内执行。 +type vipProgramConfigData struct { + AppCode string `json:"app_code"` + ProgramType string `json:"program_type"` + LevelCount int32 `json:"level_count"` + SameLevelExpiryPolicy string `json:"same_level_expiry_policy"` + UpgradeExpiryPolicy string `json:"upgrade_expiry_policy"` + DowngradePurchasePolicy string `json:"downgrade_purchase_policy"` + BenefitInheritancePolicy string `json:"benefit_inheritance_policy"` + GrantMode string `json:"grant_mode"` + TrialCardEnabled bool `json:"trial_card_enabled"` + Status string `json:"status"` + ConfigVersion int64 `json:"config_version"` + UpdatedByAdminID int64 `json:"updated_by_admin_id"` + CreatedAtMS int64 `json:"created_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` +} + +// vipBenefitData 是最终生效权益的资格描述。客户端不得根据 unlock_level 自行继承; +// 对可关闭通知还需结合 state.user_settings,真正执行前以 CheckVipBenefit 为准。 +type vipBenefitData struct { + BenefitCode string `json:"benefit_code"` + Name string `json:"name"` + BenefitType string `json:"benefit_type"` + UnlockLevel int32 `json:"unlock_level"` + Status string `json:"status"` + TrialEnabled bool `json:"trial_enabled"` + ResourceID int64 `json:"resource_id"` + ResourceType string `json:"resource_type"` + ExecutionScope string `json:"execution_scope"` + AutoEquip bool `json:"auto_equip"` + SortOrder int32 `json:"sort_order"` + MetadataJSON string `json:"metadata_json"` + CreatedAtMS int64 `json:"created_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` +} + +type vipTrialCardData struct { + TrialCardID string `json:"trial_card_id"` + EntitlementID string `json:"entitlement_id"` + ResourceID int64 `json:"resource_id"` + UserID int64 `json:"user_id"` + Level int32 `json:"level"` + Name string `json:"name"` + Status string `json:"status"` + Equipped bool `json:"equipped"` + DurationMS int64 `json:"duration_ms"` + EffectiveAtMS int64 `json:"effective_at_ms"` + ExpiresAtMS int64 `json:"expires_at_ms"` + RemainingDurationMS int64 `json:"remaining_duration_ms"` + GrantSource string `json:"grant_source"` + SourceGrantID string `json:"source_grant_id"` + CreatedAtMS int64 `json:"created_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` +} + +type vipStateData struct { + PaidVIP overviewVIPData `json:"paid_vip"` + EquippedTrialCard *vipTrialCardData `json:"equipped_trial_card"` + EffectiveVIP overviewVIPData `json:"effective_vip"` + EffectiveSource string `json:"effective_source"` + EffectiveBenefits []vipBenefitData `json:"effective_benefits"` + EvaluatedAtMS int64 `json:"evaluated_at_ms"` + ProgramConfig vipProgramConfigData `json:"program_config"` + UserSettings vipUserSettingsData `json:"user_settings"` +} + +type vipUserSettingsData struct { + AppCode string `json:"app_code"` + UserID int64 `json:"user_id"` + RoomEntryNoticeEnabled bool `json:"room_entry_notice_enabled"` + OnlineGlobalNoticeEnabled bool `json:"online_global_notice_enabled"` + UpdatedAtMS int64 `json:"updated_at_ms"` } type vipRewardItemData struct { diff --git a/services/gateway-service/internal/transport/http/walletapi/handler.go b/services/gateway-service/internal/transport/http/walletapi/handler.go index d2656228..bc8194e1 100644 --- a/services/gateway-service/internal/transport/http/walletapi/handler.go +++ b/services/gateway-service/internal/transport/http/walletapi/handler.go @@ -20,6 +20,7 @@ type Handler struct { userIdentityClient client.UserIdentityClient userProfileClient client.UserProfileClient userHostClient client.UserHostClient + broadcastClient client.BroadcastClient imAccountImporter TencentIMAccountImporter imPublisher TencentIMUserMessagePublisher imGroupIDPrefix string @@ -50,6 +51,7 @@ type Config struct { UserIdentityClient client.UserIdentityClient UserProfileClient client.UserProfileClient UserHostClient client.UserHostClient + BroadcastClient client.BroadcastClient IMAccountImporter TencentIMAccountImporter IMPublisher TencentIMUserMessagePublisher IMGroupIDPrefix string @@ -66,6 +68,7 @@ func New(config Config) *Handler { userIdentityClient: config.UserIdentityClient, userProfileClient: config.UserProfileClient, userHostClient: config.UserHostClient, + broadcastClient: config.BroadcastClient, imAccountImporter: config.IMAccountImporter, imPublisher: config.IMPublisher, imGroupIDPrefix: config.IMGroupIDPrefix, @@ -115,6 +118,7 @@ func (h *Handler) WalletHandlers() httproutes.WalletHandlers { WithdrawSalaryWallet: h.withdrawSalaryWallet, GetPointWalletOverview: h.getPointWalletOverview, WithdrawPointWallet: h.withdrawPointWallet, + TransferPointWalletToCoinSeller: h.transferPointWalletToCoinSeller, GetRedPacketConfig: h.getRedPacketConfig, ListRoomRedPackets: h.listRoomRedPackets, CreateRoomRedPacket: h.createRoomRedPacket, @@ -133,9 +137,16 @@ func (h *Handler) WalletHandlers() httproutes.WalletHandlers { func (h *Handler) VIPHandlers() httproutes.VIPHandlers { return httproutes.VIPHandlers{ - GetMyVIP: h.getMyVIP, - ListVIPPackages: h.listVIPPackages, - PurchaseVIP: h.purchaseVIP, + GetMyVIP: h.getMyVIP, + ListVIPPackages: h.listVIPPackages, + PurchaseVIP: h.purchaseVIP, + EquipVIPTrialCard: h.equipVIPTrialCard, + UnequipVIPTrialCard: h.unequipVIPTrialCard, + TriggerOnlineNotice: h.triggerVIPOnlineNotice, + HandleVIPSettings: h.handleVIPSettings, + GetCurrentCoinRebate: h.getCurrentVIPCoinRebate, + ListCoinRebateStatuses: h.listVIPCoinRebateStatuses, + ClaimCoinRebate: h.claimVIPCoinRebate, } } diff --git a/services/gateway-service/internal/transport/http/walletapi/point_wallet_handler.go b/services/gateway-service/internal/transport/http/walletapi/point_wallet_handler.go index cfd002bf..73e6f51b 100644 --- a/services/gateway-service/internal/transport/http/walletapi/point_wallet_handler.go +++ b/services/gateway-service/internal/transport/http/walletapi/point_wallet_handler.go @@ -18,16 +18,15 @@ import ( ) const ( - pointWalletAssetType = "POINT" - pointWalletMinimumGross = int64(1000000) - pointWalletPointsPerUSD = int64(100000) - pointWalletWithdrawFeeBPS = int32(500) - pointWalletWithdrawalScope = "huwaa" + pointWalletAssetType = "POINT" ) type pointWithdrawalGatewayClient interface { FreezePointWithdrawal(ctx context.Context, req *walletv1.FreezePointWithdrawalRequest) (*walletv1.FreezePointWithdrawalResponse, error) ReleasePointWithdrawal(ctx context.Context, req *walletv1.ReleasePointWithdrawalRequest) (*walletv1.ReleasePointWithdrawalResponse, error) + ListPointWithdrawalCoinSellers(ctx context.Context, req *walletv1.ListPointWithdrawalCoinSellersRequest) (*walletv1.ListPointWithdrawalCoinSellersResponse, error) + TransferPointToCoinSeller(ctx context.Context, req *walletv1.TransferPointToCoinSellerRequest) (*walletv1.TransferPointToCoinSellerResponse, error) + GetPointWithdrawalConfig(ctx context.Context, req *walletv1.GetPointWithdrawalConfigRequest) (*walletv1.GetPointWithdrawalConfigResponse, error) } type pointWalletOverviewData struct { @@ -37,6 +36,19 @@ type pointWalletOverviewData struct { PointsPerUSD int64 `json:"points_per_usd"` FeeBPS int32 `json:"fee_bps"` MinimumPoints int64 `json:"minimum_points"` + CoinSellers []pointWalletCoinSellerData `json:"coin_sellers"` + PolicyInstance string `json:"policy_instance_code"` +} + +type pointWalletCoinSellerData struct { + UserID string `json:"user_id"` + DisplayUserID string `json:"display_user_id"` + Nickname string `json:"nickname"` + Avatar string `json:"avatar"` + SortOrder int32 `json:"sort_order"` + PointAmount int64 `json:"point_amount"` + SellerCoinAmount int64 `json:"seller_coin_amount"` + ServiceCountryCodes []string `json:"service_country_codes"` } type pointWalletBalanceData struct { @@ -66,6 +78,36 @@ type pointWalletWithdrawBody struct { Address string `json:"address"` } +type pointWalletCoinSellerTransferBody struct { + CommandID string `json:"command_id"` + CommandIDCamel string `json:"commandId"` + SellerUserID flexibleJSONInt64 `json:"seller_user_id"` + SellerUserIDCamel flexibleJSONInt64 `json:"sellerUserId"` + PointAmount int64 `json:"point_amount"` + PointAmountCamel int64 `json:"pointAmount"` +} + +func (b pointWalletCoinSellerTransferBody) commandID() string { + if value := strings.TrimSpace(b.CommandID); value != "" { + return value + } + return strings.TrimSpace(b.CommandIDCamel) +} + +func (b pointWalletCoinSellerTransferBody) sellerUserID() int64 { + if b.SellerUserID.Int64() > 0 { + return b.SellerUserID.Int64() + } + return b.SellerUserIDCamel.Int64() +} + +func (b pointWalletCoinSellerTransferBody) amount() int64 { + if b.PointAmount > 0 { + return b.PointAmount + } + return b.PointAmountCamel +} + func (b pointWalletWithdrawBody) commandID() string { if value := strings.TrimSpace(b.CommandID); value != "" { return value @@ -102,7 +144,7 @@ func (b pointWalletWithdrawBody) usdtTRC20Address() string { } func (h *Handler) getPointWalletOverview(writer http.ResponseWriter, request *http.Request) { - if !isHuwaaPointWalletRequest(request) { + if !isPointWalletRequest(request) { httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "permission denied") return } @@ -110,7 +152,37 @@ func (h *Handler) getPointWalletOverview(writer http.ResponseWriter, request *ht httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } + pointWallet, ok := h.walletClient.(pointWithdrawalGatewayClient) + if !ok { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } userID := auth.UserIDFromContext(request.Context()) + profileResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ + Meta: httpkit.UserMeta(request, ""), UserId: userID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + profile := profileResp.GetUser() + if profile == nil { + httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "user not found") + return + } + config, err := pointWallet.GetPointWithdrawalConfig(request.Context(), &walletv1.GetPointWithdrawalConfigRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), + RegionId: profile.GetRegionId(), NowMs: time.Now().UTC().UnixMilli(), + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + if !config.GetFound() { + // 没有已发布政策就关闭资金入口;不能回退到编译期常量造成 Admin 看似配置但账务仍走旧值。 + httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "point wallet policy is not configured") + return + } balanceResp, err := h.walletClient.GetBalances(request.Context(), &walletv1.GetBalancesRequest{ RequestId: httpkit.RequestIDFromContext(request.Context()), UserId: userID, @@ -121,26 +193,33 @@ func (h *Handler) getPointWalletOverview(writer http.ResponseWriter, request *ht httpkit.WriteRPCError(writer, request, err) return } - profileResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ - Meta: httpkit.UserMeta(request, ""), - UserId: userID, + sellerResp, err := pointWallet.ListPointWithdrawalCoinSellers(request.Context(), &walletv1.ListPointWithdrawalCoinSellersRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), + CountryCode: pointWalletCountryCode(profile), }) if err != nil { httpkit.WriteRPCError(writer, request, err) return } + sellers, err := h.pointWalletCoinSellerData(request, sellerResp.GetSellers()) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } httpkit.WriteOK(writer, request, pointWalletOverviewData{ AssetType: pointWalletAssetType, - Balance: pointBalanceFromProto(firstBalance(balanceResp.GetBalances())), + Balance: pointBalanceFromProto(firstBalance(balanceResp.GetBalances()), config.GetPointsPerUsd()), WithdrawAddress: salaryWithdrawAddressFromProto(profileResp.GetUser()), - PointsPerUSD: pointWalletPointsPerUSD, - FeeBPS: pointWalletWithdrawFeeBPS, - MinimumPoints: pointWalletMinimumGross, + PointsPerUSD: config.GetPointsPerUsd(), + FeeBPS: config.GetFeeBps(), + MinimumPoints: config.GetMinimumPoints(), + CoinSellers: sellers, + PolicyInstance: config.GetPolicyInstanceCode(), }) } func (h *Handler) withdrawPointWallet(writer http.ResponseWriter, request *http.Request) { - if !isHuwaaPointWalletRequest(request) { + if !isPointWalletRequest(request) { httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "permission denied") return } @@ -164,13 +243,31 @@ func (h *Handler) withdrawPointWallet(writer http.ResponseWriter, request *http. return } grossPoints := body.grossPointAmount() - if grossPoints < pointWalletMinimumGross { + userID := auth.UserIDFromContext(request.Context()) + profileResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{Meta: httpkit.UserMeta(request, ""), UserId: userID}) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + profile := profileResp.GetUser() + if profile == nil { + httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "user not found") + return + } + config, err := pointWallet.GetPointWithdrawalConfig(request.Context(), &walletv1.GetPointWithdrawalConfigRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), + RegionId: profile.GetRegionId(), NowMs: time.Now().UTC().UnixMilli(), + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + if !config.GetFound() || grossPoints < config.GetMinimumPoints() { httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } - feePoints := grossPoints * int64(pointWalletWithdrawFeeBPS) / 10000 + feePoints := grossPoints * int64(config.GetFeeBps()) / 10000 netPoints := grossPoints - feePoints - userID := auth.UserIDFromContext(request.Context()) balanceResp, err := h.walletClient.GetBalances(request.Context(), &walletv1.GetBalancesRequest{ RequestId: httpkit.RequestIDFromContext(request.Context()), UserId: userID, @@ -199,8 +296,8 @@ func (h *Handler) withdrawPointWallet(writer http.ResponseWriter, request *http. GrossPointAmount: grossPoints, FeePointAmount: feePoints, NetPointAmount: netPoints, - PointsPerUsd: pointWalletPointsPerUSD, - FeeBps: pointWalletWithdrawFeeBPS, + PointsPerUsd: config.GetPointsPerUsd(), + FeeBps: config.GetFeeBps(), Reason: "point withdrawal submitted", AppCode: appcode.FromContext(request.Context()), WithdrawalRef: httpkit.RequestIDFromContext(request.Context()), @@ -213,8 +310,13 @@ func (h *Handler) withdrawPointWallet(writer http.ResponseWriter, request *http. AppCode: appcode.FromContext(request.Context()), UserID: userID, SalaryAssetType: pointWalletAssetType, - WithdrawAmount: formatPointUSD(netPoints, pointWalletPointsPerUSD), + WithdrawAmount: formatPointUSD(netPoints, config.GetPointsPerUsd()), WithdrawAmountMinor: grossPoints, + PointFeeAmount: feePoints, + PointNetAmount: netPoints, + PointsPerUSD: config.GetPointsPerUsd(), + PointFeeBPS: config.GetFeeBps(), + PointPolicyInstance: config.GetPolicyInstanceCode(), WithdrawMethod: financewithdrawal.MethodUSDTTRC20, WithdrawAddress: address, FreezeCommandID: freezeCommandID, @@ -222,7 +324,7 @@ func (h *Handler) withdrawPointWallet(writer http.ResponseWriter, request *http. CreatedAtMS: nowMS, }) if err != nil { - rollbackPointWithdrawalFreeze(request, pointWallet, freezeCommandID, userID, grossPoints, feePoints, netPoints) + rollbackPointWithdrawalFreeze(request, pointWallet, freezeCommandID, userID, grossPoints, feePoints, netPoints, config.GetPointsPerUsd(), config.GetFeeBps()) httpkit.WriteError(writer, request, http.StatusInternalServerError, httpkit.CodeInternalError, "internal error") return } @@ -239,12 +341,13 @@ func (h *Handler) withdrawPointWallet(writer http.ResponseWriter, request *http. "gross_point_amount": grossPoints, "fee_point_amount": feePoints, "net_point_amount": netPoints, - "points_per_usd": pointWalletPointsPerUSD, - "fee_bps": pointWalletWithdrawFeeBPS, - "gross_usd": formatPointUSD(grossPoints, pointWalletPointsPerUSD), - "fee_usd": formatPointUSD(feePoints, pointWalletPointsPerUSD), - "net_usd": formatPointUSD(netPoints, pointWalletPointsPerUSD), - "balance_after": pointBalanceFromProto(freezeResp.GetBalance()), + "points_per_usd": config.GetPointsPerUsd(), + "fee_bps": config.GetFeeBps(), + "policy_instance_code": config.GetPolicyInstanceCode(), + "gross_usd": formatPointUSD(grossPoints, config.GetPointsPerUsd()), + "fee_usd": formatPointUSD(feePoints, config.GetPointsPerUsd()), + "net_usd": formatPointUSD(netPoints, config.GetPointsPerUsd()), + "balance_after": pointBalanceFromProto(freezeResp.GetBalance(), config.GetPointsPerUsd()), "withdraw_method": application.WithdrawMethod, "withdraw_address": application.WithdrawAddress, "freeze_transaction_id": freezeResp.GetTransactionId(), @@ -252,11 +355,17 @@ func (h *Handler) withdrawPointWallet(writer http.ResponseWriter, request *http. }) } -func isHuwaaPointWalletRequest(request *http.Request) bool { - return strings.EqualFold(appcode.FromContext(request.Context()), pointWalletWithdrawalScope) +func isPointWalletRequest(request *http.Request) bool { + // 新钱包首先服务 Huwaa 与 Fami;Lalu 旧 gonghui 页面和既有工资钱包路由不做行为变更。 + switch strings.ToLower(strings.TrimSpace(appcode.FromContext(request.Context()))) { + case "huwaa", "fami": + return true + default: + return false + } } -func pointBalanceFromProto(balance *walletv1.AssetBalance) pointWalletBalanceData { +func pointBalanceFromProto(balance *walletv1.AssetBalance, pointsPerUSD int64) pointWalletBalanceData { if balance == nil { return pointWalletBalanceData{AssetType: pointWalletAssetType, DisplayUSD: "0.00"} } @@ -265,7 +374,7 @@ func pointBalanceFromProto(balance *walletv1.AssetBalance) pointWalletBalanceDat AvailableAmount: balance.GetAvailableAmount(), FrozenAmount: balance.GetFrozenAmount(), Version: balance.GetVersion(), - DisplayUSD: formatPointUSD(balance.GetAvailableAmount(), pointWalletPointsPerUSD), + DisplayUSD: formatPointUSD(balance.GetAvailableAmount(), pointsPerUSD), } } @@ -288,7 +397,7 @@ func pointWithdrawalFreezeCommandID(submittedCommandID string, requestID string, return fmt.Sprintf("point-withdrawal:%d", nowMS) } -func rollbackPointWithdrawalFreeze(request *http.Request, walletClient pointWithdrawalGatewayClient, freezeCommandID string, userID int64, grossPoints int64, feePoints int64, netPoints int64) { +func rollbackPointWithdrawalFreeze(request *http.Request, walletClient pointWithdrawalGatewayClient, freezeCommandID string, userID int64, grossPoints int64, feePoints int64, netPoints int64, pointsPerUSD int64, feeBPS int32) { rollbackCommandID := strings.TrimSpace(freezeCommandID) + ":rollback" if len(rollbackCommandID) > 128 { rollbackCommandID = fmt.Sprintf("point-withdrawal-rollback:%s", httpkit.RequestIDFromContext(request.Context())) @@ -302,8 +411,8 @@ func rollbackPointWithdrawalFreeze(request *http.Request, walletClient pointWith GrossPointAmount: grossPoints, FeePointAmount: feePoints, NetPointAmount: netPoints, - PointsPerUsd: pointWalletPointsPerUSD, - FeeBps: pointWalletWithdrawFeeBPS, + PointsPerUsd: pointsPerUSD, + FeeBps: feeBPS, Reason: "point withdrawal application create failed", AppCode: appcode.FromContext(request.Context()), }) @@ -311,3 +420,119 @@ func rollbackPointWithdrawalFreeze(request *http.Request, walletClient pointWith slog.Error("point_withdrawal_freeze_rollback_failed", "user_id", userID, "gross_point_amount", grossPoints, "freeze_command_id", freezeCommandID, "error", err) } } + +// transferPointWalletToCoinSeller 只接收目标和 POINT 金额;wallet 事务会重新读取 active 白名单与比例。 +func (h *Handler) transferPointWalletToCoinSeller(writer http.ResponseWriter, request *http.Request) { + if !isPointWalletRequest(request) { + httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "permission denied") + return + } + pointWallet, ok := h.walletClient.(pointWithdrawalGatewayClient) + if !ok || h.userProfileClient == nil || h.userHostClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + var body pointWalletCoinSellerTransferBody + if !httpkit.Decode(writer, request, &body) { + return + } + commandID := body.commandID() + sellerUserID := body.sellerUserID() + pointAmount := body.amount() + if commandID == "" || sellerUserID <= 0 || pointAmount <= 0 { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + userID := auth.UserIDFromContext(request.Context()) + profileResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{Meta: httpkit.UserMeta(request, ""), UserId: userID}) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + if profileResp.GetUser() == nil { + httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "user not found") + return + } + runtimeConfig, err := pointWallet.GetPointWithdrawalConfig(request.Context(), &walletv1.GetPointWithdrawalConfigRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), + RegionId: profileResp.GetUser().GetRegionId(), NowMs: time.Now().UTC().UnixMilli(), + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + if !runtimeConfig.GetFound() || pointAmount < runtimeConfig.GetMinimumPoints() { + // 币商和 USDT 两种提现共用已发布政策中的最低门槛,客户端不能通过切换页签绕过。 + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "point withdrawal amount is below minimum") + return + } + sellerProfileResp, err := h.userHostClient.GetCoinSellerProfile(request.Context(), &userv1.GetCoinSellerProfileRequest{ + Meta: httpkit.UserMeta(request, ""), UserId: sellerUserID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + sellerProfile := sellerProfileResp.GetCoinSellerProfile() + if sellerProfile == nil || sellerProfile.GetStatus() != "active" || sellerProfile.GetMerchantAssetType() != "COIN_SELLER_COIN" { + // Admin 白名单只证明配置存在;转账时仍要检查实时币商身份,防止币商被停用后继续收款。 + httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "coin seller is not active") + return + } + receipt, err := pointWallet.TransferPointToCoinSeller(request.Context(), &walletv1.TransferPointToCoinSellerRequest{ + CommandId: commandID, AppCode: appcode.FromContext(request.Context()), SourceUserId: userID, + SellerUserId: sellerUserID, PointAmount: pointAmount, + SourceCountryCode: pointWalletCountryCode(profileResp.GetUser()), Reason: "point wallet withdrawal to coin seller", + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + httpkit.WriteOK(writer, request, map[string]any{ + "transaction_id": receipt.GetTransactionId(), "seller_user_id": strconv.FormatInt(sellerUserID, 10), + "point_amount": receipt.GetPointAmount(), "seller_coin_amount": receipt.GetSellerCoinAmount(), + "source_point_balance_after": receipt.GetSourcePointBalanceAfter(), "seller_balance_after": receipt.GetSellerBalanceAfter(), + "ratio_point_amount": receipt.GetRatioPointAmount(), "ratio_seller_coin_amount": receipt.GetRatioSellerCoinAmount(), + }) +} + +func (h *Handler) pointWalletCoinSellerData(request *http.Request, configs []*walletv1.PointWithdrawalCoinSellerConfig) ([]pointWalletCoinSellerData, error) { + if len(configs) == 0 { + return []pointWalletCoinSellerData{}, nil + } + userIDs := make([]int64, 0, len(configs)) + for _, config := range configs { + if config != nil && config.GetSellerUserId() > 0 { + userIDs = append(userIDs, config.GetSellerUserId()) + } + } + profiles, err := h.userProfileClient.BatchGetUsers(request.Context(), &userv1.BatchGetUsersRequest{Meta: httpkit.UserMeta(request, ""), UserIds: userIDs}) + if err != nil { + return nil, err + } + items := make([]pointWalletCoinSellerData, 0, len(configs)) + for _, config := range configs { + if config == nil { + continue + } + profile := profiles.GetUsers()[config.GetSellerUserId()] + // 白名单可在用户删除/禁用后短暂残留;读模型跳过缺失用户,转账入口还会实时校验 coin-seller 身份。 + if profile == nil || profile.GetStatus() != userv1.UserStatus_USER_STATUS_ACTIVE { + continue + } + items = append(items, pointWalletCoinSellerData{ + UserID: strconv.FormatInt(profile.GetUserId(), 10), DisplayUserID: profile.GetDisplayUserId(), + Nickname: profile.GetUsername(), Avatar: profile.GetAvatar(), SortOrder: config.GetSortOrder(), + PointAmount: config.GetPointAmount(), SellerCoinAmount: config.GetSellerCoinAmount(), + ServiceCountryCodes: append([]string(nil), config.GetServiceCountryCodes()...), + }) + } + return items, nil +} + +func pointWalletCountryCode(profile *userv1.User) string { + if profile == nil { + return "" + } + return strings.ToUpper(strings.TrimSpace(profile.GetCountry())) +} diff --git a/services/gateway-service/internal/transport/http/walletapi/point_wallet_handler_test.go b/services/gateway-service/internal/transport/http/walletapi/point_wallet_handler_test.go index 5e96c997..0f20e58d 100644 --- a/services/gateway-service/internal/transport/http/walletapi/point_wallet_handler_test.go +++ b/services/gateway-service/internal/transport/http/walletapi/point_wallet_handler_test.go @@ -91,6 +91,11 @@ func TestWithdrawPointWalletFreezesWithClientCommandIDAndCreatesApplication(t *t writer.last.SalaryAssetType != "POINT" || writer.last.WithdrawAmount != "9.50" || writer.last.WithdrawAmountMinor != 1_000_000 || + writer.last.PointFeeAmount != 50_000 || + writer.last.PointNetAmount != 950_000 || + writer.last.PointsPerUSD != 100_000 || + writer.last.PointFeeBPS != 500 || + writer.last.PointPolicyInstance != "test-policy" || writer.last.FreezeCommandID != "client-point-withdraw-1" || writer.last.FreezeTransactionID != "point-freeze-tx" || writer.last.WithdrawAddress != "TQY9pFbZHuR4fUN9WgXPYj5nmB6nQiLQfF" { @@ -112,13 +117,34 @@ func TestWithdrawPointWalletFreezesWithClientCommandIDAndCreatesApplication(t *t } } +func TestTransferPointWalletToCoinSellerUsesStringIDAndTrustedCountry(t *testing.T) { + wallet := &fakePointWalletClient{} + handler := New(Config{ + WalletClient: wallet, + UserProfileClient: &fakePointUserProfileClient{country: "SA"}, + UserHostClient: &fakePointUserHostClient{}, + }) + recorder := httptest.NewRecorder() + request := pointWalletRequest(`{"command_id":"point-seller-1","seller_user_id":"9007199254740001","point_amount":1000000}`, "fami") + + httpkit.WithRequestID(http.HandlerFunc(handler.transferPointWalletToCoinSeller)).ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("point seller transfer status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if wallet.lastPointTransfer == nil || wallet.lastPointTransfer.GetSellerUserId() != 9007199254740001 || wallet.lastPointTransfer.GetSourceUserId() != 42001 || wallet.lastPointTransfer.GetSourceCountryCode() != "SA" || wallet.lastPointTransfer.GetAppCode() != "fami" { + t.Fatalf("point seller transfer request mismatch: %+v", wallet.lastPointTransfer) + } +} + type fakePointWalletClient struct { client.WalletClient - balances []*walletv1.AssetBalance - lastBalance *walletv1.GetBalancesRequest - lastFreeze *walletv1.FreezePointWithdrawalRequest - lastRelease *walletv1.ReleasePointWithdrawalRequest - freezeErr error + balances []*walletv1.AssetBalance + lastBalance *walletv1.GetBalancesRequest + lastFreeze *walletv1.FreezePointWithdrawalRequest + lastRelease *walletv1.ReleasePointWithdrawalRequest + lastPointTransfer *walletv1.TransferPointToCoinSellerRequest + freezeErr error } func (f *fakePointWalletClient) GetBalances(_ context.Context, req *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error) { @@ -151,9 +177,23 @@ func (f *fakePointWalletClient) ReleasePointWithdrawal(_ context.Context, req *w return &walletv1.ReleasePointWithdrawalResponse{TransactionId: "point-release-tx"}, nil } +func (f *fakePointWalletClient) GetPointWithdrawalConfig(_ context.Context, _ *walletv1.GetPointWithdrawalConfigRequest) (*walletv1.GetPointWithdrawalConfigResponse, error) { + return &walletv1.GetPointWithdrawalConfigResponse{Found: true, PointsPerUsd: 100_000, FeeBps: 500, MinimumPoints: 1_000_000, PolicyInstanceCode: "test-policy"}, nil +} + +func (f *fakePointWalletClient) ListPointWithdrawalCoinSellers(_ context.Context, _ *walletv1.ListPointWithdrawalCoinSellersRequest) (*walletv1.ListPointWithdrawalCoinSellersResponse, error) { + return &walletv1.ListPointWithdrawalCoinSellersResponse{}, nil +} + +func (f *fakePointWalletClient) TransferPointToCoinSeller(_ context.Context, req *walletv1.TransferPointToCoinSellerRequest) (*walletv1.TransferPointToCoinSellerResponse, error) { + f.lastPointTransfer = req + return &walletv1.TransferPointToCoinSellerResponse{TransactionId: "point-seller-tx", PointAmount: req.GetPointAmount(), SellerCoinAmount: 920000}, nil +} + type fakePointUserProfileClient struct { client.UserProfileClient address string + country string err error } @@ -161,7 +201,15 @@ func (f *fakePointUserProfileClient) GetUser(_ context.Context, req *userv1.GetU if f.err != nil { return nil, f.err } - return &userv1.GetUserResponse{User: &userv1.User{UserId: req.GetUserId(), WithdrawUsdtTrc20Address: f.address}}, nil + return &userv1.GetUserResponse{User: &userv1.User{UserId: req.GetUserId(), Country: f.country, WithdrawUsdtTrc20Address: f.address}}, nil +} + +type fakePointUserHostClient struct { + client.UserHostClient +} + +func (f *fakePointUserHostClient) GetCoinSellerProfile(_ context.Context, req *userv1.GetCoinSellerProfileRequest) (*userv1.GetCoinSellerProfileResponse, error) { + return &userv1.GetCoinSellerProfileResponse{CoinSellerProfile: &userv1.CoinSellerProfile{UserId: req.GetUserId(), Status: "active", MerchantAssetType: "COIN_SELLER_COIN"}}, nil } func (f *fakePointUserProfileClient) UpdateUserWithdrawAddress(_ context.Context, req *userv1.UpdateUserWithdrawAddressRequest) (*userv1.UpdateUserWithdrawAddressResponse, error) { @@ -190,6 +238,11 @@ func (f *fakePointWithdrawalWriter) CreateApplication(_ context.Context, command SalaryAssetType: command.SalaryAssetType, WithdrawAmount: command.WithdrawAmount, WithdrawAmountMinor: command.WithdrawAmountMinor, + PointFeeAmount: command.PointFeeAmount, + PointNetAmount: command.PointNetAmount, + PointsPerUSD: command.PointsPerUSD, + PointFeeBPS: command.PointFeeBPS, + PointPolicyInstance: command.PointPolicyInstance, WithdrawMethod: command.WithdrawMethod, WithdrawAddress: command.WithdrawAddress, FreezeCommandID: command.FreezeCommandID, diff --git a/services/gateway-service/internal/transport/http/walletapi/view_shared.go b/services/gateway-service/internal/transport/http/walletapi/view_shared.go index d0d7d6f9..8d05b218 100644 --- a/services/gateway-service/internal/transport/http/walletapi/view_shared.go +++ b/services/gateway-service/internal/transport/http/walletapi/view_shared.go @@ -3,12 +3,14 @@ package walletapi import walletv1 "hyapp.local/api/proto/wallet/v1" type overviewVIPData struct { - Level int32 `json:"level"` - Name string `json:"name"` - Active bool `json:"active"` - StartedAtMS int64 `json:"started_at_ms"` - ExpiresAtMS int64 `json:"expires_at_ms"` - Unavailable bool `json:"unavailable,omitempty"` + Level int32 `json:"level"` + Name string `json:"name"` + Active bool `json:"active"` + StartedAtMS int64 `json:"started_at_ms"` + ExpiresAtMS int64 `json:"expires_at_ms"` + ProgramType string `json:"program_type,omitempty"` + ConfigVersion int64 `json:"config_version,omitempty"` + Unavailable bool `json:"unavailable,omitempty"` } type resourceData struct { diff --git a/services/gateway-service/internal/transport/http/walletapi/vip_handler.go b/services/gateway-service/internal/transport/http/walletapi/vip_handler.go index 86dd03e5..587aecef 100644 --- a/services/gateway-service/internal/transport/http/walletapi/vip_handler.go +++ b/services/gateway-service/internal/transport/http/walletapi/vip_handler.go @@ -1,17 +1,48 @@ package walletapi import ( - "hyapp/services/gateway-service/internal/transport/http/httpkit" + "crypto/sha256" + "encoding/json" + "fmt" "net/http" "strings" + activityv1 "hyapp.local/api/proto/activity/v1" + userv1 "hyapp.local/api/proto/user/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" "hyapp/pkg/appcode" "hyapp/services/gateway-service/internal/auth" - - walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/services/gateway-service/internal/transport/http/httpkit" ) -// getMyVIP 返回当前用户 VIP 状态。 +const ( + vipOnlineNoticeBenefitCode = "online_global_notice" + vipOnlineNoticeBroadcastType = "vip_online_notice" + vipOnlineNoticeAvatarFrame = "avatar_frame" + vipOnlineNoticeCommandMaxLen = 128 + vipOnlineNoticeMessageMaxLen = 256 +) + +type vipOnlineNoticeRequestBody struct { + // CommandID 是单次 App 进程启动的业务幂等键;request_id 只用于链路追踪,不能替代它。 + CommandID string `json:"command_id"` + CommandIDAlt string `json:"commandId"` +} + +type vipOnlineNoticeMetadata struct { + Message string `json:"message"` +} + +type vipOnlineNoticeAvatarFrameData struct { + ResourceID int64 + Code string + AssetURL string + PreviewURL string + AnimationURL string +} + +// getMyVIP 同时返回旧版扁平 VIP 字段和新的 paid/trial/effective 状态。 +// 扁平字段继续取 GetMyVipResponse.vip,保证旧客户端升级期间无需同时切换解析路径。 func (h *Handler) getMyVIP(writer http.ResponseWriter, request *http.Request) { if h.walletClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") @@ -26,7 +57,12 @@ func (h *Handler) getMyVIP(writer http.ResponseWriter, request *http.Request) { httpkit.WriteRPCError(writer, request, err) return } - httpkit.WriteOK(writer, request, vipFromProto(resp.GetVip())) + state := vipStateFromProto(resp.GetState()) + httpkit.WriteOK(writer, request, vipMeData{ + OverviewVIPData: vipFromProto(resp.GetVip()), + ProgramConfig: state.ProgramConfig, + State: state, + }) } // listVIPPackages 返回可购买 VIP 包和当前会员状态。 @@ -48,7 +84,17 @@ func (h *Handler) listVIPPackages(writer http.ResponseWriter, request *http.Requ for _, item := range resp.GetPackages() { packages = append(packages, vipPackageFromProto(item)) } - httpkit.WriteOK(writer, request, map[string]any{"current_vip": vipFromProto(resp.GetCurrentVip()), "packages": packages}) + state := vipStateFromProto(resp.GetState()) + programConfig := vipProgramConfigFromProto(resp.GetProgramConfig()) + if resp.GetProgramConfig() == nil { + programConfig = state.ProgramConfig + } + httpkit.WriteOK(writer, request, map[string]any{ + "current_vip": vipFromProto(resp.GetCurrentVip()), + "packages": packages, + "program_config": programConfig, + "state": state, + }) } // purchaseVIP 购买、续期或升级 VIP。 @@ -86,19 +132,239 @@ func (h *Handler) purchaseVIP(writer http.ResponseWriter, request *http.Request) "coin_spent": resp.GetCoinSpent(), "coin_balance_after": resp.GetCoinBalanceAfter(), "reward_items": vipRewardItemsFromProto(resp.GetRewardItems()), + "program_config": vipProgramConfigFromProto(resp.GetState().GetProgramConfig()), + "state": vipStateFromProto(resp.GetState()), }) } +// equipVIPTrialCard 只接受背包实例 entitlement_id;同资源的多张卡不能用 resource_id +// 代替,否则切换时会误改另一张卡的独立到期时间。 +func (h *Handler) equipVIPTrialCard(writer http.ResponseWriter, request *http.Request) { + if h.walletClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + entitlementID := strings.TrimSpace(request.PathValue("entitlement_id")) + if entitlementID == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + resp, err := h.walletClient.EquipVipTrialCard(request.Context(), &walletv1.EquipVipTrialCardRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + AppCode: appcode.FromContext(request.Context()), + UserId: auth.UserIDFromContext(request.Context()), + EntitlementId: entitlementID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + state := vipStateFromProto(resp.GetState()) + httpkit.WriteOK(writer, request, map[string]any{ + "trial_card": vipTrialCardFromProto(resp.GetTrialCard()), + "program_config": state.ProgramConfig, + "state": state, + "server_time_ms": resp.GetServerTimeMs(), + }) +} + +// unequipVIPTrialCard 只解除当前装备关系;体验卡实例和其绝对过期时间仍保留在背包, +// 因而客户端可以随后重新佩戴尚未过期的任意等级体验卡。 +func (h *Handler) unequipVIPTrialCard(writer http.ResponseWriter, request *http.Request) { + if h.walletClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + resp, err := h.walletClient.UnequipVipTrialCard(request.Context(), &walletv1.UnequipVipTrialCardRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + AppCode: appcode.FromContext(request.Context()), + UserId: auth.UserIDFromContext(request.Context()), + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + state := vipStateFromProto(resp.GetState()) + httpkit.WriteOK(writer, request, map[string]any{ + "unequipped": resp.GetUnequipped(), + "program_config": state.ProgramConfig, + "state": state, + "server_time_ms": resp.GetServerTimeMs(), + }) +} + +// triggerVIPOnlineNotice 是 VIP 上线全服飘屏的唯一 App HTTP 入口。 +// Flutter 负责进程内“UTC 当日只请求一次”的展示策略;服务端不做按日持久化去重,因而杀进程 +// 后的新进程可以再次请求。单次请求重试仍通过 command_id 映射为稳定 event_id,避免网络重试重复播报。 +func (h *Handler) triggerVIPOnlineNotice(writer http.ResponseWriter, request *http.Request) { + if h.walletClient == nil || h.userProfileClient == nil || h.broadcastClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + var body vipOnlineNoticeRequestBody + if !httpkit.Decode(writer, request, &body) { + return + } + commandID := strings.TrimSpace(body.CommandID) + if commandID == "" { + commandID = strings.TrimSpace(body.CommandIDAlt) + } + if commandID == "" || len(commandID) > vipOnlineNoticeCommandMaxLen { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + + ctx := request.Context() + userID := auth.UserIDFromContext(ctx) + appCode := appcode.FromContext(ctx) + benefitResp, err := h.walletClient.CheckVipBenefit(ctx, &walletv1.CheckVipBenefitRequest{ + RequestId: httpkit.RequestIDFromContext(ctx), + AppCode: appCode, + UserId: userID, + BenefitCode: vipOnlineNoticeBenefitCode, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + if !benefitResp.GetAllowed() { + // 客户端展示态可能滞后,执行前必须重新查 effective benefit;体验卡排除、过期和后台停用 + // 都以 wallet-service 此刻的判定为准,不能仅比较 VIP9 等级。 + httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "permission denied") + return + } + equippedResp, err := h.walletClient.BatchGetUserEquippedResources(ctx, &walletv1.BatchGetUserEquippedResourcesRequest{ + RequestId: httpkit.RequestIDFromContext(ctx), + AppCode: appCode, + UserIds: []int64{userID}, + ResourceTypes: []string{vipOnlineNoticeAvatarFrame}, + }) + if err != nil { + // 头像框是全服消息的一部分,不能在 wallet 查询失败时把“无头像框”当成真实佩戴态广播。 + // 失败请求由客户端复用同一 command_id 重试,成功后仍只会写入同一条 activity outbox。 + httpkit.WriteRPCError(writer, request, err) + return + } + avatarFrame := vipOnlineNoticeAvatarFrameFromResponse(equippedResp, userID) + profileResp, err := h.userProfileClient.GetUser(ctx, &userv1.GetUserRequest{ + Meta: httpkit.UserMeta(request, ""), + UserId: userID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + profile := profileResp.GetUser() + if profile == nil || profile.GetUserId() != userID { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + + effectiveVIP := benefitResp.GetState().GetEffectiveVip() + displayID := strings.TrimSpace(profile.GetPrettyDisplayUserId()) + if displayID == "" { + displayID = strings.TrimSpace(profile.GetDisplayUserId()) + } + payload, err := json.Marshal(map[string]any{ + "user_id": userID, + "sender_user_id": userID, + "nickname": profile.GetUsername(), + "avatar": profile.GetAvatar(), + "display_user_id": displayID, + "raw_display_user_id": profile.GetDisplayUserId(), + "pretty_display_user_id": profile.GetPrettyDisplayUserId(), + // avatar_frame_url 是客户端高频兼容字段,固定等同资源 asset_url;其余字段保留完整素材信息, + // 让静态、预览和动画渲染都只消费 wallet 返回的当前佩戴事实。 + "avatar_frame_url": avatarFrame.AssetURL, + "avatar_frame_resource_id": avatarFrame.ResourceID, + "avatar_frame_code": avatarFrame.Code, + "avatar_frame_asset_url": avatarFrame.AssetURL, + "avatar_frame_preview_url": avatarFrame.PreviewURL, + "avatar_frame_animation_url": avatarFrame.AnimationURL, + "vip_program_type": effectiveVIP.GetProgramType(), + "vip_level": effectiveVIP.GetLevel(), + "vip_name": effectiveVIP.GetName(), + "message": vipOnlineNoticeMessage(appCode, benefitResp.GetBenefit().GetMetadataJson()), + "action": map[string]string{"type": "none"}, + }) + if err != nil { + httpkit.WriteError(writer, request, http.StatusInternalServerError, httpkit.CodeInternalError, "internal error") + return + } + // event_id 使用 user_id + command_id 摘要,既隔离不同用户,又避免客户端长命令超过 + // activity outbox 的 VARCHAR(128) 主键;相同 command_id 的 HTTP 重试会命中同一条 outbox。 + commandDigest := sha256.Sum256([]byte(commandID)) + eventID := fmt.Sprintf("vip_online_notice:%d:%x", userID, commandDigest) + broadcastResp, err := h.broadcastClient.PublishGlobalBroadcast(ctx, &activityv1.PublishGlobalBroadcastRequest{ + Meta: httpkit.ActivityMeta(request), + EventId: eventID, + BroadcastType: vipOnlineNoticeBroadcastType, + PayloadJson: string(payload), + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + httpkit.WriteOK(writer, request, map[string]any{ + "event_id": broadcastResp.GetEventId(), + "group_id": broadcastResp.GetGroupId(), + "status": broadcastResp.GetStatus(), + "created": broadcastResp.GetCreated(), + "evaluated_at_ms": benefitResp.GetEvaluatedAtMs(), + }) +} + +// vipOnlineNoticeAvatarFrameFromResponse 只接受目标用户下的 avatar_frame。 +// wallet 的 batch RPC 已负责过滤过期佩戴,这里仍防御错误分组、错误资源类型和空资源,避免串用户素材。 +func vipOnlineNoticeAvatarFrameFromResponse(resp *walletv1.BatchGetUserEquippedResourcesResponse, userID int64) vipOnlineNoticeAvatarFrameData { + for _, user := range resp.GetUsers() { + if user.GetUserId() != userID { + continue + } + for _, item := range user.GetResources() { + resource := item.GetResource() + if resource.GetResourceType() != vipOnlineNoticeAvatarFrame || resource.GetResourceId() <= 0 { + continue + } + return vipOnlineNoticeAvatarFrameData{ + ResourceID: resource.GetResourceId(), + Code: resource.GetResourceCode(), + AssetURL: resource.GetAssetUrl(), + PreviewURL: resource.GetPreviewUrl(), + AnimationURL: resource.GetAnimationUrl(), + } + } + } + return vipOnlineNoticeAvatarFrameData{} +} + +// vipOnlineNoticeMessage 从每级权益 metadata_json 读取 App 独立文案。 +// Fami 旧配置没有 metadata 时保留产品确认的默认文案;其他 App 使用中性兜底,避免把 Fami 名称串到 Lalu。 +func vipOnlineNoticeMessage(appCode string, metadataJSON string) string { + var metadata vipOnlineNoticeMetadata + if json.Unmarshal([]byte(strings.TrimSpace(metadataJSON)), &metadata) == nil { + if message := strings.TrimSpace(metadata.Message); message != "" && len(message) <= vipOnlineNoticeMessageMaxLen { + return message + } + } + if strings.EqualFold(strings.TrimSpace(appCode), "fami") { + return "欢迎进入Fami,祝你有美好的一天" + } + return "欢迎进入,祝你有美好的一天" +} + func vipFromProto(vip *walletv1.UserVip) overviewVIPData { if vip == nil { return overviewVIPData{} } return overviewVIPData{ - Level: vip.GetLevel(), - Name: vip.GetName(), - Active: vip.GetActive(), - StartedAtMS: vip.GetStartedAtMs(), - ExpiresAtMS: vip.GetExpiresAtMs(), + Level: vip.GetLevel(), + Name: vip.GetName(), + Active: vip.GetActive(), + StartedAtMS: vip.GetStartedAtMs(), + ExpiresAtMS: vip.GetExpiresAtMs(), + ProgramType: vip.GetProgramType(), + ConfigVersion: vip.GetConfigVersion(), } } @@ -120,6 +386,114 @@ func vipPackageFromProto(item *walletv1.VipLevel) vipPackageData { RequiredRechargeCoinAmount: item.GetRequiredRechargeCoinAmount(), UserRechargeCoinAmount: item.GetUserRechargeCoinAmount(), PurchaseLockedReason: item.GetPurchaseLockedReason(), + Benefits: vipBenefitsFromProto(item.GetBenefits()), + ConfigVersion: item.GetConfigVersion(), + } +} + +func vipProgramConfigFromProto(config *walletv1.VipProgramConfig) vipProgramConfigData { + if config == nil { + return vipProgramConfigData{} + } + return vipProgramConfigData{ + AppCode: config.GetAppCode(), + ProgramType: config.GetProgramType(), + LevelCount: config.GetLevelCount(), + SameLevelExpiryPolicy: config.GetSameLevelExpiryPolicy(), + UpgradeExpiryPolicy: config.GetUpgradeExpiryPolicy(), + DowngradePurchasePolicy: config.GetDowngradePurchasePolicy(), + BenefitInheritancePolicy: config.GetBenefitInheritancePolicy(), + GrantMode: config.GetGrantMode(), + TrialCardEnabled: config.GetTrialCardEnabled(), + Status: config.GetStatus(), + ConfigVersion: config.GetConfigVersion(), + UpdatedByAdminID: config.GetUpdatedByAdminId(), + CreatedAtMS: config.GetCreatedAtMs(), + UpdatedAtMS: config.GetUpdatedAtMs(), + } +} + +func vipBenefitsFromProto(items []*walletv1.VipBenefit) []vipBenefitData { + result := make([]vipBenefitData, 0, len(items)) + for _, item := range items { + if item == nil { + continue + } + result = append(result, vipBenefitData{ + BenefitCode: item.GetBenefitCode(), + Name: item.GetName(), + BenefitType: item.GetBenefitType(), + UnlockLevel: item.GetUnlockLevel(), + Status: item.GetStatus(), + TrialEnabled: item.GetTrialEnabled(), + ResourceID: item.GetResourceId(), + ResourceType: item.GetResourceType(), + ExecutionScope: item.GetExecutionScope(), + AutoEquip: item.GetAutoEquip(), + SortOrder: item.GetSortOrder(), + MetadataJSON: item.GetMetadataJson(), + CreatedAtMS: item.GetCreatedAtMs(), + UpdatedAtMS: item.GetUpdatedAtMs(), + }) + } + return result +} + +func vipTrialCardFromProto(card *walletv1.VipTrialCard) *vipTrialCardData { + if card == nil { + return nil + } + return &vipTrialCardData{ + TrialCardID: card.GetTrialCardId(), + EntitlementID: card.GetEntitlementId(), + ResourceID: card.GetResourceId(), + UserID: card.GetUserId(), + Level: card.GetLevel(), + Name: card.GetName(), + Status: card.GetStatus(), + Equipped: card.GetEquipped(), + DurationMS: card.GetDurationMs(), + EffectiveAtMS: card.GetEffectiveAtMs(), + ExpiresAtMS: card.GetExpiresAtMs(), + RemainingDurationMS: card.GetRemainingDurationMs(), + GrantSource: card.GetGrantSource(), + SourceGrantID: card.GetSourceGrantId(), + CreatedAtMS: card.GetCreatedAtMs(), + UpdatedAtMS: card.GetUpdatedAtMs(), + } +} + +func vipStateFromProto(state *walletv1.VipState) vipStateData { + if state == nil { + return vipStateData{ + EffectiveBenefits: []vipBenefitData{}, + UserSettings: vipUserSettingsData{ + RoomEntryNoticeEnabled: true, OnlineGlobalNoticeEnabled: true, + }, + } + } + return vipStateData{ + PaidVIP: vipFromProto(state.GetPaidVip()), + EquippedTrialCard: vipTrialCardFromProto(state.GetEquippedTrialCard()), + EffectiveVIP: vipFromProto(state.GetEffectiveVip()), + EffectiveSource: state.GetEffectiveSource(), + EffectiveBenefits: vipBenefitsFromProto(state.GetEffectiveBenefits()), + EvaluatedAtMS: state.GetEvaluatedAtMs(), + ProgramConfig: vipProgramConfigFromProto(state.GetProgramConfig()), + UserSettings: vipUserSettingsFromProto(state.GetUserSettings()), + } +} + +func vipUserSettingsFromProto(settings *walletv1.VipUserSettings) vipUserSettingsData { + if settings == nil { + // wallet 滚动升级期间旧实例没有 tag 8;与 wallet owner 的缺行语义一致,按默认开启兼容。 + return vipUserSettingsData{RoomEntryNoticeEnabled: true, OnlineGlobalNoticeEnabled: true} + } + return vipUserSettingsData{ + AppCode: settings.GetAppCode(), UserID: settings.GetUserId(), + RoomEntryNoticeEnabled: settings.GetRoomEntryNoticeEnabled(), + OnlineGlobalNoticeEnabled: settings.GetOnlineGlobalNoticeEnabled(), + UpdatedAtMS: settings.GetUpdatedAtMs(), } } diff --git a/services/gateway-service/internal/transport/http/walletapi/vip_notice_test.go b/services/gateway-service/internal/transport/http/walletapi/vip_notice_test.go new file mode 100644 index 00000000..383f69d1 --- /dev/null +++ b/services/gateway-service/internal/transport/http/walletapi/vip_notice_test.go @@ -0,0 +1,18 @@ +package walletapi + +import ( + "strings" + "testing" +) + +func TestVIPOnlineNoticeMessageUsesConfiguredValueAndSafeFallbacks(t *testing.T) { + if got := vipOnlineNoticeMessage("fami", "{"); got != "欢迎进入Fami,祝你有美好的一天" { + t.Fatalf("Fami fallback mismatch: %q", got) + } + if got := vipOnlineNoticeMessage("lalu", `{"message":" Lalu VIP online "}`); got != "Lalu VIP online" { + t.Fatalf("configured message mismatch: %q", got) + } + if got := vipOnlineNoticeMessage("lalu", `{"message":"`+strings.Repeat("x", 257)+`"}`); got != "欢迎进入,祝你有美好的一天" { + t.Fatalf("oversized message fallback mismatch: %q", got) + } +} diff --git a/services/gateway-service/internal/transport/http/walletapi/vip_rebate_handler.go b/services/gateway-service/internal/transport/http/walletapi/vip_rebate_handler.go new file mode 100644 index 00000000..695a3d84 --- /dev/null +++ b/services/gateway-service/internal/transport/http/walletapi/vip_rebate_handler.go @@ -0,0 +1,165 @@ +package walletapi + +import ( + "net/http" + "strings" + + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + "hyapp/services/gateway-service/internal/auth" + "hyapp/services/gateway-service/internal/transport/http/httpkit" +) + +type vipCoinRebateStatusesRequestBody struct { + RebateIDs []string `json:"rebate_ids"` + Page int32 `json:"page"` + PageSize int32 `json:"page_size"` +} + +type vipCoinRebateClaimRequestBody struct { + CommandID string `json:"command_id"` + CommandIDAlt string `json:"commandId"` +} + +type vipDailyCoinRebateData struct { + RebateID string `json:"rebate_id"` + UserID string `json:"user_id"` + TaskDay string `json:"task_day"` + VIPLevel int32 `json:"vip_level"` + VIPName string `json:"vip_name"` + CoinAmount int64 `json:"coin_amount"` + Status string `json:"status"` + AvailableAtMS int64 `json:"available_at_ms"` + ExpiresAtMS int64 `json:"expires_at_ms"` + ConfigVersion int64 `json:"config_version"` + ClaimedAtMS int64 `json:"claimed_at_ms"` + WalletTransactionID string `json:"wallet_transaction_id"` + CreatedAtMS int64 `json:"created_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` +} + +// getCurrentVIPCoinRebate 返回当前 UTC 自然日的领取事实。found=false 是用户当日无资格或 +// cron 尚未生成的正常状态,客户端不能据 VIP 等级自行补算金额。 +func (h *Handler) getCurrentVIPCoinRebate(writer http.ResponseWriter, request *http.Request) { + if h.walletClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + resp, err := h.walletClient.GetMyCurrentVipDailyCoinRebate(request.Context(), &walletv1.GetMyCurrentVipDailyCoinRebateRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + AppCode: appcode.FromContext(request.Context()), + UserId: auth.UserIDFromContext(request.Context()), + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + var rebate *vipDailyCoinRebateData + if resp.GetFound() && resp.GetRebate() != nil { + item := vipDailyCoinRebateFromProto(resp.GetRebate()) + rebate = &item + } + httpkit.WriteOK(writer, request, map[string]any{ + "found": resp.GetFound(), + "rebate": rebate, + "server_time_ms": resp.GetServerTimeMs(), + }) +} + +// listVIPCoinRebateStatuses 为系统通知恢复真实领取状态。rebate_ids 非空时 wallet 只返回 +// 当前用户拥有的指定记录;为空时提供受限历史分页,不能接受客户端 user_id/app_code。 +func (h *Handler) listVIPCoinRebateStatuses(writer http.ResponseWriter, request *http.Request) { + if h.walletClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + var body vipCoinRebateStatusesRequestBody + if !httpkit.Decode(writer, request, &body) { + return + } + if len(body.RebateIDs) > 100 || body.Page < 0 || body.PageSize < 0 || body.PageSize > 100 { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + resp, err := h.walletClient.ListMyVipDailyCoinRebateStatuses(request.Context(), &walletv1.ListMyVipDailyCoinRebateStatusesRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + AppCode: appcode.FromContext(request.Context()), + UserId: auth.UserIDFromContext(request.Context()), + Page: body.Page, + PageSize: body.PageSize, + RebateIds: body.RebateIDs, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + items := make([]vipDailyCoinRebateData, 0, len(resp.GetRebates())) + for _, rebate := range resp.GetRebates() { + items = append(items, vipDailyCoinRebateFromProto(rebate)) + } + httpkit.WriteOK(writer, request, map[string]any{ + "rebates": items, + "total": resp.GetTotal(), + "server_time_ms": resp.GetServerTimeMs(), + }) +} + +// claimVIPCoinRebate 只接收返现 ID 和业务幂等键;金额、等级、归属和 [UTC 0点,次日0点) +// 领取窗口全部由 wallet 锁内事实决定,避免客户端篡改金额或跨用户领取。 +func (h *Handler) claimVIPCoinRebate(writer http.ResponseWriter, request *http.Request) { + if h.walletClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + rebateID := strings.TrimSpace(request.PathValue("rebate_id")) + var body vipCoinRebateClaimRequestBody + if !httpkit.Decode(writer, request, &body) { + return + } + commandID := strings.TrimSpace(body.CommandID) + if commandID == "" { + commandID = strings.TrimSpace(body.CommandIDAlt) + } + if rebateID == "" || len(rebateID) > 96 || commandID == "" || len(commandID) > 128 { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + resp, err := h.walletClient.ClaimVipDailyCoinRebate(request.Context(), &walletv1.ClaimVipDailyCoinRebateRequest{ + CommandId: commandID, + AppCode: appcode.FromContext(request.Context()), + UserId: auth.UserIDFromContext(request.Context()), + RebateId: rebateID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + httpkit.WriteOK(writer, request, map[string]any{ + "rebate": vipDailyCoinRebateFromProto(resp.GetRebate()), + "transaction_id": resp.GetTransactionId(), + "coin_balance": balanceFromProto(resp.GetCoinBalance()), + "server_time_ms": resp.GetServerTimeMs(), + }) +} + +func vipDailyCoinRebateFromProto(rebate *walletv1.VipDailyCoinRebate) vipDailyCoinRebateData { + if rebate == nil { + return vipDailyCoinRebateData{} + } + return vipDailyCoinRebateData{ + RebateID: rebate.GetRebateId(), + UserID: httpkit.UserIDString(rebate.GetUserId()), + TaskDay: rebate.GetTaskDay(), + VIPLevel: rebate.GetVipLevel(), + VIPName: rebate.GetVipName(), + CoinAmount: rebate.GetCoinAmount(), + Status: rebate.GetStatus(), + AvailableAtMS: rebate.GetAvailableAtMs(), + ExpiresAtMS: rebate.GetExpiresAtMs(), + ConfigVersion: rebate.GetConfigVersion(), + ClaimedAtMS: rebate.GetClaimedAtMs(), + WalletTransactionID: rebate.GetWalletTransactionId(), + CreatedAtMS: rebate.GetCreatedAtMs(), + UpdatedAtMS: rebate.GetUpdatedAtMs(), + } +} diff --git a/services/gateway-service/internal/transport/http/walletapi/vip_settings_handler.go b/services/gateway-service/internal/transport/http/walletapi/vip_settings_handler.go new file mode 100644 index 00000000..dddbc381 --- /dev/null +++ b/services/gateway-service/internal/transport/http/walletapi/vip_settings_handler.go @@ -0,0 +1,94 @@ +package walletapi + +import ( + "net/http" + + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + "hyapp/services/gateway-service/internal/auth" + "hyapp/services/gateway-service/internal/transport/http/httpkit" +) + +type vipSettingsPatchBody struct { + // 指针保留 PATCH 的三态:未提交、显式 true、显式 false。 + RoomEntryNoticeEnabled *bool `json:"room_entry_notice_enabled"` + OnlineGlobalNoticeEnabled *bool `json:"online_global_notice_enabled"` +} + +// handleVIPSettings 在同一路径分发 GET/PATCH,避免向当前 ServeMux 重复注册相同 pattern。 +func (h *Handler) handleVIPSettings(writer http.ResponseWriter, request *http.Request) { + switch request.Method { + case http.MethodGet: + h.getVIPSettings(writer, request) + case http.MethodPatch: + h.patchVIPSettings(writer, request) + default: + httpkit.WriteError(writer, request, http.StatusMethodNotAllowed, httpkit.CodeInvalidArgument, "invalid argument") + } +} + +// getVIPSettings 复用 GetMyVip 的同一状态快照,不为 room/online 开关再发第二个 wallet RPC。 +func (h *Handler) getVIPSettings(writer http.ResponseWriter, request *http.Request) { + if h.walletClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + ctx := request.Context() + appCode := appcode.FromContext(ctx) + userID := auth.UserIDFromContext(ctx) + resp, err := h.walletClient.GetMyVip(ctx, &walletv1.GetMyVipRequest{ + RequestId: httpkit.RequestIDFromContext(ctx), AppCode: appCode, UserId: userID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + if resp == nil || resp.GetState() == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + settings := vipUserSettingsFromProto(resp.GetState().GetUserSettings()) + // 兼容滚动升级期间旧 wallet 未返回 settings 标识字段;默认布尔值仍由 converter 保持开启。 + if settings.AppCode == "" { + settings.AppCode = appCode + } + if settings.UserID == 0 { + settings.UserID = userID + } + httpkit.WriteOK(writer, request, map[string]any{ + "settings": settings, "evaluated_at_ms": resp.GetState().GetEvaluatedAtMs(), + }) +} + +// patchVIPSettings 只从鉴权上下文取 app/user,客户端不能替他人或跨 App 改设置。 +func (h *Handler) patchVIPSettings(writer http.ResponseWriter, request *http.Request) { + if h.walletClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + var body vipSettingsPatchBody + if !httpkit.Decode(writer, request, &body) { + return + } + if body.RoomEntryNoticeEnabled == nil && body.OnlineGlobalNoticeEnabled == nil { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + ctx := request.Context() + resp, err := h.walletClient.UpdateMyVipSettings(ctx, &walletv1.UpdateMyVipSettingsRequest{ + RequestId: httpkit.RequestIDFromContext(ctx), AppCode: appcode.FromContext(ctx), + UserId: auth.UserIDFromContext(ctx), RoomEntryNoticeEnabled: body.RoomEntryNoticeEnabled, + OnlineGlobalNoticeEnabled: body.OnlineGlobalNoticeEnabled, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + if resp == nil || resp.GetSettings() == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + httpkit.WriteOK(writer, request, map[string]any{ + "settings": vipUserSettingsFromProto(resp.GetSettings()), "server_time_ms": resp.GetServerTimeMs(), + }) +} diff --git a/services/lucky-gift-service/configs/config.docker.yaml b/services/lucky-gift-service/configs/config.docker.yaml index fea06f85..15c393e7 100644 --- a/services/lucky-gift-service/configs/config.docker.yaml +++ b/services/lucky-gift-service/configs/config.docker.yaml @@ -12,6 +12,21 @@ health_http_addr: ":13113" mysql_dsn: "hyapp:hyapp@tcp(mysql:3306)/hyapp_lucky_gift?parseTime=true&charset=utf8mb4&loc=UTC" mysql_auto_migrate: true wallet_service_addr: "wallet-service:13004" +rocketmq: + enabled: true + name_servers: ["rocketmq-namesrv:9876"] + name_server_domain: "" + access_key: "" + secret_key: "" + security_token: "" + namespace: "" + vip_channel: false + lucky_gift_outbox: + enabled: true + topic: "hyapp_lucky_gift_outbox" + producer_group: "hyapp-lucky-gift-outbox-producer" + send_timeout: "5s" + retry: 2 lucky_gift_worker: enabled: true worker_poll_interval: "1s" diff --git a/services/lucky-gift-service/configs/config.tencent.example.yaml b/services/lucky-gift-service/configs/config.tencent.example.yaml index a95eea5c..f503b36c 100644 --- a/services/lucky-gift-service/configs/config.tencent.example.yaml +++ b/services/lucky-gift-service/configs/config.tencent.example.yaml @@ -20,6 +20,23 @@ mysql_auto_migrate: false # 中奖入账仍由 wallet-service 负责,lucky-gift-service 只持有抽奖事实和待补偿 outbox。 wallet_service_addr: "wallet-service.internal:13004" +# 中奖事实只从 lucky_gift_outbox 发布;真实接入凭据必须由部署环境注入,不能写入可提交配置。 +rocketmq: + enabled: true + name_servers: [] + name_server_domain: "http://MQ_INST_REPLACE.mq-internet-access.mq-internet.tencenttdmq.com:80" + access_key: "TENCENT_ROCKETMQ_ACCESS_KEY" + secret_key: "TENCENT_ROCKETMQ_SECRET_KEY" + security_token: "" + namespace: "MQ_INST_REPLACE" + vip_channel: false + lucky_gift_outbox: + enabled: true + topic: "hyapp_lucky_gift_outbox" + producer_group: "hyapp-lucky-gift-outbox-producer" + send_timeout: "5s" + retry: 2 + lucky_gift_worker: enabled: true worker_poll_interval: "1s" diff --git a/services/lucky-gift-service/configs/config.yaml b/services/lucky-gift-service/configs/config.yaml index fd8a3f0d..dec2a17f 100644 --- a/services/lucky-gift-service/configs/config.yaml +++ b/services/lucky-gift-service/configs/config.yaml @@ -12,6 +12,21 @@ health_http_addr: ":13113" mysql_dsn: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_lucky_gift?parseTime=true&charset=utf8mb4&loc=UTC" mysql_auto_migrate: true wallet_service_addr: "127.0.0.1:13004" +rocketmq: + enabled: true + name_servers: ["127.0.0.1:19876"] + name_server_domain: "" + access_key: "" + secret_key: "" + security_token: "" + namespace: "" + vip_channel: false + lucky_gift_outbox: + enabled: true + topic: "hyapp_lucky_gift_outbox" + producer_group: "hyapp-lucky-gift-outbox-producer" + send_timeout: "5s" + retry: 2 lucky_gift_worker: enabled: true worker_poll_interval: "1s" diff --git a/services/lucky-gift-service/deploy/mysql/initdb/001_lucky_gift_service.sql b/services/lucky-gift-service/deploy/mysql/initdb/001_lucky_gift_service.sql index 3b8fe370..1967f15c 100644 --- a/services/lucky-gift-service/deploy/mysql/initdb/001_lucky_gift_service.sql +++ b/services/lucky-gift-service/deploy/mysql/initdb/001_lucky_gift_service.sql @@ -47,9 +47,32 @@ CREATE TABLE IF NOT EXISTS lucky_gift_outbox ( KEY idx_lucky_gift_outbox_status_retry (app_code, status, next_retry_at_ms), KEY idx_lucky_gift_outbox_lucky_retry (app_code, event_type, status, next_retry_at_ms, created_at_ms, outbox_id), KEY idx_lucky_gift_outbox_lucky_lock (app_code, event_type, status, lock_until_ms, created_at_ms, outbox_id), - KEY idx_lucky_gift_outbox_retention (app_code, status, updated_at_ms, outbox_id) + KEY idx_lucky_gift_outbox_retention (app_code, status, updated_at_ms, outbox_id), + KEY idx_lucky_gift_outbox_claim_retry (event_type, status, next_retry_at_ms, created_at_ms, outbox_id), + KEY idx_lucky_gift_outbox_claim_lock (event_type, status, lock_until_ms, created_at_ms, outbox_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物 owner outbox 表'; +-- Worker 的 outbox 抢占 SQL 不带 app_code(跨 App 统一补偿),老索引都以 app_code 开头无法命中, +-- 每次轮询(默认 1 秒 × 空轮询最多 6 条查询)都会全索引扫描并 filesort 整表。 +-- 这两个无 app_code 前缀的索引让空轮询变成零行索引定位,是线上 MySQL 基线 CPU 的主要治理点。 +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'lucky_gift_outbox' AND INDEX_NAME = 'idx_lucky_gift_outbox_claim_retry') = 0, + 'ALTER TABLE lucky_gift_outbox ADD INDEX idx_lucky_gift_outbox_claim_retry (event_type, status, next_retry_at_ms, created_at_ms, outbox_id), ALGORITHM=INPLACE, LOCK=NONE', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'lucky_gift_outbox' AND INDEX_NAME = 'idx_lucky_gift_outbox_claim_lock') = 0, + 'ALTER TABLE lucky_gift_outbox ADD INDEX idx_lucky_gift_outbox_claim_lock (event_type, status, lock_until_ms, created_at_ms, outbox_id), ALGORITHM=INPLACE, LOCK=NONE', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + CREATE TABLE IF NOT EXISTS lucky_gift_rule_versions ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', pool_id VARCHAR(96) NOT NULL COMMENT '幸运礼物奖池 ID', diff --git a/services/lucky-gift-service/internal/app/app.go b/services/lucky-gift-service/internal/app/app.go index 2f8a4911..f085bd17 100644 --- a/services/lucky-gift-service/internal/app/app.go +++ b/services/lucky-gift-service/internal/app/app.go @@ -14,10 +14,13 @@ import ( "hyapp/pkg/grpchealth" "hyapp/pkg/healthhttp" "hyapp/pkg/logx" + "hyapp/pkg/rocketmqx" serviceapp "hyapp/pkg/servicekit/app" servicegrpc "hyapp/pkg/servicekit/grpcserver" servicehealth "hyapp/pkg/servicekit/health" + servicemq "hyapp/pkg/servicekit/mq" "hyapp/services/lucky-gift-service/internal/config" + "hyapp/services/lucky-gift-service/internal/integration" luckygiftservice "hyapp/services/lucky-gift-service/internal/service/luckygift" mysqlstorage "hyapp/services/lucky-gift-service/internal/storage/mysql" grpcserver "hyapp/services/lucky-gift-service/internal/transport/grpc" @@ -26,16 +29,17 @@ import ( // App 装配 lucky-gift-service 的 owner 进程。 // 该进程是幸运礼物规则、抽奖、外部 App 接入和 outbox 的唯一写入者;其他服务只持有 gRPC client。 type App struct { - cfg config.Config - server *grpc.Server - listener net.Listener - health *grpchealth.ServingChecker - healthHTTP *healthhttp.Server - repository *mysqlstorage.Repository - walletConn *grpc.ClientConn - service *luckygiftservice.Service - workers *serviceapp.BackgroundGroup - closeOnce sync.Once + cfg config.Config + server *grpc.Server + listener net.Listener + health *grpchealth.ServingChecker + healthHTTP *healthhttp.Server + repository *mysqlstorage.Repository + walletConn *grpc.ClientConn + eventProducer *rocketmqx.Producer + service *luckygiftservice.Service + workers *serviceapp.BackgroundGroup + closeOnce sync.Once } func New(cfg config.Config) (*App, error) { @@ -58,15 +62,30 @@ func New(cfg config.Config) (*App, error) { _ = repository.Close() return nil, err } + var eventProducer *rocketmqx.Producer + var eventPublisher luckygiftservice.EventPublisher + if cfg.RocketMQ.LuckyGiftOutbox.Enabled { + eventProducer, err = rocketmqx.NewProducer(rocketMQProducerConfig(cfg.RocketMQ)) + if err != nil { + _ = walletConn.Close() + _ = repository.Close() + return nil, err + } + eventPublisher = integration.NewRocketMQEventPublisher(eventProducer, cfg.RocketMQ.LuckyGiftOutbox.Topic) + } listener, err := net.Listen("tcp", cfg.GRPCAddr) if err != nil { + servicemq.ShutdownProducers([]*rocketmqx.Producer{eventProducer}) _ = walletConn.Close() _ = repository.Close() return nil, err } server := servicegrpc.New("lucky-gift-service") - svc := luckygiftservice.New(repository, luckygiftservice.WithWallet(walletv1.NewWalletServiceClient(walletConn))) + svc := luckygiftservice.New(repository, + luckygiftservice.WithWallet(walletv1.NewWalletServiceClient(walletConn)), + luckygiftservice.WithEventPublisher(eventPublisher), + ) luckygiftv1.RegisterLuckyGiftServiceServer(server, grpcserver.NewLuckyGiftServer(svc)) luckygiftv1.RegisterAdminLuckyGiftServiceServer(server, grpcserver.NewAdminLuckyGiftServer(svc)) health, healthHTTP, err := servicehealth.New(server, servicehealth.Config{ @@ -80,21 +99,23 @@ func New(cfg config.Config) (*App, error) { }) if err != nil { _ = listener.Close() + servicemq.ShutdownProducers([]*rocketmqx.Producer{eventProducer}) _ = walletConn.Close() _ = repository.Close() return nil, err } return &App{ - cfg: cfg, - server: server, - listener: listener, - health: health, - healthHTTP: healthHTTP, - repository: repository, - walletConn: walletConn, - service: svc, - workers: serviceapp.NewBackground(context.Background()), + cfg: cfg, + server: server, + listener: listener, + health: health, + healthHTTP: healthHTTP, + repository: repository, + walletConn: walletConn, + eventProducer: eventProducer, + service: svc, + workers: serviceapp.NewBackground(context.Background()), }, nil } @@ -106,6 +127,10 @@ func (a *App) Run() error { } }() } + if err := servicemq.StartProducers([]*rocketmqx.Producer{a.eventProducer}); err != nil { + a.health.MarkStopped() + return err + } a.health.MarkServing() if a.cfg.LuckyGiftWorker.Enabled && a.service != nil { options := luckyGiftWorkerOptions(a.cfg.NodeID, a.cfg.LuckyGiftWorker) @@ -113,11 +138,22 @@ func (a *App) Run() error { a.service.RunWorker(ctx, options) }) } + if a.cfg.LuckyGiftWorker.Retention.Enabled && a.service != nil { + retention := a.cfg.LuckyGiftWorker.Retention + a.workers.Go(func(ctx context.Context) { + a.service.RunOutboxRetentionWorker(ctx, luckygiftservice.OutboxRetentionOptions{ + ScanInterval: retention.ScanInterval, + MaxAge: retention.MaxAge, + BatchSize: retention.BatchSize, + }) + }) + } err := a.server.Serve(a.listener) if a.workers != nil { a.workers.StopAndWait() } a.health.MarkStopped() + servicemq.ShutdownProducers([]*rocketmqx.Producer{a.eventProducer}) if errors.Is(err, grpc.ErrServerStopped) { return nil } @@ -141,12 +177,30 @@ func (a *App) Close() { if a.walletConn != nil { _ = a.walletConn.Close() } + servicemq.ShutdownProducers([]*rocketmqx.Producer{a.eventProducer}) if a.repository != nil { _ = a.repository.Close() } }) } +func rocketMQProducerConfig(cfg config.RocketMQConfig) rocketmqx.ProducerConfig { + return rocketmqx.ProducerConfig{ + EndpointConfig: rocketmqx.EndpointConfig{ + NameServers: cfg.NameServers, + NameServerDomain: cfg.NameServerDomain, + AccessKey: cfg.AccessKey, + SecretKey: cfg.SecretKey, + SecurityToken: cfg.SecurityToken, + Namespace: cfg.Namespace, + VIPChannel: cfg.VIPChannel, + }, + GroupName: cfg.LuckyGiftOutbox.ProducerGroup, + SendTimeout: cfg.LuckyGiftOutbox.SendTimeout, + Retry: cfg.LuckyGiftOutbox.Retry, + } +} + func luckyGiftWorkerOptions(nodeID string, cfg config.LuckyGiftWorkerConfig) luckygiftservice.WorkerOptions { return luckygiftservice.WorkerOptions{ WorkerID: nodeID, diff --git a/services/lucky-gift-service/internal/config/config.go b/services/lucky-gift-service/internal/config/config.go index a576b53f..3f8a8baf 100644 --- a/services/lucky-gift-service/internal/config/config.go +++ b/services/lucky-gift-service/internal/config/config.go @@ -1,6 +1,7 @@ package config import ( + "fmt" "strings" "time" @@ -20,11 +21,36 @@ type Config struct { MySQLAutoMigrate bool `yaml:"mysql_auto_migrate"` WalletServiceAddr string `yaml:"wallet_service_addr"` LuckyGiftWorker LuckyGiftWorkerConfig `yaml:"lucky_gift_worker"` - Log logx.Config `yaml:"log"` + // RocketMQ 只发布 lucky-gift-service 自有 outbox 事实;room/activity 不能直读幸运礼物数据库。 + RocketMQ RocketMQConfig `yaml:"rocketmq"` + Log logx.Config `yaml:"log"` +} + +// RocketMQConfig 保存幸运礼物 owner topic 的连接身份。 +type RocketMQConfig struct { + Enabled bool `yaml:"enabled"` + NameServers []string `yaml:"name_servers"` + NameServerDomain string `yaml:"name_server_domain"` + AccessKey string `yaml:"access_key"` + SecretKey string `yaml:"secret_key"` + SecurityToken string `yaml:"security_token"` + Namespace string `yaml:"namespace"` + VIPChannel bool `yaml:"vip_channel"` + LuckyGiftOutbox LuckyGiftOutboxMQConfig `yaml:"lucky_gift_outbox"` +} + +// LuckyGiftOutboxMQConfig 控制 granted 中奖事实发布;地址和凭据来自运行环境,业务开关在可运行配置中默认开启。 +type LuckyGiftOutboxMQConfig struct { + Enabled bool `yaml:"enabled"` + Topic string `yaml:"topic"` + ProducerGroup string `yaml:"producer_group"` + SendTimeout time.Duration `yaml:"send_timeout"` + Retry int `yaml:"retry"` } // LuckyGiftWorkerConfig 控制 draw outbox 补偿和统计快照刷新。 -// 外部 App 抽奖不走 HyApp wallet,但仍写 outbox 给审计/统计;内部房间抽奖中奖后由 worker 兜底补发钱包和 IM。 +// 外部 App 抽奖不走 HyApp wallet,但仍写 outbox 给审计/统计;内部房间抽奖中奖后由 worker 兜底返奖, +// 并在 granted 收敛后把 owner 事实可靠发布给 room/activity 的独立消费者。 type LuckyGiftWorkerConfig struct { Enabled bool `yaml:"enabled"` WorkerPollInterval time.Duration `yaml:"worker_poll_interval"` @@ -35,6 +61,21 @@ type LuckyGiftWorkerConfig struct { PublishTimeout time.Duration `yaml:"publish_timeout"` StatsRefreshInterval time.Duration `yaml:"stats_refresh_interval"` StatsBatchSize int `yaml:"stats_batch_size"` + // Retention 控制 delivered 历史事件的定期清理;它独立于 Enabled,临时关闭补偿投递不会停掉清理。 + Retention OutboxRetentionConfig `yaml:"retention"` +} + +// OutboxRetentionConfig 控制 lucky_gift_outbox 中 delivered 历史事件的小批量定期删除。 +// 只清理 delivered:pending/retryable/delivering 仍在投递生命周期内,failed 留给人工补偿。 +// 抽奖事实和审计都在 lucky_draw_records;下游只读 RocketMQ owner 事实,不直读本表,因此 delivered 行可定期清理。 +type OutboxRetentionConfig struct { + Enabled bool `yaml:"enabled"` + // MaxAge 是 delivered 事件按 updated_at_ms 计算的保留时长,早于 now-MaxAge 的才会删除。 + MaxAge time.Duration `yaml:"max_age"` + // ScanInterval 是清空一轮可删事件后到下一轮扫描的间隔。 + ScanInterval time.Duration `yaml:"scan_interval"` + // BatchSize 是单条 DELETE 的行数上限,小批量避免大事务长时间持有行锁。 + BatchSize int `yaml:"batch_size"` } func Default() Config { @@ -57,7 +98,15 @@ func Default() Config { PublishTimeout: 5 * time.Second, StatsRefreshInterval: 10 * time.Minute, StatsBatchSize: 5000, + Retention: OutboxRetentionConfig{ + Enabled: true, + // 线上 delivered 约 9 万行/天且无下游读方;7 天足够排障,30 天会让表膨胀到近 300 万行。 + MaxAge: 7 * 24 * time.Hour, + ScanInterval: 5 * time.Minute, + BatchSize: 500, + }, }, + RocketMQ: defaultRocketMQConfig(), Log: logx.Config{ Level: "info", Format: "json", @@ -66,6 +115,19 @@ func Default() Config { } } +func defaultRocketMQConfig() RocketMQConfig { + return RocketMQConfig{ + Enabled: false, + LuckyGiftOutbox: LuckyGiftOutboxMQConfig{ + Enabled: false, + Topic: "hyapp_lucky_gift_outbox", + ProducerGroup: "hyapp-lucky-gift-outbox-producer", + SendTimeout: 5 * time.Second, + Retry: 2, + }, + } +} + func Load(path string) (Config, error) { cfg := Default() if strings.TrimSpace(path) == "" { @@ -122,5 +184,68 @@ func Load(path string) (Config, error) { if cfg.LuckyGiftWorker.StatsBatchSize <= 0 { cfg.LuckyGiftWorker.StatsBatchSize = 5000 } + defaults := Default().LuckyGiftWorker.Retention + if cfg.LuckyGiftWorker.Retention.MaxAge <= 0 { + cfg.LuckyGiftWorker.Retention.MaxAge = defaults.MaxAge + } + if cfg.LuckyGiftWorker.Retention.ScanInterval <= 0 { + cfg.LuckyGiftWorker.Retention.ScanInterval = defaults.ScanInterval + } + if cfg.LuckyGiftWorker.Retention.BatchSize <= 0 { + cfg.LuckyGiftWorker.Retention.BatchSize = defaults.BatchSize + } + if cfg.LuckyGiftWorker.Retention.Enabled && cfg.LuckyGiftWorker.Retention.MaxAge < 24*time.Hour { + // 保留窗口低于一天基本可以断定是把单位写错(比如 30 写成 30ns/30s),宁可拒绝启动。 + return Config{}, fmt.Errorf("lucky_gift_worker retention max_age %s is below the 24h safety floor", cfg.LuckyGiftWorker.Retention.MaxAge) + } + rocketMQ, err := normalizeRocketMQConfig(cfg.RocketMQ) + if err != nil { + return Config{}, err + } + cfg.RocketMQ = rocketMQ return cfg, nil } + +func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) { + defaults := defaultRocketMQConfig() + cfg.NameServers = normalizeStringSlice(cfg.NameServers) + cfg.NameServerDomain = strings.TrimSpace(cfg.NameServerDomain) + cfg.AccessKey = strings.TrimSpace(cfg.AccessKey) + cfg.SecretKey = strings.TrimSpace(cfg.SecretKey) + cfg.SecurityToken = strings.TrimSpace(cfg.SecurityToken) + cfg.Namespace = strings.TrimSpace(cfg.Namespace) + if cfg.LuckyGiftOutbox.Topic = strings.TrimSpace(cfg.LuckyGiftOutbox.Topic); cfg.LuckyGiftOutbox.Topic == "" { + cfg.LuckyGiftOutbox.Topic = defaults.LuckyGiftOutbox.Topic + } + if cfg.LuckyGiftOutbox.ProducerGroup = strings.TrimSpace(cfg.LuckyGiftOutbox.ProducerGroup); cfg.LuckyGiftOutbox.ProducerGroup == "" { + cfg.LuckyGiftOutbox.ProducerGroup = defaults.LuckyGiftOutbox.ProducerGroup + } + if cfg.LuckyGiftOutbox.SendTimeout <= 0 { + cfg.LuckyGiftOutbox.SendTimeout = defaults.LuckyGiftOutbox.SendTimeout + } + if cfg.LuckyGiftOutbox.Retry < 0 { + cfg.LuckyGiftOutbox.Retry = defaults.LuckyGiftOutbox.Retry + } + if cfg.LuckyGiftOutbox.Enabled { + cfg.Enabled = true + } + if cfg.Enabled { + if len(cfg.NameServers) == 0 && cfg.NameServerDomain == "" { + return RocketMQConfig{}, fmt.Errorf("rocketmq name_servers or name_server_domain is required") + } + if (cfg.AccessKey == "") != (cfg.SecretKey == "") { + return RocketMQConfig{}, fmt.Errorf("rocketmq access_key and secret_key must be configured together") + } + } + return cfg, nil +} + +func normalizeStringSlice(input []string) []string { + out := make([]string, 0, len(input)) + for _, value := range input { + if value = strings.TrimSpace(value); value != "" { + out = append(out, value) + } + } + return out +} diff --git a/services/lucky-gift-service/internal/config/config_test.go b/services/lucky-gift-service/internal/config/config_test.go new file mode 100644 index 00000000..f1bac323 --- /dev/null +++ b/services/lucky-gift-service/internal/config/config_test.go @@ -0,0 +1,86 @@ +package config + +import ( + "os" + "path/filepath" + "strings" + "testing" + "time" +) + +func TestLoadDefaultsEnableOutboxRetention(t *testing.T) { + cfg, err := Load("") + if err != nil { + t.Fatalf("Load(empty) failed: %v", err) + } + retention := cfg.LuckyGiftWorker.Retention + if !retention.Enabled { + t.Fatal("retention should be enabled by default") + } + if retention.MaxAge != 7*24*time.Hour { + t.Fatalf("retention.MaxAge = %s, want 168h", retention.MaxAge) + } + if retention.ScanInterval != 5*time.Minute { + t.Fatalf("retention.ScanInterval = %s, want 5m", retention.ScanInterval) + } + if retention.BatchSize != 500 { + t.Fatalf("retention.BatchSize = %d, want 500", retention.BatchSize) + } +} + +func TestLoadRunnableConfigsPublishLuckyGiftOwnerOutbox(t *testing.T) { + for _, path := range []string{"../../configs/config.yaml", "../../configs/config.docker.yaml", "../../configs/config.tencent.example.yaml"} { + cfg, err := Load(path) + if err != nil { + t.Fatalf("Load(%s) failed: %v", path, err) + } + if !cfg.RocketMQ.Enabled || !cfg.RocketMQ.LuckyGiftOutbox.Enabled { + t.Fatalf("%s must enable lucky owner outbox: %+v", path, cfg.RocketMQ) + } + if cfg.RocketMQ.LuckyGiftOutbox.Topic != "hyapp_lucky_gift_outbox" || cfg.RocketMQ.LuckyGiftOutbox.ProducerGroup == "" { + t.Fatalf("%s has incomplete lucky owner routing: %+v", path, cfg.RocketMQ.LuckyGiftOutbox) + } + } +} + +func TestLoadLocalUsesComposeRocketMQHostPort(t *testing.T) { + cfg, err := Load("../../configs/config.yaml") + if err != nil { + t.Fatalf("load local config: %v", err) + } + if len(cfg.RocketMQ.NameServers) != 1 || cfg.RocketMQ.NameServers[0] != "127.0.0.1:19876" { + t.Fatalf("local config must use compose host-mapped nameserver port: %+v", cfg.RocketMQ.NameServers) + } +} + +func TestLoadRejectsEnabledRocketMQWithoutEndpoint(t *testing.T) { + path := filepath.Join(t.TempDir(), "config.yaml") + content := ` +rocketmq: + enabled: true + lucky_gift_outbox: + enabled: true +` + if err := os.WriteFile(path, []byte(content), 0o600); err != nil { + t.Fatalf("write config: %v", err) + } + if _, err := Load(path); err == nil || !strings.Contains(err.Error(), "name_servers") { + t.Fatalf("Load should reject missing RocketMQ endpoint, got err=%v", err) + } +} + +func TestLoadRejectsRetentionMaxAgeBelowSafetyFloor(t *testing.T) { + path := filepath.Join(t.TempDir(), "config.yaml") + content := ` +lucky_gift_worker: + retention: + enabled: true + max_age: 30s +` + if err := os.WriteFile(path, []byte(content), 0o600); err != nil { + t.Fatalf("write config: %v", err) + } + if _, err := Load(path); err == nil || !strings.Contains(err.Error(), "safety floor") { + t.Fatalf("Load should reject max_age below 24h, got err=%v", err) + } +} diff --git a/services/lucky-gift-service/internal/domain/luckygift/lucky_gift.go b/services/lucky-gift-service/internal/domain/luckygift/lucky_gift.go index 248506b0..619e3bbe 100644 --- a/services/lucky-gift-service/internal/domain/luckygift/lucky_gift.go +++ b/services/lucky-gift-service/internal/domain/luckygift/lucky_gift.go @@ -117,6 +117,11 @@ type DrawCommand struct { PaidAtMS int64 VisibleRegionID int64 CountryID int64 + // Sender* 是扣费时刻的公开展示快照;owner outbox 必须携带快照,避免下游为一条飘屏同步反查 user-service。 + SenderName string + SenderAvatar string + SenderDisplayUserID string + SenderPrettyDisplayUserID string } type CheckResult struct { @@ -155,6 +160,16 @@ type DrawResult struct { CreatedAtMS int64 WalletTransactionID string CoinBalanceAfter int64 + // Hits 只保留 gift_count 批量中的中奖子抽,避免为了连击表现把所有 999 条审计记录塞进同步响应。 + Hits []DrawHit +} + +type DrawHit struct { + GiftIndex int32 + DrawID string + SelectedTierID string + MultiplierPPM int64 + RewardCoins int64 } type DrawSummary struct { @@ -254,4 +269,6 @@ type DrawOutbox struct { type DrawRewardState struct { AllGranted bool WalletTransactionID string + // RewardGrantedAtMS 取 granted draw 的稳定 updated_at_ms;owner MQ 重投必须保持同一事实正文不漂移。 + RewardGrantedAtMS int64 } diff --git a/services/lucky-gift-service/internal/integration/rocketmq_outbox.go b/services/lucky-gift-service/internal/integration/rocketmq_outbox.go new file mode 100644 index 00000000..b8a4539a --- /dev/null +++ b/services/lucky-gift-service/internal/integration/rocketmq_outbox.go @@ -0,0 +1,55 @@ +package integration + +import ( + "context" + "errors" + "strings" + + luckygifteventsv1 "hyapp.local/api/proto/events/luckygift/v1" + "hyapp/pkg/luckygiftmq" + "hyapp/pkg/rocketmqx" +) + +// RocketMQEventPublisher 把已经完成钱包入账的幸运礼物事实发布到 owner topic。 +// 它不读取 outbox 表;claim、重试和 delivered 状态仍由 lucky-gift-service 的 settlement worker 独占。 +type RocketMQEventPublisher struct { + producer *rocketmqx.Producer + topic string +} + +func NewRocketMQEventPublisher(producer *rocketmqx.Producer, topic string) *RocketMQEventPublisher { + return &RocketMQEventPublisher{producer: producer, topic: strings.TrimSpace(topic)} +} + +// PublishLuckyGiftEvent 只有在 Broker ACK 后返回成功;worker 随后才能把 owner outbox 标为 delivered。 +func (p *RocketMQEventPublisher) PublishLuckyGiftEvent(ctx context.Context, envelope *luckygifteventsv1.EventEnvelope) error { + if p == nil || p.producer == nil { + return errors.New("lucky gift RocketMQ producer is not configured") + } + message, err := luckyGiftRocketMessage(p.topic, envelope) + if err != nil { + return err + } + return p.producer.SendSync(ctx, message) +} + +func luckyGiftRocketMessage(topic string, envelope *luckygifteventsv1.EventEnvelope) (rocketmqx.Message, error) { + topic = strings.TrimSpace(topic) + if topic == "" { + return rocketmqx.Message{}, errors.New("lucky gift outbox topic is required") + } + body, err := luckygiftmq.EncodeLuckyGiftOutboxMessage(envelope) + if err != nil { + return rocketmqx.Message{}, err + } + tag, err := luckygiftmq.EventTypeTag(envelope.GetEventType()) + if err != nil { + return rocketmqx.Message{}, err + } + return rocketmqx.Message{ + Topic: topic, + Tag: tag, + Keys: []string{envelope.GetEventId(), envelope.GetDrawId()}, + Body: body, + }, nil +} diff --git a/services/lucky-gift-service/internal/integration/rocketmq_outbox_test.go b/services/lucky-gift-service/internal/integration/rocketmq_outbox_test.go new file mode 100644 index 00000000..909365a8 --- /dev/null +++ b/services/lucky-gift-service/internal/integration/rocketmq_outbox_test.go @@ -0,0 +1,56 @@ +package integration + +import ( + "testing" + + "google.golang.org/protobuf/proto" + luckygifteventsv1 "hyapp.local/api/proto/events/luckygift/v1" + "hyapp/pkg/luckygiftmq" +) + +func TestLuckyGiftRocketMessageUsesOwnerEventIdentity(t *testing.T) { + fact, err := proto.Marshal(&luckygifteventsv1.LuckyGiftDrawn{ + DrawId: "draw-10x", + CommandId: "cmd-10x", + RoomId: "room-1", + GiftId: "43", + GiftCount: 1, + SenderUserId: 123456, + TargetUserId: 654321, + CoinSpent: 100, + MultiplierPpm: luckygiftmq.LuckyGiftDisplayMinMultiplierPPM, + EffectiveRewardCoins: 1000, + RewardStatus: "granted", + DrawCreatedAtMs: 1783940398274, + RewardGrantedAtMs: 1783940398374, + }) + if err != nil { + t.Fatalf("marshal lucky fact: %v", err) + } + envelope := &luckygifteventsv1.EventEnvelope{ + EventId: "lucky_gift_drawn:draw-10x", + EventType: luckygiftmq.EventTypeLuckyGiftDrawn, + AppCode: "lalu", + DrawId: "draw-10x", + OccurredAtMs: 1783940398274, + Body: fact, + } + + message, err := luckyGiftRocketMessage("hyapp_lucky_gift_outbox", envelope) + if err != nil { + t.Fatalf("build RocketMQ message: %v", err) + } + if message.Topic != "hyapp_lucky_gift_outbox" || message.Tag != luckygiftmq.EventTypeLuckyGiftDrawn { + t.Fatalf("unexpected routing: topic=%q tag=%q", message.Topic, message.Tag) + } + if len(message.Keys) != 2 || message.Keys[0] != envelope.GetEventId() || message.Keys[1] != envelope.GetDrawId() { + t.Fatalf("unexpected keys: %v", message.Keys) + } + decoded, _, err := luckygiftmq.DecodeLuckyGiftOutboxMessage(message.Body) + if err != nil { + t.Fatalf("decode RocketMQ body: %v", err) + } + if decoded.GetEventId() != envelope.GetEventId() || decoded.GetOccurredAtMs() != envelope.GetOccurredAtMs() { + t.Fatalf("decoded envelope mismatch: %+v", decoded) + } +} diff --git a/services/lucky-gift-service/internal/service/luckygift/gift_limits_test.go b/services/lucky-gift-service/internal/service/luckygift/gift_limits_test.go new file mode 100644 index 00000000..31b078b9 --- /dev/null +++ b/services/lucky-gift-service/internal/service/luckygift/gift_limits_test.go @@ -0,0 +1,71 @@ +package luckygift + +import ( + "context" + "fmt" + "strings" + "testing" + + "hyapp/pkg/xerr" + domain "hyapp/services/lucky-gift-service/internal/domain/luckygift" +) + +type batchLimitRepository struct { + Repository + batchCalls int +} + +func (r *batchLimitRepository) ExecuteLuckyGiftDrawBatch(_ context.Context, cmds []domain.DrawCommand, _ int64) ([]domain.DrawResult, error) { + r.batchCalls++ + return make([]domain.DrawResult, len(cmds)), nil +} + +func TestNormalizeDrawCommandRejectsOversizedInternalGiftWork(t *testing.T) { + base := domain.DrawCommand{ + CommandID: "cmd-lucky", + UserID: 1, + DeviceID: "device-1", + RoomID: "room-1", + AnchorID: "2", + GiftID: "rose", + GiftCount: 1, + CoinSpent: 10, + TargetUserID: 2, + } + svc := &Service{} + oversizedCount := base + oversizedCount.GiftCount = 1_000 + if _, err := svc.normalizeDrawCommand(oversizedCount); err == nil { + t.Fatal("gift_count above 999 was accepted") + } + oversizedID := base + oversizedID.CommandID = strings.Repeat("x", 129) + if _, err := svc.normalizeDrawCommand(oversizedID); err == nil { + t.Fatal("command_id above 128 bytes was accepted") + } +} + +func TestDrawBatchRejectsAggregateWorkAcrossTargets(t *testing.T) { + repository := &batchLimitRepository{} + svc := New(repository) + commands := make([]domain.DrawCommand, 0, 6) + for index := 0; index < 6; index++ { + commands = append(commands, domain.DrawCommand{ + CommandID: fmt.Sprintf("cmd-lucky-target-%d", index), UserID: 1, TargetUserID: int64(index + 10), + DeviceID: "device-1", RoomID: "room-1", AnchorID: "2", GiftID: "rose", + GiftCount: 999, CoinSpent: 999, + }) + } + if _, err := svc.DrawBatch(context.Background(), commands); !xerr.IsCode(err, xerr.InvalidArgument) { + t.Fatalf("5994 aggregate draw units should be rejected, got %v", err) + } + if repository.batchCalls != 0 { + t.Fatalf("oversized batch reached repository %d times", repository.batchCalls) + } + if _, err := svc.DrawBatch(context.Background(), commands[:5]); err != nil { + t.Fatalf("4995 aggregate draw units should be accepted: %v", err) + } + if repository.batchCalls != 1 { + t.Fatalf("accepted batch repository calls=%d, want 1", repository.batchCalls) + } +} diff --git a/services/lucky-gift-service/internal/service/luckygift/lucky_gift_event_test.go b/services/lucky-gift-service/internal/service/luckygift/lucky_gift_event_test.go new file mode 100644 index 00000000..05ef9e2f --- /dev/null +++ b/services/lucky-gift-service/internal/service/luckygift/lucky_gift_event_test.go @@ -0,0 +1,174 @@ +package luckygift + +import ( + "context" + "errors" + "testing" + "time" + + "google.golang.org/protobuf/proto" + luckygifteventsv1 "hyapp.local/api/proto/events/luckygift/v1" + "hyapp/pkg/luckygiftmq" + domain "hyapp/services/lucky-gift-service/internal/domain/luckygift" +) + +type captureLuckyGiftEventPublisher struct { + envelope *luckygifteventsv1.EventEnvelope + err error +} + +func (p *captureLuckyGiftEventPublisher) PublishLuckyGiftEvent(_ context.Context, envelope *luckygifteventsv1.EventEnvelope) error { + p.envelope = proto.Clone(envelope).(*luckygifteventsv1.EventEnvelope) + return p.err +} + +// settlementEventRepository embeds the full port only to keep this test focused on the methods used by the granted branch. +// Any unexpected repository call still panics through the nil embedded interface and exposes a widened code path immediately. +type settlementEventRepository struct { + Repository + rewardState domain.DrawRewardState + deliveredOutbox string + retryableOutbox string + failedOutbox string + failedDrawUpdate bool +} + +func (r *settlementEventRepository) GetLuckyGiftDrawRewardState(context.Context, string, []string) (domain.DrawRewardState, error) { + return r.rewardState, nil +} + +func (r *settlementEventRepository) MarkLuckyGiftOutboxDelivered(_ context.Context, event domain.DrawOutbox, _ int64) error { + r.deliveredOutbox = event.OutboxID + return nil +} + +func (r *settlementEventRepository) MarkLuckyGiftOutboxRetryable(_ context.Context, event domain.DrawOutbox, _ int, _ int64, _ string, _ int64) error { + r.retryableOutbox = event.OutboxID + return nil +} + +func (r *settlementEventRepository) MarkLuckyGiftOutboxFailed(_ context.Context, event domain.DrawOutbox, _ int, _ string, _ int64) error { + r.failedOutbox = event.OutboxID + return nil +} + +func (r *settlementEventRepository) MarkLuckyGiftDrawsFailed(context.Context, string, []string, string, int64) error { + r.failedDrawUpdate = true + return nil +} + +func TestPublishLuckyGiftDrawnFactCarriesGrantedSnapshot(t *testing.T) { + publisher := &captureLuckyGiftEventPublisher{} + service := New(nil, WithEventPublisher(publisher)) + service.SetClock(func() time.Time { return time.UnixMilli(1783940398374).UTC() }) + payload := testLuckyGiftDrawnPayload() + + if err := service.publishLuckyGiftDrawnFact(context.Background(), payload, luckyGiftRewardReceipt{WalletTransactionID: "wallet-tx-1", RewardGrantedAtMS: 1783940398320}); err != nil { + t.Fatalf("publish lucky gift fact: %v", err) + } + if publisher.envelope == nil || publisher.envelope.GetEventType() != luckygiftmq.EventTypeLuckyGiftDrawn || publisher.envelope.GetOccurredAtMs() != payload.CreatedAtMS { + t.Fatalf("unexpected owner envelope: %+v", publisher.envelope) + } + var fact luckygifteventsv1.LuckyGiftDrawn + if err := proto.Unmarshal(publisher.envelope.GetBody(), &fact); err != nil { + t.Fatalf("decode owner fact: %v", err) + } + if fact.GetRewardStatus() != domain.StatusGranted || fact.GetWalletTransactionId() != "wallet-tx-1" || fact.GetMultiplierPpm() != luckygiftmq.LuckyGiftDisplayMinMultiplierPPM || fact.GetSenderName() != "Lucky Sender" || fact.GetRewardGrantedAtMs() != 1783940398320 { + t.Fatalf("unexpected owner fact: %+v", &fact) + } +} + +func TestPublishLuckyGiftDrawnFactIsStableAcrossBrokerRetries(t *testing.T) { + publisher := &captureLuckyGiftEventPublisher{} + service := New(nil, WithEventPublisher(publisher)) + payload := testLuckyGiftDrawnPayload() + receipt := luckyGiftRewardReceipt{WalletTransactionID: "wallet-tx-stable", RewardGrantedAtMS: 1783940398320} + + service.SetClock(func() time.Time { return time.UnixMilli(1783940400000).UTC() }) + if err := service.publishLuckyGiftDrawnFact(context.Background(), payload, receipt); err != nil { + t.Fatalf("first publish: %v", err) + } + first := proto.Clone(publisher.envelope).(*luckygifteventsv1.EventEnvelope) + + service.SetClock(func() time.Time { return time.UnixMilli(1783940500000).UTC() }) + if err := service.publishLuckyGiftDrawnFact(context.Background(), payload, receipt); err != nil { + t.Fatalf("retry publish: %v", err) + } + if !proto.Equal(first, publisher.envelope) { + t.Fatalf("same event_id changed across retry:\nfirst=%+v\nretry=%+v", first, publisher.envelope) + } +} + +func TestGrantedDrawKeepsSettlementRetryableUntilBrokerAcceptsFact(t *testing.T) { + repository := &settlementEventRepository{rewardState: domain.DrawRewardState{AllGranted: true, WalletTransactionID: "wallet-tx-existing", RewardGrantedAtMS: 1783940398320}} + publisher := &captureLuckyGiftEventPublisher{err: errors.New("broker unavailable")} + service := New(repository, WithEventPublisher(publisher)) + service.SetClock(func() time.Time { return time.UnixMilli(1783940398374).UTC() }) + event := domain.DrawOutbox{AppCode: "lalu", OutboxID: "lucky_reward_draw-10x", EventType: domain.EventTypeLuckyGiftRewardSettlement} + + err := service.processRewardSettlementOutbox(context.Background(), event, testLuckyGiftDrawnPayload(), WorkerOptions{MaxRetry: 8}) + if err != nil { + t.Fatalf("retry transition should be persisted without returning a second error: %v", err) + } + if repository.retryableOutbox != event.OutboxID || repository.deliveredOutbox != "" || repository.failedOutbox != "" { + t.Fatalf("unexpected outbox transition: retryable=%q delivered=%q failed=%q", repository.retryableOutbox, repository.deliveredOutbox, repository.failedOutbox) + } + if repository.failedDrawUpdate { + t.Fatal("broker failure must never overwrite an already granted draw") + } + if publisher.envelope == nil { + t.Fatal("granted retry must still publish the owner fact") + } +} + +func TestGrantedDrawBrokerRetryExhaustionNeverMarksCreditedDrawFailed(t *testing.T) { + repository := &settlementEventRepository{rewardState: domain.DrawRewardState{AllGranted: true, WalletTransactionID: "wallet-tx-existing", RewardGrantedAtMS: 1783940398320}} + publisher := &captureLuckyGiftEventPublisher{err: errors.New("broker outage exceeded realtime retry window")} + service := New(repository, WithEventPublisher(publisher)) + service.SetClock(func() time.Time { return time.UnixMilli(1783940398374).UTC() }) + event := domain.DrawOutbox{ + AppCode: "lalu", + OutboxID: "lucky_reward_draw-10x", + EventType: domain.EventTypeLuckyGiftRewardSettlement, + PayloadJSON: `{"draw_id":"draw-10x"}`, + RetryCount: 7, + } + + if err := service.processRewardSettlementOutbox(context.Background(), event, testLuckyGiftDrawnPayload(), WorkerOptions{MaxRetry: 8}); err != nil { + t.Fatalf("terminal outbox transition: %v", err) + } + if repository.failedOutbox != event.OutboxID || repository.retryableOutbox != "" || repository.deliveredOutbox != "" { + t.Fatalf("unexpected terminal transition: failed=%q retryable=%q delivered=%q", repository.failedOutbox, repository.retryableOutbox, repository.deliveredOutbox) + } + if repository.failedDrawUpdate { + t.Fatal("credited draw must remain granted when realtime fact exhausts retries") + } +} + +func testLuckyGiftDrawnPayload() luckyGiftDrawnPayload { + return luckyGiftDrawnPayload{ + EventType: "lucky_gift_drawn", + EventID: "lucky_gift_drawn:draw-10x", + AppCode: "lalu", + DrawID: "draw-10x", + DrawIDs: []string{"draw-10x"}, + CommandID: "cmd-10x", + PoolID: "super_lucky", + RoomID: "room-1", + GiftID: "43", + GiftCount: 1, + UserID: 123456, + SenderUserID: 123456, + TargetUserID: 654321, + SenderName: "Lucky Sender", + SenderAvatar: "https://cdn.example/sender.png", + SenderDisplayUserID: "123456", + VisibleRegionID: 1, + CountryID: 86, + CoinSpent: 100, + MultiplierPPM: luckygiftmq.LuckyGiftDisplayMinMultiplierPPM, + BaseRewardCoins: 1000, + EffectiveRewardCoins: 1000, + CreatedAtMS: 1783940398274, + } +} diff --git a/services/lucky-gift-service/internal/service/luckygift/outbox_retention.go b/services/lucky-gift-service/internal/service/luckygift/outbox_retention.go new file mode 100644 index 00000000..a6eb1c0b --- /dev/null +++ b/services/lucky-gift-service/internal/service/luckygift/outbox_retention.go @@ -0,0 +1,111 @@ +package luckygift + +import ( + "context" + "log/slog" + "time" + + "hyapp/pkg/logx" +) + +// outboxRetentionBatchPause 是同一轮内两批 DELETE 之间的让路间隔,避免清理积压时长时间占用行锁和 IO。 +const outboxRetentionBatchPause = 100 * time.Millisecond + +// OutboxRetentionOptions 控制 lucky_gift_outbox 中 delivered 历史事件的定期清理循环。 +type OutboxRetentionOptions struct { + // ScanInterval 是清空一轮可删事件后到下一轮扫描的间隔;非正数会被归一到安全默认值。 + ScanInterval time.Duration + // MaxAge 是 delivered 事件按 updated_at_ms 计算的保留时长。 + MaxAge time.Duration + // BatchSize 是单条 DELETE 的行数上限。 + BatchSize int +} + +func defaultOutboxRetentionOptions() OutboxRetentionOptions { + // 默认值和 config.Default 的 retention 保持一致,防止手动构造绕过配置归一化后误删新事件。 + // 线上 delivered 约 9 万行/天且无下游读方,7 天窗口在可排障和表体积之间取平衡。 + return OutboxRetentionOptions{ + ScanInterval: 5 * time.Minute, + MaxAge: 7 * 24 * time.Hour, + BatchSize: 500, + } +} + +func normalizeOutboxRetentionOptions(options OutboxRetentionOptions) OutboxRetentionOptions { + defaults := defaultOutboxRetentionOptions() + if options.ScanInterval <= 0 { + options.ScanInterval = defaults.ScanInterval + } + if options.MaxAge <= 0 { + options.MaxAge = defaults.MaxAge + } + if options.BatchSize <= 0 { + options.BatchSize = defaults.BatchSize + } + return options +} + +// RunOutboxRetentionWorker 周期性删除 delivered 且超过保留窗口的 lucky_gift_outbox 历史事件。 +// 它只做数据保留治理,不参与投递;多节点同时运行时 DELETE 靠 MySQL 行锁串行,语义幂等。 +func (s *Service) RunOutboxRetentionWorker(ctx context.Context, options OutboxRetentionOptions) { + if s == nil || s.repository == nil { + return + } + options = normalizeOutboxRetentionOptions(options) + + // 启动先执行一轮,让积压清理不用等第一个 scan interval。 + s.runOutboxRetentionRound(ctx, options) + + ticker := time.NewTicker(options.ScanInterval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + s.runOutboxRetentionRound(ctx, options) + } + } +} + +func (s *Service) runOutboxRetentionRound(ctx context.Context, options OutboxRetentionOptions) { + if _, err := s.ProcessOutboxRetention(ctx, options); err != nil && ctx.Err() == nil { + // 单轮失败只影响清理进度,下一轮扫描会继续;不升级为 Error 避免和投递故障混在告警里。 + logx.Warn(ctx, "lucky_gift_outbox_retention_failed", slog.String("error", err.Error())) + } +} + +// ProcessOutboxRetention 执行一轮清理:按批删除到没有可删事件为止,返回本轮总删除行数。 +func (s *Service) ProcessOutboxRetention(ctx context.Context, options OutboxRetentionOptions) (int64, error) { + if err := s.requireRepository(); err != nil { + return 0, err + } + options = normalizeOutboxRetentionOptions(options) + cutoffMS := s.now().UTC().Add(-options.MaxAge).UnixMilli() + + var total int64 + for { + deleted, err := s.repository.DeleteDeliveredLuckyGiftOutboxBefore(ctx, cutoffMS, options.BatchSize) + total += deleted + if err != nil { + return total, err + } + if deleted < int64(options.BatchSize) { + break + } + // 整批删满说明还有积压,稍作停顿继续,直到删空;退出信号优先于继续清理。 + select { + case <-ctx.Done(): + return total, ctx.Err() + case <-time.After(outboxRetentionBatchPause): + } + } + + if total > 0 { + logx.Info(ctx, "lucky_gift_outbox_retention_purged", + slog.Int64("rows_deleted", total), + slog.Int64("cutoff_ms", cutoffMS), + ) + } + return total, nil +} diff --git a/services/lucky-gift-service/internal/service/luckygift/outbox_retention_test.go b/services/lucky-gift-service/internal/service/luckygift/outbox_retention_test.go new file mode 100644 index 00000000..60fedd1e --- /dev/null +++ b/services/lucky-gift-service/internal/service/luckygift/outbox_retention_test.go @@ -0,0 +1,23 @@ +package luckygift + +import ( + "testing" + "time" +) + +func TestNormalizeOutboxRetentionOptionsFillsSafeDefaults(t *testing.T) { + got := normalizeOutboxRetentionOptions(OutboxRetentionOptions{}) + want := defaultOutboxRetentionOptions() + if got != want { + t.Fatalf("normalizeOutboxRetentionOptions(zero) = %+v, want %+v", got, want) + } + + custom := OutboxRetentionOptions{ + ScanInterval: time.Minute, + MaxAge: 7 * 24 * time.Hour, + BatchSize: 100, + } + if got := normalizeOutboxRetentionOptions(custom); got != custom { + t.Fatalf("normalizeOutboxRetentionOptions(custom) = %+v, want unchanged %+v", got, custom) + } +} diff --git a/services/lucky-gift-service/internal/service/luckygift/service.go b/services/lucky-gift-service/internal/service/luckygift/service.go index 937de7c1..bd47d698 100644 --- a/services/lucky-gift-service/internal/service/luckygift/service.go +++ b/services/lucky-gift-service/internal/service/luckygift/service.go @@ -12,12 +12,16 @@ import ( "time" "hyapp/pkg/appcode" + "hyapp/pkg/giftlimits" "hyapp/pkg/logx" + "hyapp/pkg/luckygiftmq" "hyapp/pkg/tencentim" "hyapp/pkg/xerr" domain "hyapp/services/lucky-gift-service/internal/domain/luckygift" "google.golang.org/grpc" + "google.golang.org/protobuf/proto" + luckygifteventsv1 "hyapp.local/api/proto/events/luckygift/v1" walletv1 "hyapp.local/api/proto/wallet/v1" ) @@ -35,6 +39,7 @@ type Repository interface { ListLuckyGiftPoolBalances(ctx context.Context, query domain.PoolBalanceQuery) ([]domain.PoolBalance, error) GetLuckyGiftDrawRewardState(ctx context.Context, appCode string, drawIDs []string) (domain.DrawRewardState, error) ClaimPendingLuckyGiftOutbox(ctx context.Context, workerID string, nowMS int64, lockTTL time.Duration, batchSize int) ([]domain.DrawOutbox, error) + DeleteDeliveredLuckyGiftOutboxBefore(ctx context.Context, cutoffMS int64, limit int) (int64, error) MarkLuckyGiftOutboxDelivered(ctx context.Context, event domain.DrawOutbox, nowMS int64) error MarkLuckyGiftOutboxRetryable(ctx context.Context, event domain.DrawOutbox, retryCount int, nextRetryAtMS int64, lastErr string, nowMS int64) error MarkLuckyGiftOutboxFailed(ctx context.Context, event domain.DrawOutbox, retryCount int, lastErr string, nowMS int64) error @@ -57,11 +62,18 @@ type UserPublisher interface { PublishUserCustomMessage(ctx context.Context, message tencentim.CustomUserMessage) error } +// EventPublisher 把钱包已确认入账的中奖事实发布到 lucky-gift-service 专属 topic。 +// 下游 room/activity 各自使用独立 consumer group 和本地幂等 outbox,发布失败只能重试展示事实,不能回滚钱包。 +type EventPublisher interface { + PublishLuckyGiftEvent(ctx context.Context, envelope *luckygifteventsv1.EventEnvelope) error +} + // Service 承载幸运礼物查询、抽奖命令和后台规则配置。 type Service struct { repository Repository wallet WalletClient userPub UserPublisher + eventPub EventPublisher now func() time.Time } @@ -79,6 +91,12 @@ func WithUserPublisher(publisher UserPublisher) Option { } } +func WithEventPublisher(publisher EventPublisher) Option { + return func(s *Service) { + s.eventPub = publisher + } +} + func New(repository Repository, options ...Option) *Service { service := &Service{repository: repository, now: time.Now} for _, option := range options { @@ -132,11 +150,19 @@ func (s *Service) DrawBatch(ctx context.Context, cmds []domain.DrawCommand) ([]d return nil, xerr.New(xerr.InvalidArgument, "lucky gift batch draw commands are required") } normalized := make([]domain.DrawCommand, 0, len(cmds)) + totalUnits := int64(0) for _, cmd := range cmds { item, err := s.normalizeDrawCommand(cmd) if err != nil { return nil, err } + units := int64(item.GiftCount) + if units > giftlimits.MaxTotalGiftUnits-totalUnits { + // BatchExecute 的每个 command 对应一个收礼目标;逐项 <=999 仍可能被大量目标放大。 + // 在进入 repository 的共享奖池事务前按所有目标求和,和 Gateway/Room/Wallet 的总工作量上限保持一致。 + return nil, xerr.New(xerr.InvalidArgument, "lucky gift batch exceeds total work limit") + } + totalUnits += units normalized = append(normalized, item) } // 批量送礼的返奖不能阻塞 room-service 主链路;repository 只落 draw fact 和 settlement outbox,worker 后续负责 wallet/IM。 @@ -207,12 +233,21 @@ func (s *Service) normalizeDrawCommand(cmd domain.DrawCommand) (domain.DrawComma cmd.DeviceID = strings.TrimSpace(cmd.DeviceID) cmd.RoomID = strings.TrimSpace(cmd.RoomID) cmd.AnchorID = strings.TrimSpace(cmd.AnchorID) - if cmd.CommandID == "" || cmd.UserID <= 0 || cmd.DeviceID == "" || cmd.RoomID == "" || cmd.AnchorID == "" || cmd.GiftID == "" || cmd.CoinSpent <= 0 { + cmd.SenderName = strings.TrimSpace(cmd.SenderName) + cmd.SenderAvatar = strings.TrimSpace(cmd.SenderAvatar) + cmd.SenderDisplayUserID = strings.TrimSpace(cmd.SenderDisplayUserID) + cmd.SenderPrettyDisplayUserID = strings.TrimSpace(cmd.SenderPrettyDisplayUserID) + if !giftlimits.ValidCommandID(cmd.CommandID) || cmd.UserID <= 0 || cmd.DeviceID == "" || cmd.RoomID == "" || cmd.AnchorID == "" || cmd.GiftID == "" || cmd.CoinSpent <= 0 { return domain.DrawCommand{}, xerr.New(xerr.InvalidArgument, "lucky gift draw command is incomplete") } - if cmd.TargetUserID < 0 || cmd.GiftCount <= 0 { + if cmd.TargetUserID < 0 { return domain.DrawCommand{}, xerr.New(xerr.InvalidArgument, "lucky gift draw metadata is invalid") } + if _, ok := giftlimits.WorkUnits(int64(cmd.GiftCount), 1); !ok { + // One lucky draw command is per target; multi-target room sends are split before this owner. + // The cap prevents a direct internal caller from allocating unbounded per-unit RNG/write work. + return domain.DrawCommand{}, xerr.New(xerr.InvalidArgument, "lucky gift count exceeds command limit") + } if cmd.PaidAtMS <= 0 { // paid_at_ms 是扣费事实时间,缺失时用 UTC 服务端时间补齐,避免本地时区污染 RTP 窗口。 cmd.PaidAtMS = s.now().UTC().UnixMilli() @@ -326,6 +361,10 @@ func (s *Service) processDrawOutbox(ctx context.Context, event domain.DrawOutbox // payload 不可解析说明抽奖事实旁路已经损坏,重试也无法恢复,直接走失败收敛。 return s.markOutboxFailed(ctx, event, options, err, false) } + if strings.TrimSpace(payload.AppCode) == "" { + // 早期 settlement payload 可能只依赖 outbox 列保存 app_code;发布跨服务事实时必须把 owner 列补回信封。 + payload.AppCode = event.AppCode + } eventCtx := appcode.WithContext(ctx, event.AppCode) switch event.EventType { case domain.EventTypeLuckyGiftRewardSettlement, domain.EventTypeLuckyGiftDrawn, "": @@ -353,7 +392,14 @@ func (s *Service) processRewardSettlementOutbox(ctx context.Context, event domai ) } } - // 幸运礼物不再产生跨用户展示副作用;outbox 只负责可靠返奖和中奖用户余额通知,避免展示行为改变结算状态。 + if credited && payload.EffectiveRewardCoins > 0 { + // owner outbox 在钱包入账和 draw=granted 之后才发布中奖事实;Broker 未确认时保持 retryable, + // 但 alreadyCredited=true 禁止重试耗尽后把已经到账的抽奖覆盖成 failed。 + if err := s.publishLuckyGiftDrawnFact(ctx, payload, receipt); err != nil { + return s.markOutboxFailed(ctx, event, options, err, true) + } + } + // 只有 Broker 接受中奖事实后才交付 settlement outbox;消费者按 event_id 幂等,因此 publish 成功、ACK 落库失败只会安全重放。 return s.repository.MarkLuckyGiftOutboxDelivered(ctx, event, s.now().UTC().UnixMilli()) } @@ -370,6 +416,8 @@ func (s *Service) settleLuckyGiftReward(ctx context.Context, event domain.DrawOu alreadyGranted = state.AllGranted if alreadyGranted { walletTransactionID = state.WalletTransactionID + receipt.WalletTransactionID = state.WalletTransactionID + receipt.RewardGrantedAtMS = state.RewardGrantedAtMS credited = true } else { var err error @@ -382,15 +430,17 @@ func (s *Service) settleLuckyGiftReward(ctx context.Context, event domain.DrawOu } } if !credited { - if err := s.markLuckyGiftDrawsGrantedAfterWallet(ctx, event.AppCode, payload.DrawIDs, walletTransactionID); err != nil { + if _, err := s.markLuckyGiftDrawsGrantedAfterWallet(ctx, event.AppCode, payload.DrawIDs, walletTransactionID); err != nil { return false, false, receipt, err } return false, false, receipt, nil } if !alreadyGranted { - if err := s.markLuckyGiftDrawsGrantedAfterWallet(ctx, event.AppCode, payload.DrawIDs, walletTransactionID); err != nil { + grantedAtMS, err := s.markLuckyGiftDrawsGrantedAfterWallet(ctx, event.AppCode, payload.DrawIDs, walletTransactionID) + if err != nil { return credited, alreadyGranted, receipt, err } + receipt.RewardGrantedAtMS = grantedAtMS } return credited, alreadyGranted, receipt, nil } @@ -400,6 +450,8 @@ type luckyGiftRewardReceipt struct { CoinBalanceAfter int64 CoinFrozenAfter int64 BalanceVersion int64 + // RewardGrantedAtMS 来自 lucky draw 持久状态;同一 event_id 的 Broker 重投必须复用这个时间。 + RewardGrantedAtMS int64 } func (s *Service) creditLuckyGiftRewardFastPath(ctx context.Context, cmd domain.DrawCommand, result domain.DrawResult) domain.DrawResult { @@ -407,29 +459,33 @@ func (s *Service) creditLuckyGiftRewardFastPath(ctx context.Context, cmd domain. return result } payload := luckyGiftDrawnPayload{ - AppCode: appcode.FromContext(ctx), - DrawID: result.DrawID, - DrawIDs: luckyDrawResultIDs(result), - CommandID: cmd.CommandID, - PoolID: result.PoolID, - RoomID: cmd.RoomID, - GiftID: cmd.GiftID, - GiftCount: cmd.GiftCount, - UserID: cmd.UserID, - SenderUserID: cmd.UserID, - TargetUserID: cmd.TargetUserID, - VisibleRegionID: cmd.VisibleRegionID, - CountryID: cmd.CountryID, - CoinSpent: cmd.CoinSpent, - RuleVersion: result.RuleVersion, - ExperiencePool: result.ExperiencePool, - SelectedTierID: result.SelectedTierID, - MultiplierPPM: result.MultiplierPPM, - BaseRewardCoins: result.BaseRewardCoins, - EffectiveRewardCoins: result.EffectiveRewardCoins, - StageFeedback: result.StageFeedback, - HighMultiplier: result.HighMultiplier, - CreatedAtMS: result.CreatedAtMS, + AppCode: appcode.FromContext(ctx), + DrawID: result.DrawID, + DrawIDs: luckyDrawResultIDs(result), + CommandID: cmd.CommandID, + PoolID: result.PoolID, + RoomID: cmd.RoomID, + GiftID: cmd.GiftID, + GiftCount: cmd.GiftCount, + UserID: cmd.UserID, + SenderUserID: cmd.UserID, + TargetUserID: cmd.TargetUserID, + SenderName: cmd.SenderName, + SenderAvatar: cmd.SenderAvatar, + SenderDisplayUserID: cmd.SenderDisplayUserID, + SenderPrettyDisplayUserID: cmd.SenderPrettyDisplayUserID, + VisibleRegionID: cmd.VisibleRegionID, + CountryID: cmd.CountryID, + CoinSpent: cmd.CoinSpent, + RuleVersion: result.RuleVersion, + ExperiencePool: result.ExperiencePool, + SelectedTierID: result.SelectedTierID, + MultiplierPPM: result.MultiplierPPM, + BaseRewardCoins: result.BaseRewardCoins, + EffectiveRewardCoins: result.EffectiveRewardCoins, + StageFeedback: result.StageFeedback, + HighMultiplier: result.HighMultiplier, + CreatedAtMS: result.CreatedAtMS, } receipt, err := s.creditLuckyGiftReward(ctx, payload.AppCode, payload) if err != nil { @@ -442,7 +498,8 @@ func (s *Service) creditLuckyGiftRewardFastPath(ctx context.Context, cmd domain. } result.RewardStatus = domain.StatusGranted result.WalletTransactionID = receipt.WalletTransactionID - if err := s.markLuckyGiftDrawsGrantedAfterWallet(ctx, payload.AppCode, payload.DrawIDs, receipt.WalletTransactionID); err != nil { + grantedAtMS, err := s.markLuckyGiftDrawsGrantedAfterWallet(ctx, payload.AppCode, payload.DrawIDs, receipt.WalletTransactionID) + if err != nil { logx.Error(ctx, "lucky_gift_reward_fast_path_mark_granted_failed", err, slog.String("draw_id", result.DrawID), slog.String("command_id", result.CommandID), @@ -450,6 +507,7 @@ func (s *Service) creditLuckyGiftRewardFastPath(ctx context.Context, cmd domain. ) return result } + receipt.RewardGrantedAtMS = grantedAtMS // HTTP/gRPC 抽奖响应不再返回返奖后余额;成功落库后的余额变化通过钱包私有 IM 发送,Flutter 用 balance_after 覆盖本地余额。 if err := s.publishLuckyGiftWalletBalanceChanged(ctx, payload, receipt, 3*time.Second); err != nil { logx.Error(ctx, "lucky_gift_wallet_balance_notice_failed", err, @@ -461,23 +519,85 @@ func (s *Service) creditLuckyGiftRewardFastPath(ctx context.Context, cmd domain. return result } -func (s *Service) markLuckyGiftDrawsGrantedAfterWallet(ctx context.Context, appCode string, drawIDs []string, walletTransactionID string) error { +func (s *Service) publishLuckyGiftDrawnFact(ctx context.Context, payload luckyGiftDrawnPayload, receipt luckyGiftRewardReceipt) error { + if s.eventPub == nil { + // 无 MQ 的显式本地隔离模式仍允许验证抽奖/钱包主链路;可提交的 Docker 和线上配置必须启用 owner topic。 + return nil + } + drawCreatedAtMS := payload.CreatedAtMS + if drawCreatedAtMS <= 0 { + return fmt.Errorf("lucky gift draw created_at_ms is missing") + } + rewardGrantedAtMS := receipt.RewardGrantedAtMS + if rewardGrantedAtMS <= 0 { + // 拆服前已 granted 的历史行理论上都有 updated_at_ms;若遇到人工旧数据缺时间,只能退化到稳定的抽奖时间, + // 不能在每次 MQ retry 时重新取 now 造成相同 event_id 对应不同事实正文。 + rewardGrantedAtMS = drawCreatedAtMS + } + body, err := proto.Marshal(&luckygifteventsv1.LuckyGiftDrawn{ + DrawId: payload.DrawID, + CommandId: payload.CommandID, + RoomId: payload.RoomID, + PoolId: payload.PoolID, + GiftId: payload.GiftID, + GiftCount: payload.GiftCount, + SenderUserId: payload.SenderUserID, + TargetUserId: payload.TargetUserID, + SenderName: payload.SenderName, + SenderAvatar: payload.SenderAvatar, + SenderDisplayUserId: payload.SenderDisplayUserID, + SenderPrettyDisplayUserId: payload.SenderPrettyDisplayUserID, + VisibleRegionId: payload.VisibleRegionID, + CountryId: payload.CountryID, + CoinSpent: payload.CoinSpent, + RuleVersion: payload.RuleVersion, + ExperiencePool: payload.ExperiencePool, + SelectedTierId: payload.SelectedTierID, + MultiplierPpm: payload.MultiplierPPM, + BaseRewardCoins: payload.BaseRewardCoins, + EffectiveRewardCoins: payload.EffectiveRewardCoins, + StageFeedback: payload.StageFeedback, + HighMultiplier: payload.HighMultiplier, + RewardStatus: domain.StatusGranted, + WalletTransactionId: receipt.WalletTransactionID, + DrawCreatedAtMs: drawCreatedAtMS, + RewardGrantedAtMs: rewardGrantedAtMS, + }) + if err != nil { + return err + } + eventID := strings.TrimSpace(payload.EventID) + if eventID == "" { + eventID = "lucky_gift_drawn:" + payload.DrawID + } + return s.eventPub.PublishLuckyGiftEvent(ctx, &luckygifteventsv1.EventEnvelope{ + EventId: eventID, + EventType: luckygiftmq.EventTypeLuckyGiftDrawn, + AppCode: payload.AppCode, + DrawId: payload.DrawID, + OccurredAtMs: drawCreatedAtMS, + Body: body, + }) +} + +func (s *Service) markLuckyGiftDrawsGrantedAfterWallet(ctx context.Context, appCode string, drawIDs []string, walletTransactionID string) (int64, error) { baseCtx := context.WithoutCancel(ctx) var lastErr error for attempt := 0; attempt < 3; attempt++ { markCtx, cancel := context.WithTimeout(appcode.WithContext(baseCtx, appCode), 5*time.Second) - err := s.repository.MarkLuckyGiftDrawsGranted(markCtx, appCode, drawIDs, walletTransactionID, s.now().UTC().UnixMilli()) + grantedAtMS := s.now().UTC().UnixMilli() + err := s.repository.MarkLuckyGiftDrawsGranted(markCtx, appCode, drawIDs, walletTransactionID, grantedAtMS) cancel() if err == nil { - return nil + return grantedAtMS, nil } lastErr = err if !isRetryableLuckyGrantMarkError(err) { - return err + return 0, err } time.Sleep(time.Duration(attempt+1) * 50 * time.Millisecond) } - return lastErr + return 0, lastErr } func (s *Service) creditLuckyGiftReward(ctx context.Context, appCode string, payload luckyGiftDrawnPayload) (luckyGiftRewardReceipt, error) { @@ -682,30 +802,31 @@ type luckyGiftDrawnPayload struct { AppCode string `json:"app_code"` DrawID string `json:"draw_id"` // DrawIDs 是聚合发放的明细集合;老 outbox 没有该字段时 decode 会回填为单个 DrawID。 - DrawIDs []string `json:"draw_ids,omitempty"` - CommandID string `json:"command_id"` - PoolID string `json:"pool_id"` - RoomID string `json:"room_id"` - GiftID string `json:"gift_id"` - GiftCount int32 `json:"gift_count"` - UserID int64 `json:"user_id"` - SenderUserID int64 `json:"sender_user_id"` - TargetUserID int64 `json:"target_user_id"` - SenderName string `json:"sender_name,omitempty"` - SenderAvatar string `json:"sender_avatar,omitempty"` - SenderDisplayUserID string `json:"sender_display_user_id,omitempty"` - VisibleRegionID int64 `json:"visible_region_id"` - CountryID int64 `json:"country_id"` - CoinSpent int64 `json:"coin_spent"` - RuleVersion int64 `json:"rule_version"` - ExperiencePool string `json:"experience_pool"` - SelectedTierID string `json:"selected_tier_id"` - MultiplierPPM int64 `json:"multiplier_ppm"` - BaseRewardCoins int64 `json:"base_reward_coins"` - EffectiveRewardCoins int64 `json:"effective_reward_coins"` - StageFeedback bool `json:"stage_feedback"` - HighMultiplier bool `json:"high_multiplier"` - CreatedAtMS int64 `json:"created_at_ms"` + DrawIDs []string `json:"draw_ids,omitempty"` + CommandID string `json:"command_id"` + PoolID string `json:"pool_id"` + RoomID string `json:"room_id"` + GiftID string `json:"gift_id"` + GiftCount int32 `json:"gift_count"` + UserID int64 `json:"user_id"` + SenderUserID int64 `json:"sender_user_id"` + TargetUserID int64 `json:"target_user_id"` + SenderName string `json:"sender_name,omitempty"` + SenderAvatar string `json:"sender_avatar,omitempty"` + SenderDisplayUserID string `json:"sender_display_user_id,omitempty"` + SenderPrettyDisplayUserID string `json:"sender_pretty_display_user_id,omitempty"` + VisibleRegionID int64 `json:"visible_region_id"` + CountryID int64 `json:"country_id"` + CoinSpent int64 `json:"coin_spent"` + RuleVersion int64 `json:"rule_version"` + ExperiencePool string `json:"experience_pool"` + SelectedTierID string `json:"selected_tier_id"` + MultiplierPPM int64 `json:"multiplier_ppm"` + BaseRewardCoins int64 `json:"base_reward_coins"` + EffectiveRewardCoins int64 `json:"effective_reward_coins"` + StageFeedback bool `json:"stage_feedback"` + HighMultiplier bool `json:"high_multiplier"` + CreatedAtMS int64 `json:"created_at_ms"` } func decodeDrawOutboxPayload(payloadJSON string) (luckyGiftDrawnPayload, error) { diff --git a/services/lucky-gift-service/internal/storage/mysql/lucky_gift_outbox_claim_test.go b/services/lucky-gift-service/internal/storage/mysql/lucky_gift_outbox_claim_test.go new file mode 100644 index 00000000..0512b202 --- /dev/null +++ b/services/lucky-gift-service/internal/storage/mysql/lucky_gift_outbox_claim_test.go @@ -0,0 +1,25 @@ +package mysql + +import "testing" + +func TestLuckyGiftPendingClaimLimitReservesRetryableQuota(t *testing.T) { + tests := []struct { + name string + batchSize int + want int + }{ + {name: "empty", batchSize: 0, want: 0}, + {name: "single slot goes to pending", batchSize: 1, want: 1}, + {name: "small batch keeps one retryable slot", batchSize: 4, want: 3}, + {name: "default batch reserves ten percent", batchSize: 100, want: 90}, + {name: "max batch reserves ten percent", batchSize: 500, want: 450}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := luckyGiftPendingClaimLimit(tt.batchSize); got != tt.want { + t.Fatalf("luckyGiftPendingClaimLimit(%d) = %d, want %d", tt.batchSize, got, tt.want) + } + }) + } +} diff --git a/services/lucky-gift-service/internal/storage/mysql/lucky_gift_repository.go b/services/lucky-gift-service/internal/storage/mysql/lucky_gift_repository.go index bc59d7e7..5fdd1387 100644 --- a/services/lucky-gift-service/internal/storage/mysql/lucky_gift_repository.go +++ b/services/lucky-gift-service/internal/storage/mysql/lucky_gift_repository.go @@ -993,31 +993,35 @@ func luckyDrawRecordSnapshots(input luckyDrawRecordInput) (string, string, strin func (r *Repository) insertLuckyAggregateRewardSettlementOutbox(ctx context.Context, tx *sql.Tx, appCode string, cmd domain.DrawCommand, state luckyBatchDrawState, nowMS int64) error { aggregate := state.AggregateResult payload, _ := json.Marshal(map[string]any{ - "event_type": "lucky_gift_drawn", - "event_id": "lucky_gift_drawn:" + aggregate.DrawID, - "app_code": appCode, - "draw_id": aggregate.DrawID, - "draw_ids": state.DrawIDs, - "command_id": cmd.CommandID, - "pool_id": aggregate.PoolID, - "user_id": cmd.UserID, - "sender_user_id": cmd.UserID, - "target_user_id": cmd.TargetUserID, - "visible_region_id": cmd.VisibleRegionID, - "country_id": cmd.CountryID, - "room_id": cmd.RoomID, - "gift_id": cmd.GiftID, - "gift_count": cmd.GiftCount, - "coin_spent": cmd.CoinSpent, - "rule_version": aggregate.RuleVersion, - "experience_pool": aggregate.ExperiencePool, - "selected_tier_id": aggregate.SelectedTierID, - "multiplier_ppm": aggregate.MultiplierPPM, - "base_reward_coins": aggregate.BaseRewardCoins, - "effective_reward_coins": aggregate.EffectiveRewardCoins, - "stage_feedback": aggregate.StageFeedback, - "high_multiplier": aggregate.HighMultiplier, - "created_at_ms": nowMS, + "event_type": "lucky_gift_drawn", + "event_id": "lucky_gift_drawn:" + aggregate.DrawID, + "app_code": appCode, + "draw_id": aggregate.DrawID, + "draw_ids": state.DrawIDs, + "command_id": cmd.CommandID, + "pool_id": aggregate.PoolID, + "user_id": cmd.UserID, + "sender_user_id": cmd.UserID, + "target_user_id": cmd.TargetUserID, + "sender_name": cmd.SenderName, + "sender_avatar": cmd.SenderAvatar, + "sender_display_user_id": cmd.SenderDisplayUserID, + "sender_pretty_display_user_id": cmd.SenderPrettyDisplayUserID, + "visible_region_id": cmd.VisibleRegionID, + "country_id": cmd.CountryID, + "room_id": cmd.RoomID, + "gift_id": cmd.GiftID, + "gift_count": cmd.GiftCount, + "coin_spent": cmd.CoinSpent, + "rule_version": aggregate.RuleVersion, + "experience_pool": aggregate.ExperiencePool, + "selected_tier_id": aggregate.SelectedTierID, + "multiplier_ppm": aggregate.MultiplierPPM, + "base_reward_coins": aggregate.BaseRewardCoins, + "effective_reward_coins": aggregate.EffectiveRewardCoins, + "stage_feedback": aggregate.StageFeedback, + "high_multiplier": aggregate.HighMultiplier, + "created_at_ms": nowMS, }) return r.insertLuckyRewardSettlementOutbox(ctx, tx, appCode, aggregate.DrawID, payload, nowMS) } @@ -1171,10 +1175,21 @@ func luckyAggregateDrawResults(cmd domain.DrawCommand, results []domain.DrawResu aggregate := results[0] aggregate.CommandID = cmd.CommandID aggregate.DrawIDs = make([]string, 0, len(results)) - for _, result := range results { + aggregate.Hits = make([]domain.DrawHit, 0) + for index, result := range results { if strings.TrimSpace(result.DrawID) != "" { aggregate.DrawIDs = append(aggregate.DrawIDs, result.DrawID) } + if result.EffectiveRewardCoins > 0 { + // gift_index 从 1 开始,对齐 Flutter 组合礼物的点击/数量序号;只传中奖项,响应大小由中奖数而不是 gift_count 决定。 + aggregate.Hits = append(aggregate.Hits, domain.DrawHit{ + GiftIndex: int32(index + 1), + DrawID: result.DrawID, + SelectedTierID: result.SelectedTierID, + MultiplierPPM: result.MultiplierPPM, + RewardCoins: result.EffectiveRewardCoins, + }) + } } if len(results) == 1 { return aggregate @@ -1313,32 +1328,37 @@ func (r *Repository) applyLuckyDraw(ctx context.Context, tx *sql.Tx, input lucky return err } if luckyDrawNeedsRewardSettlementOutbox(input.Candidate) { - // outbox 只记录钱包返奖结算;展示层副作用不参与可靠补偿链路。 + // 同一 owner outbox 先补偿钱包返奖,再在 granted 收敛后发布中奖事实;room/activity 的展示副作用 + // 由各自本地 outbox 独立补偿,不能在抽奖事务里直接调用腾讯云 IM。 payload, _ := json.Marshal(map[string]any{ - "event_type": "lucky_gift_drawn", - "event_id": "lucky_gift_drawn:" + input.DrawID, - "app_code": input.AppCode, - "draw_id": input.DrawID, - "command_id": input.Command.CommandID, - "pool_id": input.Config.PoolID, - "user_id": input.Command.UserID, - "sender_user_id": input.Command.UserID, - "target_user_id": input.Command.TargetUserID, - "visible_region_id": input.Command.VisibleRegionID, - "country_id": input.Command.CountryID, - "room_id": input.Command.RoomID, - "gift_id": input.Command.GiftID, - "gift_count": input.Command.GiftCount, - "coin_spent": input.Command.CoinSpent, - "rule_version": input.Config.RuleVersion, - "experience_pool": input.ExperiencePool, - "selected_tier_id": input.Candidate.TierID, - "multiplier_ppm": input.Candidate.MultiplierPPM, - "base_reward_coins": input.Candidate.BaseReward, - "effective_reward_coins": input.Candidate.effectiveReward(), - "stage_feedback": input.StageFeedback, - "high_multiplier": input.Candidate.HighMultiplier, - "created_at_ms": input.NowMS, + "event_type": "lucky_gift_drawn", + "event_id": "lucky_gift_drawn:" + input.DrawID, + "app_code": input.AppCode, + "draw_id": input.DrawID, + "command_id": input.Command.CommandID, + "pool_id": input.Config.PoolID, + "user_id": input.Command.UserID, + "sender_user_id": input.Command.UserID, + "target_user_id": input.Command.TargetUserID, + "sender_name": input.Command.SenderName, + "sender_avatar": input.Command.SenderAvatar, + "sender_display_user_id": input.Command.SenderDisplayUserID, + "sender_pretty_display_user_id": input.Command.SenderPrettyDisplayUserID, + "visible_region_id": input.Command.VisibleRegionID, + "country_id": input.Command.CountryID, + "room_id": input.Command.RoomID, + "gift_id": input.Command.GiftID, + "gift_count": input.Command.GiftCount, + "coin_spent": input.Command.CoinSpent, + "rule_version": input.Config.RuleVersion, + "experience_pool": input.ExperiencePool, + "selected_tier_id": input.Candidate.TierID, + "multiplier_ppm": input.Candidate.MultiplierPPM, + "base_reward_coins": input.Candidate.BaseReward, + "effective_reward_coins": input.Candidate.effectiveReward(), + "stage_feedback": input.StageFeedback, + "high_multiplier": input.Candidate.HighMultiplier, + "created_at_ms": input.NowMS, }) if err := r.insertLuckyRewardSettlementOutbox(ctx, tx, input.AppCode, input.DrawID, payload, input.NowMS); err != nil { return err @@ -1393,17 +1413,42 @@ func (r *Repository) ClaimPendingLuckyGiftOutbox(ctx context.Context, workerID s domain.EventTypeExternalLuckyGiftDrawn, } for _, eventType := range priorityTypes { - candidates, err := r.queryLuckyGiftOutboxCandidates(ctx, tx, ` - SELECT app_code, outbox_id, event_type, COALESCE(CAST(payload AS CHAR), '{}'), retry_count, created_at_ms - FROM lucky_gift_outbox - WHERE event_type = ? - AND status IN ('pending', 'retryable') - AND next_retry_at_ms <= ? - ORDER BY created_at_ms ASC, outbox_id ASC - LIMIT ? - FOR UPDATE`, batchSize, eventType, nowMS, batchSize) - if err != nil { - return nil, err + candidates := make([]domain.DrawOutbox, 0, batchSize) + // pending 和 retryable 分开抢占:IN 跨 status 会让索引无法消除排序,积压时 filesort 并 + // FOR UPDATE 锁住全部匹配行,多实例互相阻塞。单 status 查询按 idx_lucky_gift_outbox_claim_retry + // (event_type, status, next_retry_at_ms, created_at_ms, outbox_id) 的列序扫描,LIMIT 提前终止; + // pending 只拿部分额度,给 retryable 保留重试配额避免洪峰饿死补偿。 + for _, claim := range []struct { + status string + limit int + }{ + {status: "pending", limit: luckyGiftPendingClaimLimit(batchSize)}, + {status: "retryable", limit: batchSize}, + } { + remaining := batchSize - len(candidates) + if remaining <= 0 { + break + } + limit := claim.limit + if limit > remaining { + limit = remaining + } + if limit <= 0 { + continue + } + statusRows, err := r.queryLuckyGiftOutboxCandidates(ctx, tx, ` + SELECT app_code, outbox_id, event_type, COALESCE(CAST(payload AS CHAR), '{}'), retry_count, created_at_ms + FROM lucky_gift_outbox + WHERE event_type = ? + AND status = ? + AND next_retry_at_ms <= ? + ORDER BY next_retry_at_ms ASC, created_at_ms ASC, outbox_id ASC + LIMIT ? + FOR UPDATE SKIP LOCKED`, limit, eventType, claim.status, nowMS, limit) + if err != nil { + return nil, err + } + candidates = append(candidates, statusRows...) } if len(candidates) > 0 { events = append(events, candidates...) @@ -1412,15 +1457,17 @@ func (r *Repository) ClaimPendingLuckyGiftOutbox(ctx context.Context, workerID s } if len(events) == 0 { for _, eventType := range priorityTypes { + // 排序对齐 idx_lucky_gift_outbox_claim_lock (event_type, status, lock_until_ms, created_at_ms, outbox_id), + // SKIP LOCKED 让两实例的超时接管互不阻塞。 retryEvents, err := r.queryLuckyGiftOutboxCandidates(ctx, tx, ` SELECT app_code, outbox_id, event_type, COALESCE(CAST(payload AS CHAR), '{}'), retry_count, created_at_ms FROM lucky_gift_outbox WHERE event_type = ? AND status = 'delivering' AND lock_until_ms <= ? - ORDER BY created_at_ms ASC, outbox_id ASC + ORDER BY lock_until_ms ASC, created_at_ms ASC, outbox_id ASC LIMIT ? - FOR UPDATE`, batchSize, eventType, nowMS, batchSize) + FOR UPDATE SKIP LOCKED`, batchSize, eventType, nowMS, batchSize) if err != nil { return nil, err } @@ -1447,6 +1494,18 @@ func (r *Repository) ClaimPendingLuckyGiftOutbox(ctx context.Context, workerID s return events, nil } +// luckyGiftPendingClaimLimit 是单轮批次里 pending 可占用的额度上限,剩余部分留给 retryable。 +func luckyGiftPendingClaimLimit(batchSize int) int { + if batchSize <= 1 { + return batchSize + } + reserve := batchSize / 10 + if reserve < 1 { + reserve = 1 + } + return batchSize - reserve +} + func (r *Repository) queryLuckyGiftOutboxCandidates(ctx context.Context, tx *sql.Tx, query string, capacity int, args ...any) ([]domain.DrawOutbox, error) { rows, err := tx.QueryContext(ctx, query, args...) if err != nil { @@ -1504,6 +1563,94 @@ func (r *Repository) MarkLuckyGiftOutboxFailed(ctx context.Context, event domain return err } +// DeleteDeliveredLuckyGiftOutboxBefore 删除一小批 delivered 且 updated_at_ms 早于 cutoffMS 的历史事件,返回本批删除总行数。 +// 只清理 delivered 终态:pending/retryable/delivering 仍在投递生命周期内,failed 留给人工补偿排查。 +// 抽奖事实在 lucky_draw_records,outbox 仅承载补偿投递,delivered 行没有下游读方。 +func (r *Repository) DeleteDeliveredLuckyGiftOutboxBefore(ctx context.Context, cutoffMS int64, limit int) (int64, error) { + if r == nil || r.db == nil { + return 0, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + if limit <= 0 { + // 默认批量上限避免调用方传 0 导致单事务删除失控。 + limit = 500 + } + // 清理跨 App 而 idx_lucky_gift_outbox_retention 以 app_code 开头;先用主键前缀枚举 app_code, + // 再按 App 走 retention 索引定位最旧一批主键后删除,避免优化器退化成全索引扫描。 + appCodeRows, err := r.db.QueryContext(ctx, `SELECT DISTINCT app_code FROM lucky_gift_outbox`) + if err != nil { + return 0, err + } + appCodes := make([]string, 0, 4) + for appCodeRows.Next() { + var appCode string + if err := appCodeRows.Scan(&appCode); err != nil { + _ = appCodeRows.Close() + return 0, err + } + appCodes = append(appCodes, appCode) + } + if err := appCodeRows.Err(); err != nil { + _ = appCodeRows.Close() + return 0, err + } + _ = appCodeRows.Close() + + var total int64 + for _, appCode := range appCodes { + remaining := limit - int(total) + if remaining <= 0 { + break + } + outboxIDs, err := r.listDeliveredLuckyGiftOutboxIDs(ctx, appCode, cutoffMS, remaining) + if err != nil { + return total, err + } + if len(outboxIDs) == 0 { + continue + } + // delivered 是终态不会回流,select 到 delete 之间无需事务;status 条件兜底并发下的重复认领。 + args := make([]any, 0, len(outboxIDs)+1) + args = append(args, appCode) + for _, outboxID := range outboxIDs { + args = append(args, outboxID) + } + result, err := r.db.ExecContext(ctx, + `DELETE FROM lucky_gift_outbox WHERE app_code = ? AND status = 'delivered' AND outbox_id IN (`+luckySQLPlaceholders(len(outboxIDs))+`)`, + args..., + ) + if err != nil { + return total, err + } + deleted, err := result.RowsAffected() + if err != nil { + return total, err + } + total += deleted + } + return total, nil +} + +func (r *Repository) listDeliveredLuckyGiftOutboxIDs(ctx context.Context, appCode string, cutoffMS int64, limit int) ([]string, error) { + rows, err := r.db.QueryContext(ctx, ` + SELECT outbox_id FROM lucky_gift_outbox FORCE INDEX (idx_lucky_gift_outbox_retention) + WHERE app_code = ? AND status = 'delivered' AND updated_at_ms < ? + ORDER BY updated_at_ms ASC, outbox_id ASC + LIMIT ?`, appCode, cutoffMS, limit) + if err != nil { + return nil, err + } + defer rows.Close() + outboxIDs := make([]string, 0, limit) + for rows.Next() { + var outboxID string + if err := rows.Scan(&outboxID); err != nil { + return nil, err + } + outboxIDs = append(outboxIDs, outboxID) + } + return outboxIDs, rows.Err() +} + func (r *Repository) MarkLuckyGiftDrawGranted(ctx context.Context, appCode string, drawID string, walletTransactionID string, nowMS int64) error { return r.MarkLuckyGiftDrawsGranted(ctx, appCode, []string{drawID}, walletTransactionID, nowMS) } @@ -1521,7 +1668,7 @@ func (r *Repository) GetLuckyGiftDrawRewardState(ctx context.Context, appCode st args = append(args, drawID) } rows, err := r.db.QueryContext(ctx, ` - SELECT reward_status, COALESCE(reward_transaction_id, ''), COUNT(*) + SELECT reward_status, COALESCE(reward_transaction_id, ''), MAX(updated_at_ms), COUNT(*) FROM lucky_draw_records WHERE app_code = ? AND draw_id IN (`+luckySQLPlaceholders(len(drawIDs))+`) GROUP BY reward_status, reward_transaction_id`, args...) @@ -1532,11 +1679,13 @@ func (r *Repository) GetLuckyGiftDrawRewardState(ctx context.Context, appCode st var total int64 var granted int64 walletTransactionID := "" + rewardGrantedAtMS := int64(0) for rows.Next() { var status string var txID string + var updatedAtMS int64 var count int64 - if err := rows.Scan(&status, &txID, &count); err != nil { + if err := rows.Scan(&status, &txID, &updatedAtMS, &count); err != nil { return domain.DrawRewardState{}, err } total += count @@ -1545,6 +1694,9 @@ func (r *Repository) GetLuckyGiftDrawRewardState(ctx context.Context, appCode st if walletTransactionID == "" { walletTransactionID = txID } + if updatedAtMS > rewardGrantedAtMS { + rewardGrantedAtMS = updatedAtMS + } } } if err := rows.Err(); err != nil { @@ -1553,6 +1705,7 @@ func (r *Repository) GetLuckyGiftDrawRewardState(ctx context.Context, appCode st return domain.DrawRewardState{ AllGranted: total == int64(len(drawIDs)) && granted == total, WalletTransactionID: walletTransactionID, + RewardGrantedAtMS: rewardGrantedAtMS, }, nil } diff --git a/services/lucky-gift-service/internal/storage/mysql/lucky_gift_repository_test.go b/services/lucky-gift-service/internal/storage/mysql/lucky_gift_repository_test.go index 944b99f0..877da655 100644 --- a/services/lucky-gift-service/internal/storage/mysql/lucky_gift_repository_test.go +++ b/services/lucky-gift-service/internal/storage/mysql/lucky_gift_repository_test.go @@ -56,6 +56,20 @@ func TestLuckyBasePoolInCreditsFullPoolRateToSinglePool(t *testing.T) { } } +func TestLuckyAggregateDrawResultsKeepsOnlyWinningPositions(t *testing.T) { + result := luckyAggregateDrawResults(domain.DrawCommand{CommandID: "cmd-combo"}, []domain.DrawResult{ + {DrawID: "draw-1", SelectedTierID: "2x", MultiplierPPM: 2_000_000, EffectiveRewardCoins: 20}, + {DrawID: "draw-2", SelectedTierID: "0x", MultiplierPPM: 0, EffectiveRewardCoins: 0}, + {DrawID: "draw-3", SelectedTierID: "5x", MultiplierPPM: 5_000_000, EffectiveRewardCoins: 50}, + }) + if len(result.Hits) != 2 { + t.Fatalf("only winning sub-draws should be returned, got %+v", result.Hits) + } + if result.Hits[0].GiftIndex != 1 || result.Hits[0].DrawID != "draw-1" || result.Hits[1].GiftIndex != 3 || result.Hits[1].RewardCoins != 50 { + t.Fatalf("winning positions mismatch: %+v", result.Hits) + } +} + func TestMergeLuckyBasePoolsMigration(t *testing.T) { caller := mysqlschema.CallerFile(t, 1) schema := mysqlschema.New(t, mysqlschema.Config{ diff --git a/services/lucky-gift-service/internal/storage/mysql/lucky_gift_rule_config_repository.go b/services/lucky-gift-service/internal/storage/mysql/lucky_gift_rule_config_repository.go index 4c190daf..bf7cf8a6 100644 --- a/services/lucky-gift-service/internal/storage/mysql/lucky_gift_rule_config_repository.go +++ b/services/lucky-gift-service/internal/storage/mysql/lucky_gift_rule_config_repository.go @@ -65,7 +65,7 @@ func (r *Repository) PublishLuckyGiftRuleConfig(ctx context.Context, config doma settlement_window_wager, control_band_ppm, gift_price_reference, novice_max_equivalent_draws, normal_max_equivalent_draws, effective_from_ms, created_by_admin_id, created_at_ms - ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, config.AppCode, config.PoolID, config.RuleVersion, config.Enabled, config.TargetRTPPPM, config.PoolRatePPM, config.SettlementWindowWager, config.ControlBandPPM, config.GiftPriceReference, config.NoviceMaxEquivalentDraws, config.NormalMaxEquivalentDraws, diff --git a/services/lucky-gift-service/internal/storage/mysql/lucky_gift_rule_config_repository_test.go b/services/lucky-gift-service/internal/storage/mysql/lucky_gift_rule_config_repository_test.go new file mode 100644 index 00000000..1d0bfff2 --- /dev/null +++ b/services/lucky-gift-service/internal/storage/mysql/lucky_gift_rule_config_repository_test.go @@ -0,0 +1,72 @@ +package mysql + +import ( + "context" + "testing" + + "hyapp/internal/testutil/mysqlschema" + "hyapp/pkg/appcode" + domain "hyapp/services/lucky-gift-service/internal/domain/luckygift" +) + +// 回归:INSERT 列清单和占位符数量必须一致。拆分 lucky-gift-service 时列从 20 裁到 14, +// 占位符只裁到 15,导致所有规则发布在 MySQL 层直接失败并以 opaque internal error 暴露给运营后台。 +func TestPublishLuckyGiftRuleConfigPersistsVersionsAndTiers(t *testing.T) { + caller := mysqlschema.CallerFile(t, 1) + schema := mysqlschema.New(t, mysqlschema.Config{ + EnvVar: "LUCKY_GIFT_SERVICE_MYSQL_TEST_DSN", + InitDBPath: mysqlschema.InitDBPath(t, caller, "..", "..", "..", "deploy", "mysql", "initdb", "001_lucky_gift_service.sql"), + DatabasePrefix: "hy_lucky_gift", + }) + + repo := &Repository{db: schema.DB} + ctx := appcode.WithContext(context.Background(), "lalu") + config := domain.RuleConfig{ + PoolID: "lucky", + Enabled: true, + TargetRTPPPM: 950_000, + PoolRatePPM: 960_000, + SettlementWindowWager: 1_000_000, + ControlBandPPM: 10_000, + GiftPriceReference: 100, + NoviceMaxEquivalentDraws: 2_000, + NormalMaxEquivalentDraws: 20_000, + Stages: []domain.RuleStage{ + {Stage: domain.StageNovice, Tiers: []domain.RuleTier{ + {Stage: domain.StageNovice, TierID: "novice_1x", MultiplierPPM: 1_000_000, BaseWeightPPM: 1_000_000, Enabled: true}, + }}, + }, + } + + published, err := repo.PublishLuckyGiftRuleConfig(ctx, config, 1_700_000_000_000) + if err != nil { + t.Fatalf("publish first rule version: %v", err) + } + if published.RuleVersion != 1 { + t.Fatalf("first publish rule_version = %d, want 1", published.RuleVersion) + } + + // 版本必须只增不改:同奖池再次发布要拿到 v2,且两版规则和奖档都能按最新版本读回。 + again, err := repo.PublishLuckyGiftRuleConfig(ctx, config, 1_700_000_100_000) + if err != nil { + t.Fatalf("publish second rule version: %v", err) + } + if again.RuleVersion != 2 { + t.Fatalf("second publish rule_version = %d, want 2", again.RuleVersion) + } + + configs, err := repo.ListLuckyGiftRuleConfigs(ctx, "lalu") + if err != nil { + t.Fatalf("list rule configs: %v", err) + } + if len(configs) != 1 { + t.Fatalf("list should return the latest version per pool, got %d rows", len(configs)) + } + latest := configs[0] + if latest.PoolID != "lucky" || latest.RuleVersion != 2 || !latest.Enabled { + t.Fatalf("latest config = %+v, want lucky v2 enabled", latest) + } + if len(latest.Stages) != 1 || len(latest.Stages[0].Tiers) != 1 || latest.Stages[0].Tiers[0].TierID != "novice_1x" { + t.Fatalf("latest stages = %+v, want single novice tier novice_1x", latest.Stages) + } +} diff --git a/services/lucky-gift-service/internal/transport/grpc/lucky_gift_server.go b/services/lucky-gift-service/internal/transport/grpc/lucky_gift_server.go index fd3685aa..b2ddf37c 100644 --- a/services/lucky-gift-service/internal/transport/grpc/lucky_gift_server.go +++ b/services/lucky-gift-service/internal/transport/grpc/lucky_gift_server.go @@ -105,19 +105,23 @@ func (s *LuckyGiftServer) ExecuteExternalGiftDraw(ctx context.Context, req *luck func luckyDrawCommandFromProto(meta *luckygiftv1.LuckyGiftMeta) domain.DrawCommand { return domain.DrawCommand{ - CommandID: meta.GetCommandId(), - PoolID: meta.GetPoolId(), - UserID: meta.GetUserId(), - TargetUserID: meta.GetTargetUserId(), - DeviceID: meta.GetDeviceId(), - RoomID: meta.GetRoomId(), - AnchorID: meta.GetAnchorId(), - GiftID: meta.GetGiftId(), - GiftCount: meta.GetGiftCount(), - CoinSpent: meta.GetCoinSpent(), - PaidAtMS: meta.GetPaidAtMs(), - VisibleRegionID: meta.GetVisibleRegionId(), - CountryID: meta.GetCountryId(), + CommandID: meta.GetCommandId(), + PoolID: meta.GetPoolId(), + UserID: meta.GetUserId(), + TargetUserID: meta.GetTargetUserId(), + DeviceID: meta.GetDeviceId(), + RoomID: meta.GetRoomId(), + AnchorID: meta.GetAnchorId(), + GiftID: meta.GetGiftId(), + GiftCount: meta.GetGiftCount(), + CoinSpent: meta.GetCoinSpent(), + PaidAtMS: meta.GetPaidAtMs(), + VisibleRegionID: meta.GetVisibleRegionId(), + CountryID: meta.GetCountryId(), + SenderName: meta.GetSenderName(), + SenderAvatar: meta.GetSenderAvatar(), + SenderDisplayUserID: meta.GetSenderDisplayUserId(), + SenderPrettyDisplayUserID: meta.GetSenderPrettyDisplayUserId(), } } @@ -309,7 +313,7 @@ func luckyRuleConfigToProto(config domain.RuleConfig) *luckygiftv1.LuckyGiftRule } func luckyDrawResultToProto(result domain.DrawResult) *luckygiftv1.LuckyGiftDrawResult { - return &luckygiftv1.LuckyGiftDrawResult{ + response := &luckygiftv1.LuckyGiftDrawResult{ DrawId: result.DrawID, CommandId: result.CommandID, AppCode: result.AppCode, @@ -334,6 +338,13 @@ func luckyDrawResultToProto(result domain.DrawResult) *luckygiftv1.LuckyGiftDraw WalletTransactionId: result.WalletTransactionID, CoinBalanceAfter: result.CoinBalanceAfter, } + for _, hit := range result.Hits { + response.Hits = append(response.Hits, &luckygiftv1.LuckyGiftHit{ + GiftIndex: hit.GiftIndex, DrawId: hit.DrawID, SelectedTierId: hit.SelectedTierID, + MultiplierPpm: hit.MultiplierPPM, RewardCoins: hit.RewardCoins, + }) + } + return response } func externalDrawResultToProto(result domain.ExternalDrawResult) *luckygiftv1.ExternalGiftDrawResponse { diff --git a/services/notice-service/cmd/import-im-accounts/main.go b/services/notice-service/cmd/import-im-accounts/main.go new file mode 100644 index 00000000..407db0be --- /dev/null +++ b/services/notice-service/cmd/import-im-accounts/main.go @@ -0,0 +1,119 @@ +// Command import-im-accounts performs an explicit, bounded Tencent IM account +// repair before replaying private notices that failed with invalid identifier. +package main + +import ( + "context" + "crypto/sha256" + "flag" + "fmt" + "os" + "sort" + "strconv" + "strings" + "time" + + "hyapp/pkg/tencentim" + "hyapp/services/notice-service/internal/config" +) + +const executeConfirmation = "IMPORT_EXACT_TENCENT_IM_ACCOUNTS" + +type userIDFlags []int64 + +func (values *userIDFlags) String() string { + parts := make([]string, 0, len(*values)) + for _, value := range *values { + parts = append(parts, strconv.FormatInt(value, 10)) + } + return strings.Join(parts, ",") +} + +func (values *userIDFlags) Set(raw string) error { + value, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64) + if err != nil || value <= 0 { + return fmt.Errorf("user-id must be a positive int64") + } + *values = append(*values, value) + return nil +} + +func main() { + configPath := flag.String("config", "services/notice-service/configs/config.yaml", "notice-service config containing Tencent IM credentials") + execute := flag.Bool("execute", false, "perform the exact account imports; default is dry-run") + confirmation := flag.String("confirm", "", "required execution confirmation") + timeout := flag.Duration("timeout", 30*time.Second, "total import timeout") + var requested userIDFlags + flag.Var(&requested, "user-id", "exact internal user_id to import; repeat for multiple users") + flag.Parse() + + userIDs, err := normalizeUserIDs(requested) + if err != nil { + fatal(err) + } + if *timeout <= 0 || *timeout > 5*time.Minute { + fatal(fmt.Errorf("timeout must be within (0,5m]")) + } + if *execute && strings.TrimSpace(*confirmation) != executeConfirmation { + fatal(fmt.Errorf("--execute requires --confirm=%s", executeConfirmation)) + } + fingerprint := userIDsSHA256(userIDs) + if !*execute { + fmt.Printf("mode=dry-run users=%d user_ids_sha256=%s\n", len(userIDs), fingerprint) + return + } + + cfg, err := config.Load(strings.TrimSpace(*configPath)) + if err != nil { + fatal(err) + } + if !cfg.TencentIM.Enabled { + fatal(fmt.Errorf("tencent_im.enabled is required")) + } + client, err := tencentim.NewRESTClient(cfg.TencentIM.RESTConfig()) + if err != nil { + fatal(err) + } + ctx, cancel := context.WithTimeout(context.Background(), *timeout) + defer cancel() + for _, userID := range userIDs { + // Account import is idempotent in the shared REST client: Tencent's + // already-exists response is success, while transport and other API errors + // stop the bounded batch before the notice replay is attempted. + if err := client.ImportAccount(ctx, tencentim.AccountProfile{UserID: userID}); err != nil { + fatal(fmt.Errorf("import user index %d: %w", sort.Search(len(userIDs), func(index int) bool { return userIDs[index] >= userID })+1, err)) + } + } + fmt.Printf("mode=execute imported=%d user_ids_sha256=%s\n", len(userIDs), fingerprint) +} + +func normalizeUserIDs(values []int64) ([]int64, error) { + if len(values) == 0 || len(values) > 100 { + return nil, fmt.Errorf("user-id count must be between 1 and 100") + } + normalized := append([]int64(nil), values...) + sort.Slice(normalized, func(i, j int) bool { return normalized[i] < normalized[j] }) + result := normalized[:0] + for _, value := range normalized { + if value <= 0 { + return nil, fmt.Errorf("user-id must be a positive int64") + } + if len(result) == 0 || result[len(result)-1] != value { + result = append(result, value) + } + } + return result, nil +} + +func userIDsSHA256(values []int64) string { + parts := make([]string, 0, len(values)) + for _, value := range values { + parts = append(parts, strconv.FormatInt(value, 10)) + } + return fmt.Sprintf("%x", sha256.Sum256([]byte(strings.Join(parts, "\n")))) +} + +func fatal(err error) { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) +} diff --git a/services/notice-service/cmd/import-im-accounts/main_test.go b/services/notice-service/cmd/import-im-accounts/main_test.go new file mode 100644 index 00000000..fce99e13 --- /dev/null +++ b/services/notice-service/cmd/import-im-accounts/main_test.go @@ -0,0 +1,24 @@ +package main + +import "testing" + +func TestNormalizeUserIDsSortsAndDeduplicatesExactSet(t *testing.T) { + got, err := normalizeUserIDs([]int64{42, 7, 42}) + if err != nil { + t.Fatalf("normalizeUserIDs failed: %v", err) + } + if len(got) != 2 || got[0] != 7 || got[1] != 42 { + t.Fatalf("normalized IDs mismatch: %+v", got) + } + if fingerprint := userIDsSHA256(got); fingerprint != userIDsSHA256([]int64{7, 42}) { + t.Fatalf("fingerprint must be stable, got %s", fingerprint) + } +} + +func TestNormalizeUserIDsRejectsUnsafeSets(t *testing.T) { + for _, values := range [][]int64{nil, {0}, {-1}} { + if _, err := normalizeUserIDs(values); err == nil { + t.Fatalf("unsafe user IDs must fail: %+v", values) + } + } +} diff --git a/services/notice-service/cmd/replay-deliveries/main.go b/services/notice-service/cmd/replay-deliveries/main.go new file mode 100644 index 00000000..81aca04d --- /dev/null +++ b/services/notice-service/cmd/replay-deliveries/main.go @@ -0,0 +1,505 @@ +// Command replay-deliveries replays an explicitly selected set of expired +// notice delivery leases from immutable owner outbox facts. +package main + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "flag" + "fmt" + "log" + "os" + "sort" + "strings" + "time" + + roomeventsv1 "hyapp.local/api/proto/events/room/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/tencentim" + "hyapp/pkg/walletmq" + "hyapp/services/notice-service/internal/config" + "hyapp/services/notice-service/internal/modules/roomnotice" + "hyapp/services/notice-service/internal/modules/walletnotice" + mysqlplatform "hyapp/services/notice-service/internal/platform/mysql" +) + +const ( + walletSource = "wallet_outbox" + roomSource = "room_outbox" + c2cChannel = "tencent_im_c2c" +) + +type repeatedFlag []string + +func (values *repeatedFlag) String() string { return strings.Join(*values, ",") } + +func (values *repeatedFlag) Set(value string) error { + value = strings.TrimSpace(value) + if value == "" { + return errors.New("flag value cannot be empty") + } + *values = append(*values, value) + return nil +} + +type deliverySelector struct { + SourceName string + AppCode string + EventID string +} + +func (selector deliverySelector) String() string { + return selector.SourceName + ":" + selector.AppCode + ":" + selector.EventID +} + +type deliverySelectorFlag []deliverySelector + +func (values *deliverySelectorFlag) String() string { + parts := make([]string, 0, len(*values)) + for _, selector := range *values { + parts = append(parts, selector.String()) + } + return strings.Join(parts, ",") +} + +func (values *deliverySelectorFlag) Set(value string) error { + parts := strings.SplitN(strings.TrimSpace(value), ":", 3) + if len(parts) != 3 { + return errors.New("delivery must use source_name:app_code:event_id") + } + selector := deliverySelector{ + SourceName: strings.TrimSpace(parts[0]), + AppCode: strings.TrimSpace(parts[1]), + EventID: strings.TrimSpace(parts[2]), + } + if selector.SourceName != walletSource && selector.SourceName != roomSource { + return fmt.Errorf("unsupported delivery source %q", selector.SourceName) + } + if selector.AppCode == "" || selector.EventID == "" { + return errors.New("delivery app_code and event_id are required") + } + selector.AppCode = appcode.Normalize(selector.AppCode) + *values = append(*values, selector) + return nil +} + +type deliveryCandidate struct { + Selector string `json:"selector"` + SourceName string `json:"source_name"` + AppCode string `json:"app_code"` + EventID string `json:"event_id"` + Status string `json:"status"` + RetryCount int `json:"retry_count"` + UpdatedAtMS int64 `json:"updated_at_ms"` +} + +type replayItem struct { + Candidate deliveryCandidate + WalletMessage *walletmq.WalletOutboxMessage + RoomEnvelope *roomeventsv1.EventEnvelope +} + +func main() { + configPath := flag.String("config", "services/notice-service/configs/config.yaml", "path to notice-service config") + execute := flag.Bool("execute", false, "publish selected due deliveries; default is a read-only preview") + limit := flag.Int("limit", 100, "maximum candidate count, 1..500") + var eventIDs repeatedFlag + var appCodes repeatedFlag + var deliveries deliverySelectorFlag + flag.Var(&eventIDs, "event-id", "preview filter for a source event ID; repeat for multiple events") + flag.Var(&appCodes, "app-code", "required App partition for indexed preview; repeat for multiple Apps") + flag.Var(&deliveries, "delivery", "exact source_name:app_code:event_id selector copied from preview; repeat for multiple deliveries") + flag.Parse() + + if *limit <= 0 || *limit > 500 { + log.Fatal("limit must be between 1 and 500") + } + if len(deliveries) > 0 && (len(eventIDs) > 0 || len(appCodes) > 0) { + log.Fatal("--delivery cannot be combined with --event-id or --app-code") + } + if *execute && len(deliveries) == 0 { + // A write run is pinned to full primary-key selectors copied from preview; + // separate App/event filters can form an accidental cross product. + log.Fatal("--execute requires at least one exact --delivery from a preview") + } + if len(deliveries) > *limit { + log.Fatal("exact delivery count exceeds --limit") + } + if len(deliveries) == 0 && len(appCodes) == 0 { + // notice_delivery_events indexes start with source_name,app_code. Refusing + // an unpartitioned preview prevents a LIMIT query from scanning a 20+ GiB table. + log.Fatal("preview requires at least one --app-code or exact --delivery") + } + + cfg, err := config.Load(*configPath) + if err != nil { + log.Fatal(err) + } + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + store, err := mysqlplatform.Open(ctx, cfg.MySQLDSN) + if err != nil { + log.Fatal(err) + } + defer func() { _ = store.Close() }() + + nowMS := time.Now().UnixMilli() + var candidates []deliveryCandidate + if len(deliveries) > 0 { + candidates, err = loadExactDueDeliveries(ctx, store.DB, deliveries, nowMS) + } else { + candidates, err = listDueDeliveries(ctx, store.DB, eventIDs, appCodes, *limit, nowMS) + } + if err != nil { + log.Fatal(err) + } + if !*execute { + encoder := json.NewEncoder(os.Stdout) + for _, candidate := range candidates { + if err := encoder.Encode(candidate); err != nil { + log.Fatal(err) + } + } + fmt.Fprintf(os.Stderr, "previewed %d due delivery rows; no writes performed\n", len(candidates)) + return + } + if !cfg.TencentIM.Enabled { + log.Fatal("tencent_im.enabled is required for replay") + } + walletRepo, err := walletnotice.NewMySQLRepository(store.DB, cfg.WalletDatabase) + if err != nil { + log.Fatal(err) + } + roomRepo, err := roomnotice.NewMySQLRepository(store.DB, cfg.RoomDatabase) + if err != nil { + log.Fatal(err) + } + items, err := loadReplayItems(ctx, candidates, walletRepo, roomRepo) + if err != nil { + // Preload every immutable owner fact before creating an IM client or + // publishing the first item. One missing/corrupt fact therefore aborts the + // exact set without producing a partial operator repair. + log.Fatal(err) + } + publisher, err := tencentim.NewRESTClient(cfg.TencentIM.RESTConfig()) + if err != nil { + log.Fatal(err) + } + walletService := walletnotice.New(walletnotice.Config{NodeID: cfg.NodeID}, walletRepo, publisher) + roomService := roomnotice.New(roomnotice.Config{NodeID: cfg.NodeID}, roomRepo, publisher) + walletOptions := walletWorkerOptions(cfg) + roomOptions := roomWorkerOptions(cfg) + + failures := 0 + for _, item := range items { + candidate := item.Candidate + candidateCtx := appcode.WithContext(ctx, candidate.AppCode) + var replayErr error + switch candidate.SourceName { + case walletSource: + if item.WalletMessage == nil { + replayErr = errors.New("wallet owner snapshot is missing") + } else { + _, replayErr = walletService.ProcessWalletOutboxMessage(candidateCtx, *item.WalletMessage, walletOptions) + } + case roomSource: + if item.RoomEnvelope == nil { + replayErr = errors.New("room owner snapshot is missing") + } else { + _, replayErr = roomService.ProcessRoomOutboxEnvelope(candidateCtx, item.RoomEnvelope, roomOptions) + } + default: + replayErr = fmt.Errorf("unsupported source %q", candidate.SourceName) + } + if replayErr == nil { + replayErr = requireDelivered(ctx, store.DB, candidate) + } + if replayErr != nil { + failures++ + fmt.Fprintf(os.Stderr, "replay failed source=%s app_code=%s event_id=%s: %v\n", candidate.SourceName, candidate.AppCode, candidate.EventID, replayErr) + continue + } + fmt.Printf("delivered source=%s app_code=%s event_id=%s\n", candidate.SourceName, candidate.AppCode, candidate.EventID) + } + if failures > 0 { + log.Fatalf("replay finished with %d failures out of %d selected rows", failures, len(candidates)) + } + fmt.Printf("replay delivered %d selected rows\n", len(candidates)) +} + +func loadReplayItems(ctx context.Context, candidates []deliveryCandidate, walletRepo *walletnotice.MySQLRepository, roomRepo *roomnotice.MySQLRepository) ([]replayItem, error) { + items := make([]replayItem, 0, len(candidates)) + for _, candidate := range candidates { + candidateCtx := appcode.WithContext(ctx, candidate.AppCode) + item := replayItem{Candidate: candidate} + switch candidate.SourceName { + case walletSource: + if walletRepo == nil { + return nil, errors.New("wallet owner repository is not configured") + } + message, err := walletRepo.LoadWalletOutboxMessage(candidateCtx, candidate.AppCode, candidate.EventID) + if err != nil { + return nil, fmt.Errorf("load owner snapshot source=%s app_code=%s event_id=%s: %w", candidate.SourceName, candidate.AppCode, candidate.EventID, err) + } + item.WalletMessage = &message + case roomSource: + if roomRepo == nil { + return nil, errors.New("room owner repository is not configured") + } + envelope, err := roomRepo.LoadRoomOutboxEnvelope(candidateCtx, candidate.AppCode, candidate.EventID) + if err != nil { + return nil, fmt.Errorf("load owner snapshot source=%s app_code=%s event_id=%s: %w", candidate.SourceName, candidate.AppCode, candidate.EventID, err) + } + item.RoomEnvelope = envelope + default: + return nil, fmt.Errorf("unsupported source %q", candidate.SourceName) + } + items = append(items, item) + } + return items, nil +} + +func listDueDeliveries(ctx context.Context, db *sql.DB, eventIDs []string, appCodes []string, limit int, nowMS int64) ([]deliveryCandidate, error) { + if db == nil { + return nil, errors.New("notice database is not configured") + } + if limit <= 0 || limit > 500 { + return nil, errors.New("limit must be between 1 and 500") + } + apps := normalizeAppCodes(appCodes) + if len(apps) == 0 { + return nil, errors.New("at least one app_code is required for indexed preview") + } + if len(apps) > 32 { + return nil, errors.New("app_code filter count exceeds 32") + } + ids := normalizeEventIDs(eventIDs) + if len(ids) > 500 { + return nil, errors.New("event_id filter count exceeds 500") + } + candidates := make([]deliveryCandidate, 0, limit) + for _, sourceName := range []string{walletSource, roomSource} { + for _, appCodeValue := range apps { + branches := []struct { + indexName string + status string + dueColumn string + }{ + {indexName: "idx_notice_delivery_retry", status: "retryable", dueColumn: "next_retry_at_ms"}, + {indexName: "idx_notice_delivery_lock", status: "delivering", dueColumn: "lock_until_ms"}, + } + for _, branch := range branches { + query, args := dueDeliveryBranch(sourceName, appCodeValue, branch.indexName, branch.status, branch.dueColumn, ids, limit, nowMS) + branchCandidates, err := queryDueDeliveryCandidates(ctx, db, query, args...) + if err != nil { + return nil, err + } + candidates = append(candidates, branchCandidates...) + } + } + } + sort.Slice(candidates, func(i, j int) bool { + if candidates[i].UpdatedAtMS != candidates[j].UpdatedAtMS { + return candidates[i].UpdatedAtMS < candidates[j].UpdatedAtMS + } + return candidates[i].Selector < candidates[j].Selector + }) + if len(candidates) > limit { + candidates = candidates[:limit] + } + return candidates, nil +} + +func loadExactDueDeliveries(ctx context.Context, db *sql.DB, selectors []deliverySelector, nowMS int64) ([]deliveryCandidate, error) { + if db == nil { + return nil, errors.New("notice database is not configured") + } + normalized, err := normalizeDeliverySelectors(selectors) + if err != nil { + return nil, err + } + candidates := make([]deliveryCandidate, 0, len(selectors)) + for _, selector := range normalized { + key := selector.String() + var candidate deliveryCandidate + err = db.QueryRowContext(ctx, ` + SELECT source_name, app_code, source_event_id, status, retry_count, updated_at_ms + FROM notice_delivery_events FORCE INDEX (PRIMARY) + WHERE source_name = ? AND app_code = ? AND source_event_id = ? AND channel = ? + AND ((status = 'retryable' AND next_retry_at_ms <= ?) + OR (status = 'delivering' AND lock_until_ms <= ?))`, + selector.SourceName, + selector.AppCode, + selector.EventID, + c2cChannel, + nowMS, + nowMS, + ).Scan(&candidate.SourceName, &candidate.AppCode, &candidate.EventID, &candidate.Status, &candidate.RetryCount, &candidate.UpdatedAtMS) + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("delivery %q does not exist or is no longer due", key) + } + if err != nil { + return nil, err + } + if candidate.SourceName != selector.SourceName || candidate.AppCode != selector.AppCode || candidate.EventID != selector.EventID { + return nil, fmt.Errorf("delivery %q returned a mismatched primary key", key) + } + candidate.Selector = key + candidates = append(candidates, candidate) + } + return candidates, nil +} + +func normalizeDeliverySelectors(values []deliverySelector) ([]deliverySelector, error) { + normalized := make([]deliverySelector, 0, len(values)) + seen := make(map[string]struct{}, len(values)) + for _, value := range values { + selector := deliverySelector{ + SourceName: strings.TrimSpace(value.SourceName), + AppCode: strings.TrimSpace(value.AppCode), + EventID: strings.TrimSpace(value.EventID), + } + if selector.SourceName != walletSource && selector.SourceName != roomSource { + return nil, fmt.Errorf("unsupported delivery source %q", selector.SourceName) + } + if selector.AppCode == "" || selector.EventID == "" { + return nil, errors.New("delivery app_code and event_id are required") + } + selector.AppCode = appcode.Normalize(selector.AppCode) + key := selector.String() + if _, exists := seen[key]; exists { + return nil, fmt.Errorf("duplicate delivery selector %q", key) + } + seen[key] = struct{}{} + normalized = append(normalized, selector) + } + return normalized, nil +} + +func dueDeliveryBranch(sourceName string, appCodeValue string, indexName string, status string, dueColumn string, eventIDs []string, limit int, nowMS int64) (string, []any) { + // Callers pass only compile-time index/status/column tuples. Splitting status + // branches gives MySQL the complete (source,app,channel,status,due_time) prefix. + query := ` + SELECT source_name, app_code, source_event_id, status, retry_count, updated_at_ms + FROM notice_delivery_events FORCE INDEX (` + indexName + `) + WHERE source_name = ? AND app_code = ? AND channel = ? AND status = ? + AND ` + dueColumn + ` <= ?` + args := []any{sourceName, appCodeValue, c2cChannel, status, nowMS} + if len(eventIDs) > 0 { + query += " AND source_event_id IN (" + placeholders(len(eventIDs)) + ")" + for _, eventID := range eventIDs { + args = append(args, eventID) + } + } + query += " ORDER BY updated_at_ms ASC, source_event_id ASC LIMIT ?" + args = append(args, limit) + return query, args +} + +func queryDueDeliveryCandidates(ctx context.Context, db *sql.DB, query string, args ...any) ([]deliveryCandidate, error) { + rows, err := db.QueryContext(ctx, query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + candidates := make([]deliveryCandidate, 0) + for rows.Next() { + var candidate deliveryCandidate + if err := rows.Scan(&candidate.SourceName, &candidate.AppCode, &candidate.EventID, &candidate.Status, &candidate.RetryCount, &candidate.UpdatedAtMS); err != nil { + return nil, err + } + candidate.Selector = deliverySelector{SourceName: candidate.SourceName, AppCode: candidate.AppCode, EventID: candidate.EventID}.String() + candidates = append(candidates, candidate) + } + return candidates, rows.Err() +} + +func normalizeAppCodes(values []string) []string { + normalized := make([]string, 0, len(values)) + seen := make(map[string]struct{}, len(values)) + for _, value := range values { + if strings.TrimSpace(value) == "" { + continue + } + value = appcode.Normalize(value) + if _, exists := seen[value]; exists { + continue + } + seen[value] = struct{}{} + normalized = append(normalized, value) + } + return normalized +} + +func normalizeEventIDs(values []string) []string { + normalized := make([]string, 0, len(values)) + seen := make(map[string]struct{}, len(values)) + for _, value := range values { + value = strings.TrimSpace(value) + if value == "" { + continue + } + if _, exists := seen[value]; exists { + continue + } + seen[value] = struct{}{} + normalized = append(normalized, value) + } + return normalized +} + +func requireDelivered(ctx context.Context, db *sql.DB, candidate deliveryCandidate) error { + var status string + err := db.QueryRowContext(ctx, ` + SELECT status + FROM notice_delivery_events + WHERE source_name = ? AND app_code = ? AND source_event_id = ? AND channel = ?`, + candidate.SourceName, + candidate.AppCode, + candidate.EventID, + c2cChannel, + ).Scan(&status) + if err != nil { + return err + } + if status != "delivered" { + return fmt.Errorf("delivery status is %q after replay", status) + } + return nil +} + +func walletWorkerOptions(cfg config.Config) walletnotice.WalletNoticeWorkerOptions { + worker := cfg.WalletNoticeWorker + return walletnotice.WalletNoticeWorkerOptions{ + WorkerID: "wallet-notice-replay-" + cfg.NodeID, + BatchSize: worker.BatchSize, + LockTTL: worker.LockTTL, + PublishTimeout: worker.PublishTimeout, + MaxRetryCount: worker.MaxRetryCount, + InitialBackoff: worker.InitialBackoff, + MaxBackoff: worker.MaxBackoff, + } +} + +func roomWorkerOptions(cfg config.Config) roomnotice.RoomNoticeWorkerOptions { + worker := cfg.RoomNoticeWorker + return roomnotice.RoomNoticeWorkerOptions{ + WorkerID: "room-notice-replay-" + cfg.NodeID, + BatchSize: worker.BatchSize, + LockTTL: worker.LockTTL, + PublishTimeout: worker.PublishTimeout, + MaxRetryCount: worker.MaxRetryCount, + InitialBackoff: worker.InitialBackoff, + MaxBackoff: worker.MaxBackoff, + } +} + +func placeholders(count int) string { + values := make([]string, count) + for index := range values { + values[index] = "?" + } + return strings.Join(values, ",") +} diff --git a/services/notice-service/cmd/replay-deliveries/main_test.go b/services/notice-service/cmd/replay-deliveries/main_test.go new file mode 100644 index 00000000..d651ea87 --- /dev/null +++ b/services/notice-service/cmd/replay-deliveries/main_test.go @@ -0,0 +1,112 @@ +package main + +import ( + "context" + "database/sql" + "regexp" + "strings" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestDeliverySelectorFlagKeepsColonInsideEventID(t *testing.T) { + var values deliverySelectorFlag + if err := values.Set("wallet_outbox: FAMI :wallet:balance:evt-1"); err != nil { + t.Fatalf("parse exact delivery selector: %v", err) + } + if len(values) != 1 || values[0].SourceName != walletSource || values[0].AppCode != "fami" || values[0].EventID != "wallet:balance:evt-1" { + t.Fatalf("parsed selector mismatch: %+v", values) + } + if err := values.Set("unknown:lalu:event-2"); err == nil { + t.Fatal("unsupported source must be rejected before any database access") + } + if err := values.Set("room_outbox::event-3"); err == nil { + t.Fatal("empty app_code must not default to lalu") + } +} + +func TestLoadExactDueDeliveriesUsesFullPrimaryKey(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + nowMS := int64(1_700_000_000_000) + query := regexp.QuoteMeta(` + SELECT source_name, app_code, source_event_id, status, retry_count, updated_at_ms + FROM notice_delivery_events FORCE INDEX (PRIMARY) + WHERE source_name = ? AND app_code = ? AND source_event_id = ? AND channel = ? + AND ((status = 'retryable' AND next_retry_at_ms <= ?) + OR (status = 'delivering' AND lock_until_ms <= ?))`) + mock.ExpectQuery(query). + WithArgs(walletSource, "fami", "wallet:evt-1", c2cChannel, nowMS, nowMS). + WillReturnRows(sqlmock.NewRows([]string{"source_name", "app_code", "source_event_id", "status", "retry_count", "updated_at_ms"}). + AddRow(walletSource, "fami", "wallet:evt-1", "retryable", 2, nowMS-1000)) + + candidates, err := loadExactDueDeliveries(context.Background(), db, []deliverySelector{{SourceName: walletSource, AppCode: " FAMI ", EventID: "wallet:evt-1"}}, nowMS) + if err != nil { + t.Fatalf("load exact due delivery: %v", err) + } + if len(candidates) != 1 || candidates[0].Selector != "wallet_outbox:fami:wallet:evt-1" || candidates[0].RetryCount != 2 { + t.Fatalf("exact candidate mismatch: %+v", candidates) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("exact query did not use the expected primary key: %v", err) + } +} + +func TestLoadExactDueDeliveriesRejectsDuplicateBeforeQuery(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + selectors := []deliverySelector{ + {SourceName: roomSource, AppCode: "lalu", EventID: "evt-1"}, + {SourceName: roomSource, AppCode: " LALU ", EventID: "evt-1"}, + } + if _, err := loadExactDueDeliveries(context.Background(), db, selectors, 1000); err == nil || !strings.Contains(err.Error(), "duplicate") { + t.Fatalf("duplicate selector error = %v", err) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("duplicate validation unexpectedly queried MySQL: %v", err) + } +} + +func TestLoadExactDueDeliveriesRejectsNotDueRow(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + mock.ExpectQuery("FROM notice_delivery_events FORCE INDEX \\(PRIMARY\\)"). + WillReturnError(sql.ErrNoRows) + _, err = loadExactDueDeliveries(context.Background(), db, []deliverySelector{{SourceName: roomSource, AppCode: "lalu", EventID: "evt-1"}}, 1000) + if err == nil || !strings.Contains(err.Error(), "no longer due") { + t.Fatalf("not-due selector error = %v", err) + } +} + +func TestDueDeliveryBranchUsesCompleteRetryIndexPrefix(t *testing.T) { + query, args := dueDeliveryBranch(walletSource, "huwaa", "idx_notice_delivery_retry", "retryable", "next_retry_at_ms", []string{"evt-1", "evt-2"}, 20, 1234) + for _, fragment := range []string{ + "FORCE INDEX (idx_notice_delivery_retry)", + "source_name = ? AND app_code = ? AND channel = ? AND status = ?", + "next_retry_at_ms <= ?", + "source_event_id IN (?,?)", + } { + if !strings.Contains(query, fragment) { + t.Fatalf("indexed preview query is missing %q:\n%s", fragment, query) + } + } + wantArgs := []any{walletSource, "huwaa", c2cChannel, "retryable", int64(1234), "evt-1", "evt-2", 20} + if len(args) != len(wantArgs) { + t.Fatalf("preview args = %v, want %v", args, wantArgs) + } + for index := range args { + if args[index] != wantArgs[index] { + t.Fatalf("preview arg[%d] = %#v, want %#v", index, args[index], wantArgs[index]) + } + } +} diff --git a/services/notice-service/internal/app/app.go b/services/notice-service/internal/app/app.go index 20d95ea7..7c818272 100644 --- a/services/notice-service/internal/app/app.go +++ b/services/notice-service/internal/app/app.go @@ -148,13 +148,24 @@ func New(cfg config.Config) (*App, error) { confirmOptions := confirmNoticeWorkerOptions(cfg.ConfirmNoticeWorker) mqConsumers := make([]*rocketmqx.Consumer, 0, 4) if cfg.RocketMQ.WalletOutbox.Enabled { + // notice-service 只投递余额变化和 VIP 激活私信。保留 legacy Tag 是迁移期 + // 的零丢失边界,新 producer 的其他钱包事实由 Broker 直接过滤。 + walletTagExpression, err := walletmq.LegacyCompatibleTagExpression( + walletmq.EventTypeWalletBalanceChanged, + walletmq.EventTypeVIPActivated, + ) + if err != nil { + _ = listener.Close() + _ = store.Close() + return nil, err + } consumer, err := rocketmqx.NewConsumer(rocketMQConsumerConfig(cfg.RocketMQ, cfg.RocketMQ.WalletOutbox.ConsumerGroup, cfg.RocketMQ.WalletOutbox.ConsumerMaxReconsumeTimes)) if err != nil { _ = listener.Close() _ = store.Close() return nil, err } - if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletTagExpression, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { walletMessage, err := walletmq.DecodeWalletOutboxMessage(message.Body) if err != nil { return err @@ -170,6 +181,13 @@ func New(cfg config.Config) (*App, error) { mqConsumers = append(mqConsumers, consumer) } if cfg.RocketMQ.RoomOutbox.Enabled { + roomTagExpression, err := roommq.LegacyCompatibleTagExpression(roommq.EventTypeRoomUserKicked) + if err != nil { + shutdownConsumers(mqConsumers) + _ = listener.Close() + _ = store.Close() + return nil, err + } consumer, err := rocketmqx.NewConsumer(rocketMQConsumerConfig(cfg.RocketMQ, cfg.RocketMQ.RoomOutbox.ConsumerGroup, cfg.RocketMQ.RoomOutbox.ConsumerMaxReconsumeTimes)) if err != nil { shutdownConsumers(mqConsumers) @@ -177,7 +195,7 @@ func New(cfg config.Config) (*App, error) { _ = store.Close() return nil, err } - if err := consumer.Subscribe(cfg.RocketMQ.RoomOutbox.Topic, roommq.TagRoomOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + if err := consumer.Subscribe(cfg.RocketMQ.RoomOutbox.Topic, roomTagExpression, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { envelope, _, err := roommq.DecodeRoomOutboxMessage(message.Body) if err != nil { return err diff --git a/services/notice-service/internal/modules/cpnotice/service.go b/services/notice-service/internal/modules/cpnotice/service.go index 3712a5fd..abe70d60 100644 --- a/services/notice-service/internal/modules/cpnotice/service.go +++ b/services/notice-service/internal/modules/cpnotice/service.go @@ -86,9 +86,15 @@ func (s *Service) ProcessUserOutboxMessage(ctx context.Context, message usermq.U } for _, delivery := range deliveries { claimedDelivery, claimed, err := s.repository.ClaimCPDelivery(ctx, options.WorkerID, delivery, options.LockTTL) - if err != nil || !claimed { + if err != nil { return true, err } + if !claimed { + // 一条 user_outbox 事实会展开为多个按 channel 独立幂等的投递。当前 channel 已终态、 + // 被其他 worker 持有或尚未到重试时间时,只能跳过这一项;提前返回会把整条 MQ + // 消息确认掉,导致其后尚未落库或仍需补发的 channel 永久漏发。 + continue + } if err := s.publishDelivery(ctx, claimedDelivery, options); err != nil { return true, err } diff --git a/services/notice-service/internal/modules/cpnotice/service_test.go b/services/notice-service/internal/modules/cpnotice/service_test.go index 590e27f0..0821b35b 100644 --- a/services/notice-service/internal/modules/cpnotice/service_test.go +++ b/services/notice-service/internal/modules/cpnotice/service_test.go @@ -47,6 +47,39 @@ func TestProcessApplicationCreatedPublishesC2CAndBothRoomIMs(t *testing.T) { } } +func TestProcessApplicationCreatedContinuesAfterAlreadyFinalChannel(t *testing.T) { + // 模拟同一事实的 C2C 已在上一次消费中成功,而两个房间 channel 尚未处理;重放必须 + // 依赖 channel 级幂等跳过 C2C,并继续补齐后续副作用,不能把首个终态误判为整条完成。 + repo := &fakeRepository{notClaimedChannels: map[string]bool{channelTencentIMC2C: true}} + publisher := &fakePublisher{} + service := New(Config{NodeID: "notice-test"}, repo, publisher) + + handled, err := service.ProcessUserOutboxMessage(context.Background(), usermq.UserOutboxMessage{ + AppCode: "lalu", + EventID: "cp_application:created:1002:partial-replay", + EventType: eventApplicationCreated, + AggregateType: "cp_application", + AggregateID: 1002, + PayloadJSON: applicationPayloadJSON(), + OccurredAtMS: 1700000000000, + }, CPNoticeWorkerOptions{PublishTimeout: time.Second}) + if err != nil || !handled { + t.Fatalf("ProcessUserOutboxMessage failed handled=%v err=%v", handled, err) + } + if len(repo.claimedChannels) != 3 { + t.Fatalf("expected every channel to be inspected, got %+v", repo.claimedChannels) + } + if len(publisher.userMessages) != 0 { + t.Fatalf("already delivered c2c must not be published again: %+v", publisher.userMessages) + } + if len(publisher.groupMessages) != 2 || publisher.groupMessages[0].GroupID != "room-100" || publisher.groupMessages[1].GroupID != "room-100" { + t.Fatalf("expected both remaining room deliveries, got %+v", publisher.groupMessages) + } + if len(repo.delivered) != 2 || repo.delivered[0].Channel != channelTencentIMRoom || repo.delivered[1].Channel != channelTencentIMRoomSender { + t.Fatalf("expected only remaining channels to become delivered, got %+v", repo.delivered) + } +} + func TestProcessApplicationAcceptedPublishesMutualC2CAndRoomIM(t *testing.T) { repo := &fakeRepository{} publisher := &fakePublisher{} @@ -187,12 +220,18 @@ func TestPublishFailureMarksRetryable(t *testing.T) { } type fakeRepository struct { - delivered []CPDelivery - retryable []CPDelivery - failed []CPDelivery + notClaimedChannels map[string]bool + claimedChannels []string + delivered []CPDelivery + retryable []CPDelivery + failed []CPDelivery } func (r *fakeRepository) ClaimCPDelivery(_ context.Context, _ string, delivery CPDelivery, _ time.Duration) (CPDelivery, bool, error) { + r.claimedChannels = append(r.claimedChannels, delivery.Channel) + if r.notClaimedChannels[delivery.Channel] { + return CPDelivery{}, false, nil + } return delivery, true, nil } diff --git a/services/notice-service/internal/modules/roomnotice/mysql_repository.go b/services/notice-service/internal/modules/roomnotice/mysql_repository.go index 8181f24f..b1b46bdd 100644 --- a/services/notice-service/internal/modules/roomnotice/mysql_repository.go +++ b/services/notice-service/internal/modules/roomnotice/mysql_repository.go @@ -251,6 +251,47 @@ func (r *MySQLRepository) ClaimRoomKickEnvelope(ctx context.Context, workerID st return event, true, nil } +// LoadRoomOutboxEnvelope loads one immutable owner event for an exact offline +// replay. Requiring the full primary key prevents repair tooling from scanning or +// claiming room-service state that was not selected by an operator. +func (r *MySQLRepository) LoadRoomOutboxEnvelope(ctx context.Context, appCode string, eventID string) (*roomeventsv1.EventEnvelope, error) { + if r == nil || r.db == nil { + return nil, xerr.New(xerr.Unavailable, "notice repository is not configured") + } + appCode = strings.TrimSpace(appCode) + eventID = strings.TrimSpace(eventID) + if appCode == "" || eventID == "" { + return nil, xerr.New(xerr.InvalidArgument, "app_code and event_id are required") + } + appCode = appcode.Normalize(appCode) + + var ( + ownerRoomID string + envelopeBytes []byte + ) + query := fmt.Sprintf(` + SELECT room_id, envelope + FROM %s + WHERE app_code = ? AND event_id = ? AND event_type = ?`, r.roomOutboxTable) + if err := r.db.QueryRowContext(ctx, query, appCode, eventID, eventRoomUserKicked).Scan(&ownerRoomID, &envelopeBytes); err != nil { + return nil, err + } + var envelope roomeventsv1.EventEnvelope + if err := proto.Unmarshal(envelopeBytes, &envelope); err != nil { + return nil, err + } + // Validate both the requested identity and the current consumer contract. A + // mismatched historical blob must never be delivered under another event's + // notice_delivery_events key. + if envelope.GetAppCode() != appCode || envelope.GetEventId() != eventID || envelope.GetRoomId() != ownerRoomID { + return nil, fmt.Errorf("room outbox envelope identity does not match requested key") + } + if _, err := roomKickEventFromEnvelope(&envelope, envelope.GetOccurredAtMs()); err != nil { + return nil, err + } + return &envelope, nil +} + func (r *MySQLRepository) lockRoomKickEvent(ctx context.Context, tx *sql.Tx, candidate roomKickCandidate) (RoomKickEvent, error) { var ( event RoomKickEvent diff --git a/services/notice-service/internal/modules/roomnotice/replay_repository_test.go b/services/notice-service/internal/modules/roomnotice/replay_repository_test.go new file mode 100644 index 00000000..b5b3f23e --- /dev/null +++ b/services/notice-service/internal/modules/roomnotice/replay_repository_test.go @@ -0,0 +1,68 @@ +package roomnotice + +import ( + "context" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "google.golang.org/protobuf/proto" + roomeventsv1 "hyapp.local/api/proto/events/room/v1" +) + +func TestLoadRoomOutboxEnvelopeRejectsEmptyAppInsteadOfDefaultingToLalu(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + repository := &MySQLRepository{db: db, roomOutboxTable: "`hyapp_room`.`room_outbox`"} + if _, err := repository.LoadRoomOutboxEnvelope(context.Background(), "", "evt-1"); err == nil { + t.Fatal("empty app_code must be rejected before owner table access") + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("empty key unexpectedly queried room owner table: %v", err) + } +} + +func TestLoadRoomOutboxEnvelopeUsesExactImmutableOwnerEnvelope(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + body, err := proto.Marshal(&roomeventsv1.RoomUserKicked{ActorUserId: 7, TargetUserId: 42}) + if err != nil { + t.Fatalf("marshal room kick body: %v", err) + } + envelopeBytes, err := proto.Marshal(&roomeventsv1.EventEnvelope{ + AppCode: "huwaa", + EventId: "room:kick:evt-1", + EventType: eventRoomUserKicked, + RoomId: "room-1", + RoomVersion: 9, + OccurredAtMs: 1700000000000, + Body: body, + }) + if err != nil { + t.Fatalf("marshal room envelope: %v", err) + } + repository := &MySQLRepository{db: db, roomOutboxTable: "`hyapp_room`.`room_outbox`"} + mock.ExpectQuery(regexp.QuoteMeta(` + SELECT room_id, envelope + FROM `+"`hyapp_room`.`room_outbox`"+` + WHERE app_code = ? AND event_id = ? AND event_type = ?`)). + WithArgs("huwaa", "room:kick:evt-1", eventRoomUserKicked). + WillReturnRows(sqlmock.NewRows([]string{"room_id", "envelope"}).AddRow("room-1", envelopeBytes)) + + envelope, err := repository.LoadRoomOutboxEnvelope(context.Background(), " HUWAA ", "room:kick:evt-1") + if err != nil { + t.Fatalf("load exact room owner envelope: %v", err) + } + if envelope.GetAppCode() != "huwaa" || envelope.GetEventId() != "room:kick:evt-1" || envelope.GetRoomId() != "room-1" { + t.Fatalf("room owner snapshot mismatch: %+v", envelope) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("room replay query mismatch: %v", err) + } +} diff --git a/services/notice-service/internal/modules/walletnotice/mysql_repository.go b/services/notice-service/internal/modules/walletnotice/mysql_repository.go index ab2441aa..665ed4fd 100644 --- a/services/notice-service/internal/modules/walletnotice/mysql_repository.go +++ b/services/notice-service/internal/modules/walletnotice/mysql_repository.go @@ -18,8 +18,8 @@ import ( const ( sourceWalletOutbox = "wallet_outbox" channelTencentIMC2C = "tencent_im_c2c" - eventWalletBalance = "WalletBalanceChanged" - eventVIPActivated = "VipActivated" + eventWalletBalance = walletmq.EventTypeWalletBalanceChanged + eventVIPActivated = walletmq.EventTypeVIPActivated robotCoinAssetType = "ROBOT_COIN" deliveryStatusDelivering = "delivering" deliveryStatusDelivered = "delivered" @@ -256,6 +256,61 @@ func (r *MySQLRepository) ClaimWalletBalanceMessage(ctx context.Context, workerI return event, true, nil } +// LoadWalletOutboxMessage loads one immutable owner event for an operator-selected +// delivery replay. The exact (app_code, event_id) key is mandatory so an offline +// repair cannot accidentally turn into an unbounded wallet_outbox scan. +func (r *MySQLRepository) LoadWalletOutboxMessage(ctx context.Context, appCode string, eventID string) (walletmq.WalletOutboxMessage, error) { + if r == nil || r.db == nil { + return walletmq.WalletOutboxMessage{}, xerr.New(xerr.Unavailable, "notice repository is not configured") + } + appCode = strings.TrimSpace(appCode) + eventID = strings.TrimSpace(eventID) + if appCode == "" || eventID == "" { + return walletmq.WalletOutboxMessage{}, xerr.New(xerr.InvalidArgument, "app_code and event_id are required") + } + appCode = appcode.Normalize(appCode) + + message := walletmq.WalletOutboxMessage{MessageType: walletmq.MessageTypeWalletOutboxEvent} + query := fmt.Sprintf(` + SELECT app_code, event_id, event_type, transaction_id, command_id, user_id, + asset_type, available_delta, frozen_delta, + COALESCE(CAST(payload AS CHAR), '{}'), created_at_ms + FROM %s + WHERE app_code = ? AND event_id = ? + AND event_type IN (?, ?) AND asset_type <> ?`, r.walletOutboxTable) + err := r.db.QueryRowContext(ctx, query, appCode, eventID, eventWalletBalance, eventVIPActivated, robotCoinAssetType).Scan( + &message.AppCode, + &message.EventID, + &message.EventType, + &message.TransactionID, + &message.CommandID, + &message.UserID, + &message.AssetType, + &message.AvailableDelta, + &message.FrozenDelta, + &message.PayloadJSON, + &message.OccurredAtMS, + ) + if err != nil { + return walletmq.WalletOutboxMessage{}, err + } + // Reuse the live MQ decoder boundary before any external side effect. A + // malformed historical row must stay retryable instead of being sent with a + // subtly different repair-only payload. + encoded, err := walletmq.EncodeWalletOutboxMessage(message) + if err != nil { + return walletmq.WalletOutboxMessage{}, err + } + decoded, err := walletmq.DecodeWalletOutboxMessage(encoded) + if err != nil { + return walletmq.WalletOutboxMessage{}, err + } + if decoded.AppCode != appCode || decoded.EventID != eventID { + return walletmq.WalletOutboxMessage{}, fmt.Errorf("wallet outbox message identity does not match requested key") + } + return decoded, nil +} + func walletBalanceEventFromMessage(message walletmq.WalletOutboxMessage) (WalletBalanceEvent, error) { event := WalletBalanceEvent{ AppCode: appcode.Normalize(message.AppCode), diff --git a/services/notice-service/internal/modules/walletnotice/replay_repository_test.go b/services/notice-service/internal/modules/walletnotice/replay_repository_test.go new file mode 100644 index 00000000..807a5b1c --- /dev/null +++ b/services/notice-service/internal/modules/walletnotice/replay_repository_test.go @@ -0,0 +1,56 @@ +package walletnotice + +import ( + "context" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestLoadWalletOutboxMessageRejectsEmptyAppInsteadOfDefaultingToLalu(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + repository := &MySQLRepository{db: db, walletOutboxTable: "`hyapp_wallet`.`wallet_outbox`"} + if _, err := repository.LoadWalletOutboxMessage(context.Background(), " ", "evt-1"); err == nil { + t.Fatal("empty app_code must be rejected before owner table access") + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("empty key unexpectedly queried wallet owner table: %v", err) + } +} + +func TestLoadWalletOutboxMessageUsesExactImmutablePrivateFact(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + repository := &MySQLRepository{db: db, walletOutboxTable: "`hyapp_wallet`.`wallet_outbox`"} + mock.ExpectQuery(regexp.QuoteMeta(` + SELECT app_code, event_id, event_type, transaction_id, command_id, user_id, + asset_type, available_delta, frozen_delta, + COALESCE(CAST(payload AS CHAR), '{}'), created_at_ms + FROM `+"`hyapp_wallet`.`wallet_outbox`"+` + WHERE app_code = ? AND event_id = ? + AND event_type IN (?, ?) AND asset_type <> ?`)). + WithArgs("fami", "wallet:evt-1", eventWalletBalance, eventVIPActivated, robotCoinAssetType). + WillReturnRows(sqlmock.NewRows([]string{ + "app_code", "event_id", "event_type", "transaction_id", "command_id", "user_id", + "asset_type", "available_delta", "frozen_delta", "payload", "created_at_ms", + }).AddRow("fami", "wallet:evt-1", eventWalletBalance, "tx-1", "cmd-1", int64(42), "COIN", int64(100), int64(0), `{"balance_after":100}`, int64(1700000000000))) + + message, err := repository.LoadWalletOutboxMessage(context.Background(), " FAMI ", "wallet:evt-1") + if err != nil { + t.Fatalf("load exact wallet owner fact: %v", err) + } + if message.AppCode != "fami" || message.EventID != "wallet:evt-1" || message.UserID != 42 || message.PayloadJSON != `{"balance_after":100}` { + t.Fatalf("wallet owner snapshot mismatch: %+v", message) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("wallet replay query mismatch: %v", err) + } +} diff --git a/services/notice-service/internal/modules/walletnotice/service.go b/services/notice-service/internal/modules/walletnotice/service.go index c0647eb8..2dd1d8a8 100644 --- a/services/notice-service/internal/modules/walletnotice/service.go +++ b/services/notice-service/internal/modules/walletnotice/service.go @@ -59,6 +59,15 @@ func (s *Service) ProcessWalletBalanceNotices(ctx context.Context, options Walle } processed := 0 for _, event := range events { + if walletPrivateNoticeSuppressed(event.AssetType, event.PayloadJSON) { + // owner 事件已经固化“不投私信”时,把本地 delivery 位点收敛为 delivered; + // 这条终态既阻止 pull fallback 反复认领,也不需要 notice 反查 user-service 猜账号类型。 + if err := s.repository.MarkWalletBalanceDelivered(ctx, event, json.RawMessage(event.PayloadJSON), time.Now().UnixMilli()); err != nil { + return processed, err + } + processed++ + continue + } if err := s.publishWalletBalanceEvent(ctx, event, options); err != nil { return processed, err } @@ -73,8 +82,9 @@ func (s *Service) ProcessWalletOutboxMessage(ctx context.Context, message wallet if !isWalletPrivateNoticeEvent(message.EventType) { return false, nil } - if isRobotCoinWalletNotice(message.AssetType) { - // ROBOT_COIN 只服务机器人房间展示扣费,机器人账号不需要也不能接收用户 C2C 余额通知。 + if walletPrivateNoticeSuppressed(message.AssetType, message.PayloadJSON) { + // MQ 主链路在写 notice delivery 前确认 owner 快照;ROBOT_COIN 是历史兼容条件, + // suppress_private_notice 才是不依赖资产类型的显式契约。 return true, nil } options = normalizeWalletNoticeWorkerOptions(options, s.cfg.NodeID) @@ -170,6 +180,21 @@ func isRobotCoinWalletNotice(assetType string) bool { return assetType == robotCoinAssetType } +func walletPrivateNoticeSuppressed(assetType string, payloadJSON string) bool { + if isRobotCoinWalletNotice(assetType) { + return true + } + var control struct { + SuppressPrivateNotice bool `json:"suppress_private_notice"` + } + // 旧事件没有控制字段时保持原投递语义;非法 payload 也不能在这里被静默确认, + // 后续正常 payload 校验会把它收敛到 retry/failed,保留可观测性。 + if err := json.Unmarshal([]byte(payloadJSON), &control); err != nil { + return false + } + return control.SuppressPrivateNotice +} + func walletBalanceNoticePayload(event WalletBalanceEvent) (json.RawMessage, error) { var payload map[string]any if err := json.Unmarshal([]byte(event.PayloadJSON), &payload); err != nil { diff --git a/services/notice-service/internal/modules/walletnotice/service_test.go b/services/notice-service/internal/modules/walletnotice/service_test.go index 73425bc7..91d6ac25 100644 --- a/services/notice-service/internal/modules/walletnotice/service_test.go +++ b/services/notice-service/internal/modules/walletnotice/service_test.go @@ -140,6 +140,56 @@ func TestProcessWalletOutboxMessageSkipsRobotCoinPrivateIM(t *testing.T) { } } +func TestProcessWalletOutboxMessageSkipsOwnerSuppressedPrivateIM(t *testing.T) { + repo := &fakeRepository{} + publisher := &fakePublisher{} + service := New(Config{NodeID: "node-a"}, repo, publisher) + + handled, err := service.ProcessWalletOutboxMessage(context.Background(), walletmq.WalletOutboxMessage{ + AppCode: "huwaa", + EventID: "suppressed-wallet-1", + EventType: "WalletBalanceChanged", + TransactionID: "suppressed-tx-1", + CommandID: "suppressed-cmd-1", + UserID: 100002, + AssetType: "COIN", + AvailableDelta: -1, + PayloadJSON: `{"available_after":999,"suppress_private_notice":true}`, + OccurredAtMS: 1710000000000, + }, WalletNoticeWorkerOptions{MaxRetryCount: 3}) + if err != nil { + t.Fatalf("ProcessWalletOutboxMessage failed: %v", err) + } + if !handled || len(repo.claimedMessages) != 0 || len(repo.delivered) != 0 || len(publisher.messages) != 0 { + t.Fatalf("owner-suppressed notice must be acked before delivery claim: handled=%v claimed=%d delivered=%d messages=%d", handled, len(repo.claimedMessages), len(repo.delivered), len(publisher.messages)) + } +} + +func TestProcessWalletBalanceNoticesTerminatesOwnerSuppressedFallback(t *testing.T) { + repo := &fakeRepository{events: []WalletBalanceEvent{{ + AppCode: "huwaa", + EventID: "suppressed-wallet-fallback-1", + EventType: "WalletBalanceChanged", + TransactionID: "suppressed-tx-fallback-1", + CommandID: "suppressed-cmd-fallback-1", + UserID: 100003, + AssetType: "COIN", + AvailableDelta: -1, + PayloadJSON: `{"available_after":999,"suppress_private_notice":true}`, + CreatedAtMS: 1710000000000, + }}} + publisher := &fakePublisher{} + service := New(Config{NodeID: "node-a"}, repo, publisher) + + processed, err := service.ProcessWalletBalanceNotices(context.Background(), WalletNoticeWorkerOptions{MaxRetryCount: 3}) + if err != nil { + t.Fatalf("ProcessWalletBalanceNotices failed: %v", err) + } + if processed != 1 || len(repo.delivered) != 1 || len(publisher.messages) != 0 { + t.Fatalf("fallback suppression must terminate locally without C2C: processed=%d delivered=%d messages=%d", processed, len(repo.delivered), len(publisher.messages)) + } +} + func TestProcessWalletBalanceNoticesMarksRetryable(t *testing.T) { repo := &fakeRepository{ events: []WalletBalanceEvent{{ diff --git a/services/room-service/configs/config.docker.yaml b/services/room-service/configs/config.docker.yaml index a2979e78..c6e545ec 100644 --- a/services/room-service/configs/config.docker.yaml +++ b/services/room-service/configs/config.docker.yaml @@ -64,6 +64,8 @@ rocketmq: namespace: "" send_timeout: "3s" retry: 2 + # Phase A 保持 legacy;所有 room consumer 完成兼容滚动后才允许切换为 event_type。 + room_event_tag_mode: "legacy" room_outbox: enabled: true topic: "hyapp_room_outbox" @@ -79,6 +81,12 @@ rocketmq: topic: "hyapp_user_outbox" consumer_group: "hyapp-room-user-region-sync" consumer_max_reconsume_times: 16 + # 房间中奖 IM 由 lucky-gift owner 事实可靠派生;独立 group 保证不和其他下游共享消费位点。 + lucky_gift_outbox: + enabled: true + topic: "hyapp_lucky_gift_outbox" + consumer_group: "hyapp-room-lucky-gift-outbox" + consumer_max_reconsume_times: 16 outbox_worker: # Docker 和 testbox 走 MQ;room-service 同进程启动 IM bridge consumer 补偿房间群消息。 enabled: true diff --git a/services/room-service/configs/config.tencent.example.yaml b/services/room-service/configs/config.tencent.example.yaml index 29cb8a9e..d685ddd7 100644 --- a/services/room-service/configs/config.tencent.example.yaml +++ b/services/room-service/configs/config.tencent.example.yaml @@ -74,6 +74,8 @@ rocketmq: namespace: "hyapp-prod" send_timeout: "3s" retry: 2 + # Phase A 必须保持 legacy;Phase B 经消费者订阅和存量积压验证后,仅将该值切为 event_type。 + room_event_tag_mode: "legacy" room_outbox: enabled: true topic: "hyapp_room_outbox" @@ -89,6 +91,12 @@ rocketmq: topic: "hyapp_user_outbox" consumer_group: "hyapp-room-user-region-sync" consumer_max_reconsume_times: 16 + # 线上必须使用 room-service 自己的消费组;owner outbox 只通过 MQ 进入房间展示链路。 + lucky_gift_outbox: + enabled: true + topic: "hyapp_lucky_gift_outbox" + consumer_group: "hyapp-room-lucky-gift-outbox" + consumer_max_reconsume_times: 16 outbox_worker: # mq 模式下 outbox worker 只把事实投 MQ,并顺带安排火箭延迟发射唤醒。 enabled: true diff --git a/services/room-service/configs/config.yaml b/services/room-service/configs/config.yaml index 764cfa8a..7cef1f31 100644 --- a/services/room-service/configs/config.yaml +++ b/services/room-service/configs/config.yaml @@ -67,6 +67,8 @@ rocketmq: namespace: "" send_timeout: "3s" retry: 2 + # Phase A 保持 legacy;确认所有 room consumer 已部署 legacy||typed 订阅后,Phase B 才改为 event_type。 + room_event_tag_mode: "legacy" room_outbox: enabled: true topic: "hyapp_room_outbox" @@ -82,6 +84,12 @@ rocketmq: topic: "hyapp_user_outbox" consumer_group: "hyapp-room-user-region-sync" consumer_max_reconsume_times: 16 + # 幸运礼物 owner 事实由独立消费组派生为房间展示 outbox;不能复用 activity 消费位点或直连幸运礼物库。 + lucky_gift_outbox: + enabled: true + topic: "hyapp_lucky_gift_outbox" + consumer_group: "hyapp-room-lucky-gift-outbox" + consumer_max_reconsume_times: 16 outbox_worker: # room_outbox 是腾讯云 IM 和 activity-service 的异步投递通道;本地也走 MQ,避免和测试/线上链路漂移。 enabled: true diff --git a/services/room-service/deploy/mysql/initdb/001_room_service.sql b/services/room-service/deploy/mysql/initdb/001_room_service.sql index 6e4b3f52..68a02559 100644 --- a/services/room-service/deploy/mysql/initdb/001_room_service.sql +++ b/services/room-service/deploy/mysql/initdb/001_room_service.sql @@ -25,6 +25,7 @@ CREATE TABLE IF NOT EXISTS rooms ( created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', PRIMARY KEY (app_code, room_id), + UNIQUE KEY uk_rooms_room_id (room_id), UNIQUE KEY uk_rooms_short_id (app_code, room_short_id), UNIQUE KEY uk_rooms_owner_user (app_code, owner_user_id), KEY idx_rooms_region_status (app_code, visible_region_id, status) @@ -143,12 +144,35 @@ CREATE TABLE IF NOT EXISTS room_command_log ( owner_node_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '房主节点 ID', lease_token VARCHAR(128) NOT NULL DEFAULT '' COMMENT '租约令牌', payload LONGBLOB NOT NULL COMMENT '业务负载快照', + result_payload LONGBLOB NULL COMMENT '首次提交的 typed 结果,用于幂等原样重放', replayable BOOLEAN NOT NULL COMMENT '是否可重放', created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', UNIQUE KEY uk_room_command (app_code, room_id, command_id), KEY idx_room_command_replay (app_code, room_id, room_version) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Room Cell 命令日志表'; +CREATE TABLE IF NOT EXISTS room_gift_operations ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + room_id VARCHAR(64) NOT NULL COMMENT '房间 ID', + command_id VARCHAR(128) NOT NULL COMMENT '送礼全链路幂等 ID', + actor_user_id BIGINT NOT NULL COMMENT '送礼用户 ID', + command_payload LONGBLOB NOT NULL COMMENT '规范化 Room 命令,供载荷冲突校验', + request_payload LONGBLOB NOT NULL COMMENT '原始 SendGiftRequest protobuf,供服务端恢复', + status VARCHAR(32) NOT NULL COMMENT 'pending/debited/drawn/committed/compensated', + billing_payload LONGBLOB NULL COMMENT '钱包稳定回执 protobuf', + lucky_payload LONGBLOB NULL COMMENT '幸运礼物聚合结果 protobuf', + result_payload LONGBLOB NULL COMMENT '首次提交的 SendGiftResultV2 protobuf', + last_error TEXT NULL COMMENT '最近一次恢复错误', + retry_count INT NOT NULL DEFAULT 0 COMMENT '服务端恢复次数', + next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '下次允许恢复时间,UTC epoch ms', + worker_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '当前恢复 worker', + lock_until_ms BIGINT NULL COMMENT '恢复租约截止时间,UTC epoch ms', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, room_id, command_id), + KEY idx_room_gift_operation_recovery (status, next_retry_at_ms, lock_until_ms, updated_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='送礼跨钱包抽奖和房间提交 Saga'; + CREATE TABLE IF NOT EXISTS room_outbox ( app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', event_id VARCHAR(128) NOT NULL COMMENT '事件幂等 ID', diff --git a/services/room-service/deploy/mysql/migrations/002_gift_operation_v2.sql b/services/room-service/deploy/mysql/migrations/002_gift_operation_v2.sql new file mode 100644 index 00000000..0d4ffffb --- /dev/null +++ b/services/room-service/deploy/mysql/migrations/002_gift_operation_v2.sql @@ -0,0 +1,26 @@ +-- SendGift V2 需要把首次成功结果和扣款后的恢复状态固化在 room-service owner 库。 +-- 线上先执行该迁移再发布新 room-service;旧版本不会读取新增列和表,迁移可向前兼容。 +ALTER TABLE room_command_log + ADD COLUMN result_payload LONGBLOB NULL COMMENT '首次提交的 typed 结果,用于幂等原样重放' AFTER payload; + +CREATE TABLE IF NOT EXISTS room_gift_operations ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + room_id VARCHAR(64) NOT NULL COMMENT '房间 ID', + command_id VARCHAR(128) NOT NULL COMMENT '送礼全链路幂等 ID', + actor_user_id BIGINT NOT NULL COMMENT '送礼用户 ID', + command_payload LONGBLOB NOT NULL COMMENT '规范化 Room 命令,供载荷冲突校验', + request_payload LONGBLOB NOT NULL COMMENT '原始 SendGiftRequest protobuf,供服务端恢复', + status VARCHAR(32) NOT NULL COMMENT 'pending/debited/drawn/committed/compensated', + billing_payload LONGBLOB NULL COMMENT '钱包稳定回执 protobuf', + lucky_payload LONGBLOB NULL COMMENT '幸运礼物聚合结果 protobuf', + result_payload LONGBLOB NULL COMMENT '首次提交的 SendGiftResultV2 protobuf', + last_error TEXT NULL COMMENT '最近一次恢复错误', + retry_count INT NOT NULL DEFAULT 0 COMMENT '服务端恢复次数', + next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '下次允许恢复时间,UTC epoch ms', + worker_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '当前恢复 worker', + lock_until_ms BIGINT NULL COMMENT '恢复租约截止时间,UTC epoch ms', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, room_id, command_id), + KEY idx_room_gift_operation_recovery (status, next_retry_at_ms, lock_until_ms, updated_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='送礼跨钱包抽奖和房间提交 Saga'; diff --git a/services/room-service/internal/app/app.go b/services/room-service/internal/app/app.go index 8ed4ba4c..dc4505ac 100644 --- a/services/room-service/internal/app/app.go +++ b/services/room-service/internal/app/app.go @@ -15,6 +15,7 @@ import ( roomv1 "hyapp.local/api/proto/room/v1" "hyapp/pkg/healthhttp" "hyapp/pkg/logx" + "hyapp/pkg/luckygiftmq" "hyapp/pkg/rocketmqx" "hyapp/pkg/roommq" serviceapp "hyapp/pkg/servicekit/app" @@ -65,7 +66,7 @@ type App struct { healthHTTP *healthhttp.Server // mqProducers 在 outbox worker 启动前打开,承载 fanout 和延迟发射唤醒。 mqProducers []*rocketmqx.Producer - // mqConsumers 在 gRPC 服务启动前订阅,承接 room_outbox fanout 和 delayed open。 + // mqConsumers 在 gRPC 服务启动前订阅,承接火箭唤醒、用户区域同步和幸运礼物 owner 事实。 mqConsumers []*rocketmqx.Consumer // workers 统一控制本节点后台 worker,关闭时必须先停止 worker 再释放 MySQL/Redis。 workers *serviceapp.BackgroundGroup @@ -190,7 +191,7 @@ func New(cfg config.Config) (*App, error) { } mqProducers := make([]*rocketmqx.Producer, 0, 2) - mqConsumers := make([]*rocketmqx.Consumer, 0, 3) + mqConsumers := make([]*rocketmqx.Consumer, 0, 4) var rocketLaunchScheduler integration.RoomRocketLaunchScheduler if cfg.OutboxWorker.PublishMode == config.OutboxPublishModeDirect || cfg.OutboxWorker.PublishMode == config.OutboxPublishModeDual { if activityConn != nil { @@ -208,7 +209,7 @@ func New(cfg config.Config) (*App, error) { return nil, err } mqProducers = append(mqProducers, outboxProducer) - outboxPublishers = append(outboxPublishers, integration.NewRocketMQOutboxPublisher(outboxProducer, cfg.RocketMQ.RoomOutbox.Topic)) + outboxPublishers = append(outboxPublishers, integration.NewRocketMQOutboxPublisher(outboxProducer, cfg.RocketMQ.RoomOutbox.Topic, cfg.RocketMQ.RoomEventTagMode)) } if cfg.RocketMQ.RocketLaunch.Enabled { rocketProducer, err := rocketmqx.NewProducer(rocketMQProducerConfig(cfg.RocketMQ, cfg.RocketMQ.RocketLaunch.ProducerGroup)) @@ -295,6 +296,31 @@ func New(cfg config.Config) (*App, error) { } mqConsumers = append(mqConsumers, userConsumer) } + if cfg.RocketMQ.LuckyGiftOutbox.Enabled { + luckyGiftConsumer, err := rocketmqx.NewConsumer(luckyGiftRocketMQConsumerConfig(cfg.RocketMQ)) + if err != nil { + _ = repository.Close() + closeClientConn(activityConn) + _ = walletConn.Close() + _ = redisClient.Close() + return nil, err + } + if err := luckyGiftConsumer.Subscribe(cfg.RocketMQ.LuckyGiftOutbox.Topic, luckygiftmq.TagLuckyGiftDrawn, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + envelope, _, err := luckygiftmq.DecodeLuckyGiftOutboxMessage(message.Body) + if err != nil { + return err + } + // 消费回调只在本地 room_outbox 持久化成功后确认;腾讯 IM 失败由本服务 outbox worker 独立补偿。 + return svc.HandleLuckyGiftOutboxEvent(ctx, envelope) + }); err != nil { + _ = repository.Close() + closeClientConn(activityConn) + _ = walletConn.Close() + _ = redisClient.Close() + return nil, err + } + mqConsumers = append(mqConsumers, luckyGiftConsumer) + } server := servicegrpc.New("room-service") roomServer := grpcserver.NewServer(svc, grpcserver.WithOwnerForwarding(cfg.NodeID, directory, directory, cfg.OwnerForwardTimeout)) @@ -387,6 +413,11 @@ func (a *App) Run() error { // 火箭燃料进度允许 1 秒展示延迟;普通房写主 outbox,机器人展示到期后 best-effort 直发腾讯 IM。 a.service.RunRoomRocketProgressFlushWorker(ctx, 100*time.Millisecond) }) + a.workers.Go(func(ctx context.Context) { + // SendGift 跨钱包、幸运礼物和 Room Cell;恢复 worker 用稳定 command_id 主动完成扣款未知/已扣款操作, + // 客户端断线或不再重试都不能留下永久扣款无房间事实。 + a.service.RunGiftOperationRecoveryWorker(ctx, time.Second) + }) if a.cfg.RoomListCacheRefreshInterval > 0 { // Redis 发现页缓存是 MySQL owner 表的展示读模型,启动后先 warmup,再周期性修复 best-effort 增量丢失。 a.workers.Go(func(ctx context.Context) { @@ -589,6 +620,12 @@ func rocketMQConsumerConfig(cfg config.RocketMQConfig, groupName string, maxReco } } +func luckyGiftRocketMQConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig { + // 房间飘屏是实时外部副作用,不能在新建/误改 group 时从最早位点重放历史大奖。 + // 保持 SDK 的 latest 默认值,因此首次上线必须先让 room/activity consumer 建组并稳定订阅,再开启 lucky-gift producer。 + return rocketMQConsumerConfig(cfg, cfg.LuckyGiftOutbox.ConsumerGroup, cfg.LuckyGiftOutbox.ConsumerMaxReconsumeTimes, 0) +} + func rocketMQEndpointConfig(cfg config.RocketMQConfig) rocketmqx.EndpointConfig { return rocketmqx.EndpointConfig{ NameServers: cfg.NameServers, diff --git a/services/room-service/internal/config/config.go b/services/room-service/internal/config/config.go index d150a0ea..1a7215cc 100644 --- a/services/room-service/internal/config/config.go +++ b/services/room-service/internal/config/config.go @@ -10,6 +10,7 @@ import ( "hyapp/pkg/configx" "hyapp/pkg/logx" + "hyapp/pkg/roommq" "hyapp/pkg/tencentim" "hyapp/pkg/tencentrtc" ) @@ -139,19 +140,23 @@ type OutboxRetentionConfig struct { // RocketMQConfig 描述 room-service 使用的 RocketMQ 集群和业务 topic。 type RocketMQConfig struct { - Enabled bool `yaml:"enabled"` - NameServers []string `yaml:"name_servers"` - NameServerDomain string `yaml:"name_server_domain"` - AccessKey string `yaml:"access_key"` - SecretKey string `yaml:"secret_key"` - SecurityToken string `yaml:"security_token"` - Namespace string `yaml:"namespace"` - VIPChannel bool `yaml:"vip_channel"` - SendTimeout time.Duration `yaml:"send_timeout"` - Retry int `yaml:"retry"` - RoomOutbox RoomOutboxMQConfig `yaml:"room_outbox"` - RocketLaunch RocketLaunchMQConfig `yaml:"rocket_launch"` - UserOutbox UserOutboxMQConfig `yaml:"user_outbox"` + Enabled bool `yaml:"enabled"` + NameServers []string `yaml:"name_servers"` + NameServerDomain string `yaml:"name_server_domain"` + AccessKey string `yaml:"access_key"` + SecretKey string `yaml:"secret_key"` + SecurityToken string `yaml:"security_token"` + Namespace string `yaml:"namespace"` + VIPChannel bool `yaml:"vip_channel"` + SendTimeout time.Duration `yaml:"send_timeout"` + Retry int `yaml:"retry"` + // RoomEventTagMode 是 room topic producer 的两阶段发布开关。legacy 保持旧 + // consumer 可见;event_type 只能在全部 consumer 完成兼容订阅滚动后启用。 + RoomEventTagMode roommq.EventTagMode `yaml:"room_event_tag_mode"` + RoomOutbox RoomOutboxMQConfig `yaml:"room_outbox"` + RocketLaunch RocketLaunchMQConfig `yaml:"rocket_launch"` + UserOutbox UserOutboxMQConfig `yaml:"user_outbox"` + LuckyGiftOutbox LuckyGiftOutboxMQConfig `yaml:"lucky_gift_outbox"` } // RoomOutboxMQConfig 控制 room_outbox fanout topic。 @@ -186,6 +191,15 @@ type UserOutboxMQConfig struct { ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"` } +// LuckyGiftOutboxMQConfig 控制 room-service 消费 lucky-gift-service 已提交的中奖事实。 +// 该 consumer 只派生房间展示 outbox,不读取幸运礼物库,也不修改 Room Cell 状态。 +type LuckyGiftOutboxMQConfig struct { + Enabled bool `yaml:"enabled"` + Topic string `yaml:"topic"` + ConsumerGroup string `yaml:"consumer_group"` + ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"` +} + // TencentIMConfig 描述 room-service 调用腾讯云 IM REST API 的配置。 type TencentIMConfig struct { // Enabled 控制是否启用腾讯云 IM REST 桥接;关闭时只保留 Room Cell 内部提交。 @@ -341,9 +355,10 @@ func defaultOutboxWorkerConfig() OutboxWorkerConfig { func defaultRocketMQConfig() RocketMQConfig { return RocketMQConfig{ - Enabled: false, - SendTimeout: 3 * time.Second, - Retry: 2, + Enabled: false, + SendTimeout: 3 * time.Second, + Retry: 2, + RoomEventTagMode: roommq.EventTagModeLegacy, RoomOutbox: RoomOutboxMQConfig{ Enabled: false, Topic: "hyapp_room_outbox", @@ -366,6 +381,12 @@ func defaultRocketMQConfig() RocketMQConfig { ConsumerGroup: "hyapp-room-user-region-sync", ConsumerMaxReconsumeTimes: 16, }, + LuckyGiftOutbox: LuckyGiftOutboxMQConfig{ + Enabled: false, + Topic: "hyapp_lucky_gift_outbox", + ConsumerGroup: "hyapp-room-lucky-gift-outbox", + ConsumerMaxReconsumeTimes: 16, + }, } } @@ -460,6 +481,10 @@ func Normalize(cfg Config) (Config, error) { return Config{}, err } cfg.RocketMQ = rocketMQ + if cfg.RocketMQ.LuckyGiftOutbox.Enabled && !cfg.TencentIM.Enabled { + // 开启 owner 事实消费却没有 IM publisher 会让派生 outbox 永久 retry;启动时拒绝比运行后静默积压更安全。 + return Config{}, fmt.Errorf("rocketmq.lucky_gift_outbox.enabled requires tencent_im.enabled") + } return cfg, nil } @@ -552,6 +577,17 @@ func normalizeRocketMQConfig(cfg RocketMQConfig, publishMode string) (RocketMQCo if cfg.Retry < 0 { cfg.Retry = defaults.Retry } + cfg.RoomEventTagMode = roommq.EventTagMode(strings.ToLower(strings.TrimSpace(string(cfg.RoomEventTagMode)))) + if cfg.RoomEventTagMode == "" { + // 旧线上 YAML 没有此字段时必须继续发 legacy Tag;切 Phase B 必须是一次 + // 显式配置变更,不能由普通代码滚动发布顺带改变 MQ 可见性。 + cfg.RoomEventTagMode = defaults.RoomEventTagMode + } + switch cfg.RoomEventTagMode { + case roommq.EventTagModeLegacy, roommq.EventTagModeEventType: + default: + return RocketMQConfig{}, fmt.Errorf("rocketmq.room_event_tag_mode must be legacy or event_type") + } if cfg.RoomOutbox.Topic = strings.TrimSpace(cfg.RoomOutbox.Topic); cfg.RoomOutbox.Topic == "" { cfg.RoomOutbox.Topic = defaults.RoomOutbox.Topic } @@ -593,7 +629,16 @@ func normalizeRocketMQConfig(cfg RocketMQConfig, publishMode string) (RocketMQCo if cfg.UserOutbox.ConsumerMaxReconsumeTimes <= 0 { cfg.UserOutbox.ConsumerMaxReconsumeTimes = defaults.UserOutbox.ConsumerMaxReconsumeTimes } - if cfg.RoomOutbox.Enabled || cfg.RocketLaunch.Enabled || cfg.UserOutbox.Enabled { + if cfg.LuckyGiftOutbox.Topic = strings.TrimSpace(cfg.LuckyGiftOutbox.Topic); cfg.LuckyGiftOutbox.Topic == "" { + cfg.LuckyGiftOutbox.Topic = defaults.LuckyGiftOutbox.Topic + } + if cfg.LuckyGiftOutbox.ConsumerGroup = strings.TrimSpace(cfg.LuckyGiftOutbox.ConsumerGroup); cfg.LuckyGiftOutbox.ConsumerGroup == "" { + cfg.LuckyGiftOutbox.ConsumerGroup = defaults.LuckyGiftOutbox.ConsumerGroup + } + if cfg.LuckyGiftOutbox.ConsumerMaxReconsumeTimes <= 0 { + cfg.LuckyGiftOutbox.ConsumerMaxReconsumeTimes = defaults.LuckyGiftOutbox.ConsumerMaxReconsumeTimes + } + if cfg.RoomOutbox.Enabled || cfg.RocketLaunch.Enabled || cfg.UserOutbox.Enabled || cfg.LuckyGiftOutbox.Enabled { cfg.Enabled = true } if publishMode == OutboxPublishModeMQ || publishMode == OutboxPublishModeDual { diff --git a/services/room-service/internal/config/config_test.go b/services/room-service/internal/config/config_test.go index d6d82645..10f99eed 100644 --- a/services/room-service/internal/config/config_test.go +++ b/services/room-service/internal/config/config_test.go @@ -2,8 +2,13 @@ package config import ( + "os" + "path/filepath" + "strings" "testing" "time" + + "hyapp/pkg/roommq" ) // TestLoad 验证 room-service 的本地 config.yaml 能被正常读取。 @@ -44,9 +49,50 @@ func TestLoad(t *testing.T) { t.Fatalf("unexpected outbox worker config: %+v", cfg.OutboxWorker) } // 本地默认仍走 room_outbox MQ 和火箭延迟唤醒;房间 UI IM 已改为 Room Cell 提交后直发,不再启 MQ IM bridge。 - if !cfg.RocketMQ.Enabled || !cfg.RocketMQ.RoomOutbox.Enabled || cfg.RocketMQ.RoomOutbox.TencentIMConsumerEnabled || !cfg.RocketMQ.RocketLaunch.Enabled || !cfg.RocketMQ.UserOutbox.Enabled { + if !cfg.RocketMQ.Enabled || !cfg.RocketMQ.RoomOutbox.Enabled || cfg.RocketMQ.RoomOutbox.TencentIMConsumerEnabled || !cfg.RocketMQ.RocketLaunch.Enabled || !cfg.RocketMQ.UserOutbox.Enabled || !cfg.RocketMQ.LuckyGiftOutbox.Enabled { t.Fatalf("local config must enable room MQ fanout and disable MQ IM bridge: %+v", cfg.RocketMQ) } + if cfg.RocketMQ.LuckyGiftOutbox.Topic != "hyapp_lucky_gift_outbox" || cfg.RocketMQ.LuckyGiftOutbox.ConsumerGroup != "hyapp-room-lucky-gift-outbox" { + t.Fatalf("local config must isolate lucky gift display consumption: %+v", cfg.RocketMQ.LuckyGiftOutbox) + } + if cfg.RocketMQ.RoomEventTagMode != roommq.EventTagModeLegacy { + t.Fatalf("Phase A config must publish legacy room tags: %q", cfg.RocketMQ.RoomEventTagMode) + } +} + +func TestRoomEventTagModeDefaultsAndValidation(t *testing.T) { + if got := Default().RocketMQ.RoomEventTagMode; got != roommq.EventTagModeLegacy { + t.Fatalf("default room event tag mode = %q, want %q", got, roommq.EventTagModeLegacy) + } + + missing, err := Load(writeRoomConfig(t, "rocketmq:\n enabled: false\n")) + if err != nil { + t.Fatalf("Load missing room tag mode failed: %v", err) + } + if missing.RocketMQ.RoomEventTagMode != roommq.EventTagModeLegacy { + t.Fatalf("missing room_event_tag_mode = %q", missing.RocketMQ.RoomEventTagMode) + } + + typed, err := Load(writeRoomConfig(t, "rocketmq:\n enabled: false\n room_event_tag_mode: ' EVENT_TYPE '\n")) + if err != nil { + t.Fatalf("Load Phase B room tag mode failed: %v", err) + } + if typed.RocketMQ.RoomEventTagMode != roommq.EventTagModeEventType { + t.Fatalf("room_event_tag_mode = %q, want %q", typed.RocketMQ.RoomEventTagMode, roommq.EventTagModeEventType) + } + + if _, err := Load(writeRoomConfig(t, "rocketmq:\n enabled: false\n room_event_tag_mode: wildcard\n")); err == nil || !strings.Contains(err.Error(), "room_event_tag_mode") { + t.Fatalf("Load unknown room_event_tag_mode error = %v", err) + } +} + +func writeRoomConfig(t *testing.T, body string) string { + t.Helper() + path := filepath.Join(t.TempDir(), "config.yaml") + if err := os.WriteFile(path, []byte(body), 0o600); err != nil { + t.Fatalf("write room config: %v", err) + } + return path } // TestLoadTencentExample 验证线上腾讯云 IM 模板能覆盖本地默认配置。 @@ -80,9 +126,12 @@ func TestLoadTencentExample(t *testing.T) { if !cfg.OutboxWorker.Enabled || cfg.OutboxWorker.PollInterval != 200*time.Millisecond || cfg.OutboxWorker.BatchSize != 50 || cfg.OutboxWorker.Concurrency != 16 || cfg.OutboxWorker.PublishTimeout != 15*time.Second || cfg.OutboxWorker.RetryStrategy != "exponential_backoff" || cfg.OutboxWorker.MaxRetryCount != 10 { t.Fatalf("tencent example must configure outbox worker: %+v", cfg.OutboxWorker) } - if cfg.OutboxWorker.PublishMode != OutboxPublishModeMQ || !cfg.RocketMQ.RoomOutbox.Enabled || !cfg.RocketMQ.RocketLaunch.Enabled { + if cfg.OutboxWorker.PublishMode != OutboxPublishModeMQ || !cfg.RocketMQ.RoomOutbox.Enabled || !cfg.RocketMQ.RocketLaunch.Enabled || !cfg.RocketMQ.LuckyGiftOutbox.Enabled { t.Fatalf("tencent example must route room outbox and rocket launch through RocketMQ: mode=%s mq=%+v", cfg.OutboxWorker.PublishMode, cfg.RocketMQ) } + if cfg.RocketMQ.RoomEventTagMode != roommq.EventTagModeLegacy { + t.Fatalf("tencent example must remain in Phase A until consumers are verified: %q", cfg.RocketMQ.RoomEventTagMode) + } } func TestLoadDockerConfigDoesNotStartRoomIMBridgeWhenOutboxUsesMQ(t *testing.T) { @@ -100,6 +149,12 @@ func TestLoadDockerConfigDoesNotStartRoomIMBridgeWhenOutboxUsesMQ(t *testing.T) if !cfg.RocketMQ.RoomOutbox.Enabled { t.Fatalf("docker config must enable room outbox MQ") } + if !cfg.RocketMQ.LuckyGiftOutbox.Enabled || cfg.RocketMQ.LuckyGiftOutbox.ConsumerGroup != "hyapp-room-lucky-gift-outbox" { + t.Fatalf("docker config must enable an isolated lucky gift consumer: %+v", cfg.RocketMQ.LuckyGiftOutbox) + } + if cfg.RocketMQ.RoomEventTagMode != roommq.EventTagModeLegacy { + t.Fatalf("docker config must remain in Phase A: %q", cfg.RocketMQ.RoomEventTagMode) + } if cfg.RocketMQ.RoomOutbox.TencentIMConsumerEnabled { t.Fatalf("docker mq room outbox must not start Tencent IM bridge consumer") } @@ -138,6 +193,16 @@ func TestNormalizeRejectsMQPublishModeWithoutRoomOutboxMQ(t *testing.T) { } } +func TestNormalizeRejectsLuckyGiftConsumerWithoutTencentIM(t *testing.T) { + cfg := Default() + cfg.RocketMQ.LuckyGiftOutbox.Enabled = true + cfg.RocketMQ.NameServers = []string{"127.0.0.1:9876"} + + if _, err := Normalize(cfg); err == nil || !strings.Contains(err.Error(), "tencent_im.enabled") { + t.Fatalf("Normalize should reject lucky gift consumption without room IM publisher: %v", err) + } +} + func TestNormalizeOutboxWorkerKeepsSafeDefaults(t *testing.T) { cfg := Default() cfg.OutboxWorker.PollInterval = 0 diff --git a/services/room-service/internal/integration/clients.go b/services/room-service/internal/integration/clients.go index 0c11ac15..d3f4920c 100644 --- a/services/room-service/internal/integration/clients.go +++ b/services/room-service/internal/integration/clients.go @@ -10,7 +10,7 @@ import ( "hyapp/pkg/tencentim" ) -// WalletClient 抽象 room-service 对 wallet-service 的同步扣费依赖。 +// WalletClient 抽象 room-service 对 wallet-service 的同步账务和 VIP 权限事实依赖。 type WalletClient interface { // DebitGift 执行送礼扣费;失败时 room-service 不进入 Room Cell 状态提交。 DebitGift(ctx context.Context, req *walletv1.DebitGiftRequest) (*walletv1.DebitGiftResponse, error) @@ -20,6 +20,9 @@ type WalletClient interface { DebitRobotGift(ctx context.Context, req *walletv1.DebitRobotGiftRequest) (*walletv1.DebitGiftResponse, error) // GrantResourceGroup 发放火箭奖励资源组;命令 ID 必须由 room-service 按火箭结算事实生成。 GrantResourceGroup(ctx context.Context, req *walletv1.GrantResourceGroupRequest) (*walletv1.ResourceGrantResponse, error) + // GetMyVip 读取 wallet-service 已合并付费 VIP 与当前佩戴体验卡后的唯一 effective state。 + // room-service 只消费 effective_benefits,不自行按等级或 app_code 推导房间权限。 + GetMyVip(ctx context.Context, req *walletv1.GetMyVipRequest) (*walletv1.GetMyVipResponse, error) } // LuckyGiftClient 抽象 room-service 对 lucky-gift-service 幸运礼物抽奖边界的同步依赖。 diff --git a/services/room-service/internal/integration/clients_grpc.go b/services/room-service/internal/integration/clients_grpc.go index e64ebcf8..014f6a1f 100644 --- a/services/room-service/internal/integration/clients_grpc.go +++ b/services/room-service/internal/integration/clients_grpc.go @@ -45,6 +45,11 @@ func (c *grpcWalletClient) GrantResourceGroup(ctx context.Context, req *walletv1 return c.client.GrantResourceGroup(ctx, req) } +// GetMyVip 直接读取钱包已经求值后的 VIP 状态;房间侧不能复制付费 VIP、体验卡和权益继承算法。 +func (c *grpcWalletClient) GetMyVip(ctx context.Context, req *walletv1.GetMyVipRequest) (*walletv1.GetMyVipResponse, error) { + return c.client.GetMyVip(ctx, req) +} + type grpcLuckyGiftClient struct { client luckygiftv1.LuckyGiftServiceClient } diff --git a/services/room-service/internal/integration/rocketmq_outbox.go b/services/room-service/internal/integration/rocketmq_outbox.go index 2b84dc72..d107134a 100644 --- a/services/room-service/internal/integration/rocketmq_outbox.go +++ b/services/room-service/internal/integration/rocketmq_outbox.go @@ -2,8 +2,10 @@ package integration import ( "context" + "fmt" "strconv" + "google.golang.org/protobuf/proto" roomeventsv1 "hyapp.local/api/proto/events/room/v1" "hyapp/pkg/rocketmqx" "hyapp/pkg/roommq" @@ -13,37 +15,69 @@ import ( type RocketMQOutboxPublisher struct { producer *rocketmqx.Producer topic string + tagMode roommq.EventTagMode } // NewRocketMQOutboxPublisher creates a room_outbox fanout publisher. -func NewRocketMQOutboxPublisher(producer *rocketmqx.Producer, topic string) *RocketMQOutboxPublisher { - return &RocketMQOutboxPublisher{producer: producer, topic: topic} +func NewRocketMQOutboxPublisher(producer *rocketmqx.Producer, topic string, tagMode roommq.EventTagMode) *RocketMQOutboxPublisher { + return &RocketMQOutboxPublisher{producer: producer, topic: topic, tagMode: tagMode} } // PublishOutboxEvent implements OutboxPublisher. func (p *RocketMQOutboxPublisher) PublishOutboxEvent(ctx context.Context, envelope *roomeventsv1.EventEnvelope) error { - body, err := roommq.EncodeRoomOutboxMessage(envelope) + message, err := roomOutboxRocketMessage(p.topic, p.tagMode, envelope) if err != nil { return err } - return p.producer.SendSync(ctx, rocketmqx.Message{ - Topic: p.topic, - Tag: roommq.TagRoomOutboxEvent, - Keys: []string{envelope.GetEventId(), envelope.GetRoomId()}, + return p.producer.SendSync(ctx, message) +} + +// roomOutboxRocketMessage keeps envelope.event_type canonical in both migration phases. +// Phase A publishes the legacy Tag, while explicit Phase B uses the exact event_type; +// the body stays byte-compatible so old replay tools and handlers require no protocol fork. +func roomOutboxRocketMessage(topic string, tagMode roommq.EventTagMode, envelope *roomeventsv1.EventEnvelope) (rocketmqx.Message, error) { + if envelope == nil { + return rocketmqx.Message{}, fmt.Errorf("room outbox envelope is required") + } + eventTag, err := roommq.EventTypeTag(envelope.GetEventType()) + if err != nil { + return rocketmqx.Message{}, err + } + publishTag := roommq.TagRoomOutboxEvent + switch tagMode { + case "", roommq.EventTagModeLegacy: + // 空值覆盖旧测试构造和旧配置;只有显式 event_type 才允许进入 Phase B。 + case roommq.EventTagModeEventType: + publishTag = eventTag + default: + return rocketmqx.Message{}, fmt.Errorf("unsupported room event tag mode %q", tagMode) + } + // outbox 历史数据可能含首尾空白;Tag 与消息体必须使用同一规范 event_type, + // 但不能原地修改 repository 持有的 envelope,避免 composite publisher 看到副作用。 + canonicalEnvelope := proto.Clone(envelope).(*roomeventsv1.EventEnvelope) + canonicalEnvelope.EventType = eventTag + body, err := roommq.EncodeRoomOutboxMessage(canonicalEnvelope) + if err != nil { + return rocketmqx.Message{}, err + } + return rocketmqx.Message{ + Topic: topic, + Tag: publishTag, + Keys: []string{canonicalEnvelope.GetEventId(), canonicalEnvelope.GetRoomId()}, Body: body, Properties: map[string]string{ "message_type": roommq.MessageTypeRoomOutboxEvent, - "app_code": envelope.GetAppCode(), - "room_id": envelope.GetRoomId(), - "event_id": envelope.GetEventId(), - "event_type": envelope.GetEventType(), - "room_version": strconv.FormatInt(envelope.GetRoomVersion(), 10), - "occurred_at_ms": strconv.FormatInt(envelope.GetOccurredAtMs(), 10), + "app_code": canonicalEnvelope.GetAppCode(), + "room_id": canonicalEnvelope.GetRoomId(), + "event_id": canonicalEnvelope.GetEventId(), + "event_type": eventTag, + "room_version": strconv.FormatInt(canonicalEnvelope.GetRoomVersion(), 10), + "occurred_at_ms": strconv.FormatInt(canonicalEnvelope.GetOccurredAtMs(), 10), "source_database": "hyapp_room", "source_table": "room_outbox", "delivery_version": "1", }, - }) + }, nil } // RocketMQRocketLaunchScheduler publishes exact-time rocket wakeup messages. diff --git a/services/room-service/internal/integration/rocketmq_outbox_test.go b/services/room-service/internal/integration/rocketmq_outbox_test.go new file mode 100644 index 00000000..36fc2d38 --- /dev/null +++ b/services/room-service/internal/integration/rocketmq_outbox_test.go @@ -0,0 +1,64 @@ +package integration + +import ( + "testing" + + roomeventsv1 "hyapp.local/api/proto/events/room/v1" + "hyapp/pkg/roommq" +) + +func TestRoomOutboxRocketMessageUsesConfiguredTagMode(t *testing.T) { + tests := []struct { + name string + mode roommq.EventTagMode + wantTag string + }{ + {name: "missing config remains legacy", mode: "", wantTag: roommq.TagRoomOutboxEvent}, + {name: "phase a publishes legacy", mode: roommq.EventTagModeLegacy, wantTag: roommq.TagRoomOutboxEvent}, + {name: "phase b publishes event type", mode: roommq.EventTagModeEventType, wantTag: roommq.EventTypeRoomGiftSent}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + message, err := roomOutboxRocketMessage("hyapp_room_outbox", test.mode, validRoomOutboxEnvelope()) + if err != nil { + t.Fatalf("roomOutboxRocketMessage failed: %v", err) + } + if message.Tag != test.wantTag { + t.Fatalf("message tag = %q, want %q", message.Tag, test.wantTag) + } + envelope, _, err := roommq.DecodeRoomOutboxMessage(message.Body) + if err != nil { + t.Fatalf("DecodeRoomOutboxMessage failed: %v", err) + } + if envelope.GetEventType() != roommq.EventTypeRoomGiftSent { + t.Fatalf("body event_type = %q", envelope.GetEventType()) + } + if message.Topic != "hyapp_room_outbox" || len(message.Keys) != 2 { + t.Fatalf("message routing metadata mismatch: %+v", message) + } + }) + } +} + +func TestRoomOutboxRocketMessageRejectsUnsupportedModeOrUnsafeTag(t *testing.T) { + if _, err := roomOutboxRocketMessage("hyapp_room_outbox", roommq.EventTagMode("all"), validRoomOutboxEnvelope()); err == nil { + t.Fatal("roomOutboxRocketMessage unexpectedly accepted unsupported tag mode") + } + envelope := validRoomOutboxEnvelope() + envelope.EventType = "RoomGiftSent || *" + if _, err := roomOutboxRocketMessage("hyapp_room_outbox", roommq.EventTagModeLegacy, envelope); err == nil { + t.Fatal("roomOutboxRocketMessage unexpectedly accepted unsafe event_type") + } +} + +func validRoomOutboxEnvelope() *roomeventsv1.EventEnvelope { + return &roomeventsv1.EventEnvelope{ + AppCode: "lalu", + EventId: "event-1", + EventType: " " + roommq.EventTypeRoomGiftSent + " ", + RoomId: "room-1", + RoomVersion: 9, + OccurredAtMs: 1_700_000_000_000, + Body: []byte{1, 2, 3}, + } +} diff --git a/services/room-service/internal/integration/tencent_im.go b/services/room-service/internal/integration/tencent_im.go index 5c235068..1619307d 100644 --- a/services/room-service/internal/integration/tencent_im.go +++ b/services/room-service/internal/integration/tencent_im.go @@ -401,6 +401,45 @@ func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.Room "gift_value": fmt.Sprintf("%d", body.GetGiftValue()), } return base, true, nil + case "RoomLuckyGiftDrawn": + // 真人中奖由 lucky-gift owner 事实派生;room-service 只把稳定快照翻译为现有 Flutter lucky_gift_drawn 协议。 + var body roomeventsv1.RoomLuckyGiftDrawn + if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil { + return tencentim.RoomEvent{}, false, err + } + base.ActorUserID = body.GetSenderUserId() + base.TargetUserID = body.GetTargetUserId() + base.GiftValue = body.GetEffectiveRewardCoins() + base.Attributes = map[string]string{ + "event_type": "lucky_gift_drawn", + "draw_id": body.GetDrawId(), + "command_id": body.GetCommandId(), + "sender_user_id": fmt.Sprintf("%d", body.GetSenderUserId()), + "target_user_id": fmt.Sprintf("%d", body.GetTargetUserId()), + "sender_name": body.GetSenderName(), + "sender_avatar": body.GetSenderAvatar(), + "sender_display_user_id": body.GetSenderDisplayUserId(), + "sender_pretty_display_user_id": body.GetSenderPrettyDisplayUserId(), + "gift_id": body.GetGiftId(), + "gift_count": fmt.Sprintf("%d", body.GetGiftCount()), + "pool_id": body.GetPoolId(), + "visible_region_id": fmt.Sprintf("%d", body.GetVisibleRegionId()), + "country_id": fmt.Sprintf("%d", body.GetCountryId()), + "coin_spent": fmt.Sprintf("%d", body.GetCoinSpent()), + "rule_version": fmt.Sprintf("%d", body.GetRuleVersion()), + "experience_pool": body.GetExperiencePool(), + "selected_tier_id": body.GetSelectedTierId(), + "multiplier_ppm": fmt.Sprintf("%d", body.GetMultiplierPpm()), + "base_reward_coins": fmt.Sprintf("%d", body.GetBaseRewardCoins()), + "effective_reward_coins": fmt.Sprintf("%d", body.GetEffectiveRewardCoins()), + "stage_feedback": fmt.Sprintf("%t", body.GetStageFeedback()), + "high_multiplier": fmt.Sprintf("%t", body.GetHighMultiplier()), + "reward_status": body.GetRewardStatus(), + "wallet_transaction_id": body.GetWalletTransactionId(), + "created_at_ms": fmt.Sprintf("%d", body.GetDrawCreatedAtMs()), + "reward_granted_at_ms": fmt.Sprintf("%d", body.GetRewardGrantedAtMs()), + } + return base, true, nil case "RoomRobotLuckyGiftDrawn": // 机器人幸运礼物只发房间展示协议,不进入 activity-service 真实抽奖和奖池链路。 var body roomeventsv1.RoomRobotLuckyGiftDrawn @@ -541,6 +580,12 @@ func roomUserJoinedAttributesFromEvent(body *roomeventsv1.RoomUserJoined) map[st delete(values, key) } } + // VIP 入场开关必须显式传 false;否则 Flutter 无法区分“普通进房”与旧消息缺字段。 + // 这些值都来自 RoomUserJoined 固化事实,补偿投递不能按用户当前 VIP 重新计算历史进房表现。 + values["vip_program_type"] = strings.TrimSpace(body.GetVipProgramType()) + values["effective_vip_level"] = fmt.Sprintf("%d", body.GetEffectiveVipLevel()) + values["effective_vip_name"] = strings.TrimSpace(body.GetEffectiveVipName()) + values["room_entry_notice_enabled"] = fmt.Sprintf("%t", body.GetRoomEntryNoticeEnabled()) return values } @@ -592,6 +637,8 @@ func eventTypeForClient(eventType string) string { return "room_rank_changed" case "RoomRobotLuckyGiftDrawn": return "lucky_gift_drawn" + case "RoomLuckyGiftDrawn": + return "lucky_gift_drawn" case "RoomRocketFuelChanged": return "room_rocket_fuel_changed" case "RoomRocketIgnited": diff --git a/services/room-service/internal/integration/tencent_im_test.go b/services/room-service/internal/integration/tencent_im_test.go index 9d69d9eb..0a00e6a2 100644 --- a/services/room-service/internal/integration/tencent_im_test.go +++ b/services/room-service/internal/integration/tencent_im_test.go @@ -35,13 +35,17 @@ func TestRoomEventFromEnvelopeSkipsNonIMEvents(t *testing.T) { func TestRoomUserJoinedCarriesEntryVehicleSnapshot(t *testing.T) { record, err := outbox.Build("room-vehicle", "RoomUserJoined", 9, time.Now(), &roomeventsv1.RoomUserJoined{ - UserId: 42, - Role: "audience", - VisibleRegionId: 86, - Nickname: "Join Nina", - Avatar: "https://cdn.example.com/nina.png", - DisplayUserId: "100042", - PrettyDisplayUserId: "888042", + UserId: 42, + Role: "audience", + VisibleRegionId: 86, + Nickname: "Join Nina", + Avatar: "https://cdn.example.com/nina.png", + DisplayUserId: "100042", + PrettyDisplayUserId: "888042", + VipProgramType: "tiered_privilege_v1", + EffectiveVipLevel: 4, + EffectiveVipName: "VIP4", + RoomEntryNoticeEnabled: true, EntryVehicle: &roomeventsv1.RoomEntryVehicleSnapshot{ ResourceId: 7001, ResourceCode: "vehicle_gold", @@ -71,6 +75,9 @@ func TestRoomUserJoinedCarriesEntryVehicleSnapshot(t *testing.T) { if event.Attributes["nickname"] != "Join Nina" || event.Attributes["avatar"] != "https://cdn.example.com/nina.png" || event.Attributes["display_user_id"] != "100042" || event.Attributes["pretty_display_user_id"] != "888042" { t.Fatalf("joined display profile attributes mismatch: %+v", event.Attributes) } + if event.Attributes["vip_program_type"] != "tiered_privilege_v1" || event.Attributes["effective_vip_level"] != "4" || event.Attributes["effective_vip_name"] != "VIP4" || event.Attributes["room_entry_notice_enabled"] != "true" { + t.Fatalf("joined vip attributes mismatch: %+v", event.Attributes) + } } func TestRoomBackgroundChangedPublishesDedicatedIMEvent(t *testing.T) { @@ -296,6 +303,57 @@ func TestRoomRobotLuckyGiftDrawnPublishesLuckyGiftIMEvent(t *testing.T) { } } +func TestRoomLuckyGiftDrawnPublishesGrantedOwnerFactAsLuckyGiftIMEvent(t *testing.T) { + record, err := outbox.Build("room-lucky-owner", "RoomLuckyGiftDrawn", 0, time.Now(), &roomeventsv1.RoomLuckyGiftDrawn{ + DrawId: "lucky_draw_1", + CommandId: "cmd_lucky_1", + PoolId: "advanced", + GiftId: "43", + GiftCount: 1, + SenderUserId: 1001, + TargetUserId: 1002, + SenderName: "Lucky Sender", + SenderAvatar: "https://cdn.example.test/sender.png", + SenderDisplayUserId: "123456", + SenderPrettyDisplayUserId: "VIP123456", + VisibleRegionId: 1, + CountryId: 86, + CoinSpent: 100, + RuleVersion: 7, + ExperiencePool: "normal", + SelectedTierId: "50x", + MultiplierPpm: 50_000_000, + BaseRewardCoins: 5000, + EffectiveRewardCoins: 5000, + HighMultiplier: true, + RewardStatus: "granted", + WalletTransactionId: "wallet-lucky-1", + DrawCreatedAtMs: 1_783_940_398_274, + RewardGrantedAtMs: 1_783_940_398_300, + }) + if err != nil { + t.Fatalf("Build RoomLuckyGiftDrawn envelope failed: %v", err) + } + record.EventID = "lucky_gift_drawn:lucky_draw_1" + record.Envelope.EventId = record.EventID + + event, publish, err := roomEventFromEnvelope(record.Envelope) + if err != nil { + t.Fatalf("RoomLuckyGiftDrawn should decode: %v", err) + } + if !publish || event.EventType != "lucky_gift_drawn" || event.EventID != record.EventID || event.ActorUserID != 1001 || event.TargetUserID != 1002 || event.GiftValue != 5000 { + t.Fatalf("owner lucky gift IM event mismatch: publish=%t event=%+v", publish, event) + } + if event.Attributes["sender_name"] != "Lucky Sender" || + event.Attributes["sender_pretty_display_user_id"] != "VIP123456" || + event.Attributes["multiplier_ppm"] != "50000000" || + event.Attributes["reward_status"] != "granted" || + event.Attributes["created_at_ms"] != "1783940398274" || + event.Attributes["wallet_transaction_id"] != "wallet-lucky-1" { + t.Fatalf("owner lucky gift IM attributes mismatch: %+v", event.Attributes) + } +} + func TestRoomMicChangedCarriesTargetGiftValueInIMAttributes(t *testing.T) { record, err := outbox.Build("room-mic-value", "RoomMicChanged", 9, time.Now(), &roomeventsv1.RoomMicChanged{ ActorUserId: 1001, diff --git a/services/room-service/internal/room/command/command.go b/services/room-service/internal/room/command/command.go index 15579e2e..34a8751a 100644 --- a/services/room-service/internal/room/command/command.go +++ b/services/room-service/internal/room/command/command.go @@ -137,6 +137,9 @@ type JoinRoom struct { EntryVehicle EntryVehicleSnapshot `json:"entry_vehicle,omitempty"` // ActorDisplayProfile 是 gateway 在进房入口固化的展示快照;只服务 IM/UI 兜底,不参与 presence 权限和统计。 ActorDisplayProfile UserDisplayProfile `json:"actor_display_profile,omitempty"` + // PresenceRecovery 表示客户端会话仍在,只需恢复被 stale worker 清理的业务 presence。 + // 该命令不能制造新的进房展示或统计事实。 + PresenceRecovery bool `json:"presence_recovery,omitempty"` } // Type 返回命令类型。 @@ -444,6 +447,10 @@ type SendGift struct { Source string `json:"source,omitempty"` // DisplayMode 只控制客户端展示事件形态;batch 表示保留逐目标事实,但房间 IM 合并为单条多人展示消息。 DisplayMode string `json:"display_mode,omitempty"` + // ComboSessionID/BatchSeq 描述 Flutter 微批顺序;它们进入 command_id 载荷冲突校验, + // 但不能替代 Base.CommandID 作为钱包、抽奖和房间提交的唯一幂等键。 + ComboSessionID string `json:"combo_session_id,omitempty"` + BatchSeq int64 `json:"batch_seq,omitempty"` // SenderRegionID 是送礼用户所属区域,由 gateway 从 user-service 注入。 SenderRegionID int64 `json:"sender_region_id,omitempty"` // SenderCountryID 是送礼用户所属国家,由 gateway 从 user-service 注入,统计服务不能用房间区域反推。 @@ -663,6 +670,11 @@ func IdempotencyPayload(payload []byte) ([]byte, error) { delete(values, "target_is_host") delete(values, "target_host_region_id") delete(values, "target_agency_owner_user_id") + // sender country/region are also gateway-resolved snapshots. The first gift_operation persists + // their exact values for settlement and statistics, but a later retry of the same client body must + // not conflict merely because the user changed profile between timeout and replay. + delete(values, "sender_region_id") + delete(values, "sender_country_id") // SendGift 的以下字段由 wallet-service 结算后写入 command log,不属于客户端请求幂等语义。 delete(values, "billing_receipt_id") delete(values, "coin_spent") diff --git a/services/room-service/internal/room/command/idempotency_test.go b/services/room-service/internal/room/command/idempotency_test.go new file mode 100644 index 00000000..b0faf067 --- /dev/null +++ b/services/room-service/internal/room/command/idempotency_test.go @@ -0,0 +1,45 @@ +package command + +import ( + "bytes" + "testing" +) + +func TestSendGiftIdempotencyIgnoresGatewayResolvedSnapshots(t *testing.T) { + base := SendGift{ + Base: Base{CommandID: "combo-1_b1", Room: "room-1", ActorID: 42}, + TargetType: "user", TargetUserIDs: []int64{43}, TargetUserID: 43, + GiftID: "rose", GiftCount: 9, ComboSessionID: "combo-1", BatchSeq: 1, + SenderRegionID: 1001, SenderCountryID: 840, + TargetHostScopes: []GiftTargetHostScope{{TargetUserID: 43, TargetIsHost: true, TargetHostRegionID: 1001}}, + SenderDisplayProfile: GiftDisplayProfile{UserID: 42, Username: "before"}, + } + retry := base + retry.SenderRegionID = 2002 + retry.SenderCountryID = 356 + retry.TargetHostScopes = []GiftTargetHostScope{{TargetUserID: 43, TargetIsHost: false}} + retry.SenderDisplayProfile = GiftDisplayProfile{UserID: 42, Username: "after"} + + left, err := IdempotencyPayloadForCommand(base) + if err != nil { + t.Fatalf("base payload: %v", err) + } + right, err := IdempotencyPayloadForCommand(retry) + if err != nil { + t.Fatalf("retry payload: %v", err) + } + if !bytes.Equal(left, right) { + t.Fatalf("gateway snapshot drift changed client idempotency semantics:\nleft=%s\nright=%s", left, right) + } +} + +func TestSendGiftIdempotencyKeepsComboBatchIdentity(t *testing.T) { + base := SendGift{Base: Base{CommandID: "combo-1_b1", Room: "room-1", ActorID: 42}, TargetType: "user", TargetUserIDs: []int64{43}, TargetUserID: 43, GiftID: "rose", GiftCount: 1, ComboSessionID: "combo-1", BatchSeq: 1} + reused := base + reused.BatchSeq = 2 + left, _ := IdempotencyPayloadForCommand(base) + right, _ := IdempotencyPayloadForCommand(reused) + if bytes.Equal(left, right) { + t.Fatal("same command_id reused for another combo batch was not detected") + } +} diff --git a/services/room-service/internal/room/service/admin.go b/services/room-service/internal/room/service/admin.go index 9bca7eef..bfa514cc 100644 --- a/services/room-service/internal/room/service/admin.go +++ b/services/room-service/internal/room/service/admin.go @@ -83,6 +83,9 @@ func (s *Service) AdminDeleteRoom(ctx context.Context, req *roomv1.AdminDeleteRo if current == nil { return mutationResult{}, nil, xerr.New(xerr.NotFound, "room not found") } + if err := s.ensureNoRecoverableGiftOperations(ctx, current.RoomID); err != nil { + return mutationResult{}, nil, err + } // 删除不是“关闭”的另一种展示状态,而是后台确认移除房间恢复来源;状态只服务本次响应和内存 Cell 收尾。 current.Status = state.RoomStatusDeleted current.OnlineUsers = map[int64]*state.RoomUserState{} @@ -187,6 +190,11 @@ func (s *Service) applyAdminRoomMutation(ctx context.Context, logCommand command if current == nil { return mutationResult{}, nil, xerr.New(xerr.NotFound, "room not found") } + if cmd.Status != nil && (*cmd.Status == state.RoomStatusClosed || *cmd.Status == state.RoomStatusDeleted) { + if err := s.ensureNoRecoverableGiftOperations(ctx, current.RoomID); err != nil { + return mutationResult{}, nil, err + } + } changed := applyAdminRoomPatch(current, cmd) records := make([]outbox.Record, 0, 2) result := mutationResult{ diff --git a/services/room-service/internal/room/service/admin_robot_room.go b/services/room-service/internal/room/service/admin_robot_room.go index 45455c2c..5158f688 100644 --- a/services/room-service/internal/room/service/admin_robot_room.go +++ b/services/room-service/internal/room/service/admin_robot_room.go @@ -278,9 +278,37 @@ func (s *Service) startActiveRobotRooms(ctx context.Context) { return } logx.Info(ctx, "robot_room_runtime_scan_completed", slog.Int("active_room_count", len(configs))) + // 在启动/停止决策前先记录已运行集合;扫描期间由管理端并发新建的房间不在本轮 active 列表里, + // 不能被误判为已停用。 + runningRoomIDs := s.runningRobotRoomIDs() + activeRoomIDs := make(map[string]bool, len(configs)) for _, config := range configs { + activeRoomIDs[config.RoomID] = true + ownerNodeID, ownedElsewhere, err := s.roomLeaseOwnedElsewhere(ctx, config.AppCode, config.RoomID) + if err != nil { + // 目录抖动时保持现状:不认领也不停止,等下一轮扫描重新判定。 + logx.Warn(ctx, "robot_room_runtime_lease_lookup_failed", slog.String("room_id", config.RoomID), slog.String("error", err.Error())) + continue + } + if ownedElsewhere { + // 其他节点持有有效 lease 时,本节点对该房间的任何命令都只会得到 CONFLICT; + // 不启动新循环,并停掉执行权已迁走后仍残留的本地循环。 + if s.stopRobotRoomRuntime(config.RoomID) { + logx.Warn(ctx, "robot_room_runtime_ownership_lost", slog.String("room_id", config.RoomID), slog.String("owner_node_id", ownerNodeID)) + } + continue + } s.startRobotRoomRuntime(ctx, config) } + for _, roomID := range runningRoomIDs { + if activeRoomIDs[roomID] { + continue + } + // 停用/删除请求可能落在其他节点;配置不再 active 的房间不能在任何节点继续跑循环。 + if s.stopRobotRoomRuntime(roomID) { + logx.Info(ctx, "robot_room_runtime_stopped_inactive", slog.String("room_id", roomID)) + } + } } func (s *Service) startRobotRoomRuntime(parent context.Context, config RobotRoomConfig) { @@ -314,6 +342,23 @@ func (s *Service) startRobotRoomRuntime(parent context.Context, config RobotRoom } s.robotRuntimeMu.Unlock() + owned, ownerNodeID, err := s.claimRobotRoomOwnership(parent, config) + if err != nil { + logx.Warn(parent, "robot_room_runtime_lease_claim_failed", slog.String("room_id", config.RoomID), slog.String("error", err.Error())) + return + } + if !owned { + // 机器人房循环只能在 lease owner 节点运行;非 owner 节点启动只会对 owner 的房间刷 CONFLICT。 + logx.Info(parent, "robot_room_runtime_start_skipped", + slog.String("room_id", config.RoomID), + slog.String("status", config.Status), + slog.Int("robot_count", len(config.RobotUserIDs)), + slog.String("reason", "not_owner"), + slog.String("owner_node_id", ownerNodeID), + ) + return + } + if err := s.ensureRobotRoomParticipants(parent, config); err != nil { logx.Warn(parent, "robot_room_runtime_prepare_failed", slog.String("room_id", config.RoomID), slog.String("error", err.Error())) return @@ -339,16 +384,97 @@ func (s *Service) startRobotRoomRuntime(parent context.Context, config RobotRoom defer s.clearRobotRoomRuntime(config.RoomID, token) s.runRobotRoomRuntime(ctx, config) }() + go s.runRobotRoomLeaseKeeper(ctx, config, token) } -func (s *Service) stopRobotRoomRuntime(roomID string) { +// claimRobotRoomOwnership 在无有效 owner 或本节点已是 owner 时认领/续租机器人房 lease; +// 其他节点持有有效 lease 时返回现任 owner,调用方必须放弃启动循环。 +func (s *Service) claimRobotRoomOwnership(ctx context.Context, config RobotRoomConfig) (bool, string, error) { + if s.directory == nil { + // 测试中的极简 Service 可能没有装配 directory;生产 HealthCheck 会拒绝这种配置。 + return true, "", nil + } + lease, err := s.directory.EnsureOwner(ctx, runtimeRoomKey(config.AppCode, config.RoomID), s.nodeID, s.clock.Now(), s.leaseTTL) + if err != nil { + return false, "", err + } + if lease.NodeID != s.nodeID { + return false, lease.NodeID, nil + } + return true, "", nil +} + +// runRobotRoomLeaseKeeper 以短于 leaseTTL 的周期续租机器人房 lease。 +// 机器人房的写命令间隔可能远大于 leaseTTL,只靠命令续租会让 lease 频繁过期、被其他节点的 +// 扫描抢走;续租发现执行权已被接管时立即停掉本地 runtime,不再对新 owner 的房间空转 CONFLICT。 +func (s *Service) runRobotRoomLeaseKeeper(ctx context.Context, config RobotRoomConfig, token string) { + if s.directory == nil { + return + } + interval := s.leaseTTL / 3 + if interval <= 0 { + interval = time.Second + } + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + lease, err := s.directory.EnsureOwner(ctx, runtimeRoomKey(config.AppCode, config.RoomID), s.nodeID, s.clock.Now(), s.leaseTTL) + if err != nil { + // 目录抖动时保留 runtime 等下一次续租;期间的房间命令也会经 ensureCell 续租。 + logx.Warn(ctx, "robot_room_lease_renew_failed", slog.String("room_id", config.RoomID), slog.String("error", err.Error())) + continue + } + if lease.NodeID != s.nodeID { + logx.Warn(ctx, "robot_room_runtime_ownership_lost", + slog.String("room_id", config.RoomID), + slog.String("owner_node_id", lease.NodeID), + ) + s.stopRobotRoomRuntimeToken(config.RoomID, token) + return + } + } +} + +// stopRobotRoomRuntime 停止指定房间的本地循环;返回是否确实存在运行中的 runtime。 +func (s *Service) stopRobotRoomRuntime(roomID string) bool { s.robotRuntimeMu.Lock() - runtime := s.robotRuntimes[roomID] + runtime, exists := s.robotRuntimes[roomID] delete(s.robotRuntimes, roomID) s.robotRuntimeMu.Unlock() if runtime.cancel != nil { runtime.cancel() } + return exists +} + +// stopRobotRoomRuntimeToken 只停止仍由该 token 代表的 runtime,避免误停同房间的新一轮启动。 +func (s *Service) stopRobotRoomRuntimeToken(roomID string, token string) { + s.robotRuntimeMu.Lock() + runtime, exists := s.robotRuntimes[roomID] + if !exists || runtime.token != token { + s.robotRuntimeMu.Unlock() + return + } + delete(s.robotRuntimes, roomID) + s.robotRuntimeMu.Unlock() + if runtime.cancel != nil { + runtime.cancel() + } +} + +func (s *Service) runningRobotRoomIDs() []string { + s.robotRuntimeMu.Lock() + defer s.robotRuntimeMu.Unlock() + roomIDs := make([]string, 0, len(s.robotRuntimes)) + for roomID := range s.robotRuntimes { + roomIDs = append(roomIDs, roomID) + } + return roomIDs } func (s *Service) stopAllRobotRoomRuntimes() { @@ -390,12 +516,26 @@ func (s *Service) ensureRobotRoomParticipants(ctx context.Context, config RobotR activeRobots := onlineRobotIDs(config.RobotUserIDs, online) missing := activeTarget - len(activeRobots) if missing > 0 { + // 首次补回缺席机器人也必须从当前 Room Cell 快照选择真实麦位。候选池下标可能 + // 大于 seat_count;同一轮成功补位后立刻占记,避免后续候选复用尚未写回旧快照的麦位。 + occupiedSeats := make(map[int32]bool, len(snapshot.GetMicSeats())) + for _, seat := range snapshot.GetMicSeats() { + if seat.GetUserId() > 0 { + occupiedSeats[seat.GetSeatNo()] = true + } + } for _, userID := range robotRoomFillCandidates(config, online, missing) { // active 机器人房从持久化配置恢复时,旧进程可能已经把机器人 stale 成 left; // 这里只补齐后台配置的活跃人数,不再把整个候选池一次性塞回房间。 - if err := s.joinRobotRoomUser(roomCtx, config, userID, 0, fmt.Sprintf("runtime-join:%d:%d", userID, time.Now().UTC().UnixNano())); err != nil { + seatNo := robotRestoreSeatNo(config, userID, snapshot.GetMicSeats(), occupiedSeats) + if seatNo <= 0 { + logx.Warn(roomCtx, "robot_room_runtime_join_restore_skipped", slog.String("room_id", config.RoomID), slog.Int64("robot_user_id", userID), slog.String("reason", "no_free_seat")) + continue + } + if err := s.joinRobotRoomUser(roomCtx, config, userID, seatNo, fmt.Sprintf("runtime-join:%d:%d", userID, time.Now().UTC().UnixNano())); err != nil { return err } + occupiedSeats[seatNo] = true online[userID] = true activeRobots = append(activeRobots, userID) } @@ -418,13 +558,10 @@ func (s *Service) ensureRobotRoomParticipants(ctx context.Context, config RobotR if userID <= 0 || seated[userID] { continue } - seatNo := preferredRobotSeatNo(config, userID) - if occupiedSeats[seatNo] { - seatNo = firstFreeRobotSeatNo(snapshot.GetMicSeats(), activeTarget) - if seatNo <= 0 { - logx.Warn(roomCtx, "robot_room_runtime_mic_restore_skipped", slog.String("room_id", config.RoomID), slog.Int64("robot_user_id", userID), slog.String("reason", "no_free_seat")) - continue - } + seatNo := robotRestoreSeatNo(config, userID, snapshot.GetMicSeats(), occupiedSeats) + if seatNo <= 0 { + logx.Warn(roomCtx, "robot_room_runtime_mic_restore_skipped", slog.String("room_id", config.RoomID), slog.Int64("robot_user_id", userID), slog.String("reason", "no_free_seat")) + continue } // 虚拟麦位只服务机器人房展示;若并发恢复导致机器人已上麦,Conflict 可视为达成目标。 if _, err := s.RobotVirtualMicUp(roomCtx, RobotMicUpInput{ @@ -442,6 +579,39 @@ func (s *Service) ensureRobotRoomParticipants(ctx context.Context, config RobotR return nil } +// robotRestoreSeatNo 只接受当前 Room Cell 快照里真实存在且空闲的麦位。 +// robot_user_ids 是候选池而不是麦位表:历史配置可包含数十个轮换机器人,因此其下标 +// 可能远大于当前 10/15/20 个麦位;这种 preferred 必须回退到快照中的首个空麦,不能 +// 把不存在的 seat_no 发给 RobotVirtualMicUp 后让每 10 秒恢复扫描永久失败。 +func robotRestoreSeatNo(config RobotRoomConfig, userID int64, seats []*roomv1.SeatState, occupied map[int32]bool) int32 { + preferred := preferredRobotSeatNo(config, userID) + if preferred > 0 && robotSnapshotSeatFree(seats, preferred) && !occupied[preferred] { + return preferred + } + // snapshot 是本轮恢复开始时的只读副本;occupied 还包含本轮刚成功分配、尚未反映到 + // 该副本的麦位。回退时同时检查二者,避免多个待恢复机器人重复选择同一个空麦。 + var firstFree int32 + for _, seat := range seats { + seatNo := seat.GetSeatNo() + if seatNo <= 0 || seat.GetUserId() > 0 || seat.GetLocked() || occupied[seatNo] { + continue + } + if firstFree == 0 || seatNo < firstFree { + firstFree = seatNo + } + } + return firstFree +} + +func robotSnapshotSeatFree(seats []*roomv1.SeatState, seatNo int32) bool { + for _, seat := range seats { + if seat.GetSeatNo() == seatNo { + return seat.GetUserId() == 0 && !seat.GetLocked() + } + } + return false +} + func (s *Service) runRobotRoomRuntime(ctx context.Context, config RobotRoomConfig) { activity := newRobotRoomActivity(config.RobotUserIDs) if snapshot, err := s.currentSnapshot(appcode.WithContext(ctx, config.AppCode), config.RoomID); err == nil { diff --git a/services/room-service/internal/room/service/admin_robot_room_lease_test.go b/services/room-service/internal/room/service/admin_robot_room_lease_test.go new file mode 100644 index 00000000..77377610 --- /dev/null +++ b/services/room-service/internal/room/service/admin_robot_room_lease_test.go @@ -0,0 +1,184 @@ +package service + +import ( + "context" + "testing" + "time" + + "hyapp/pkg/appcode" + "hyapp/services/room-service/internal/router" +) + +type robotRoomScanRepository struct { + Repository + configs []RobotRoomConfig +} + +func (r *robotRoomScanRepository) ListActiveRobotRooms(context.Context) ([]RobotRoomConfig, error) { + return r.configs, nil +} + +func activeRobotRoomConfig(roomID string) RobotRoomConfig { + return RobotRoomConfig{ + AppCode: appcode.Default, + RoomID: roomID, + Status: robotRoomStatusActive, + RobotUserIDs: []int64{1001, 1002}, + } +} + +func TestClaimRobotRoomOwnershipRespectsOtherValidOwner(t *testing.T) { + ctx := context.Background() + now := time.Date(2026, 7, 12, 9, 0, 0, 0, time.UTC) + directory := router.NewMemoryDirectory() + svc := &Service{ + nodeID: "room-node-a", + leaseTTL: 10 * time.Second, + clock: fixedRoomTestClock{now: now}, + directory: directory, + } + config := activeRobotRoomConfig("robot-claim-test") + + owned, ownerNodeID, err := svc.claimRobotRoomOwnership(ctx, config) + if err != nil || !owned || ownerNodeID != "" { + t.Fatalf("unowned robot room should be claimed by this node: owned=%v owner=%q err=%v", owned, ownerNodeID, err) + } + if owned, _, err := svc.claimRobotRoomOwnership(ctx, config); err != nil || !owned { + t.Fatalf("self-owned robot room should renew: owned=%v err=%v", owned, err) + } + + directory.ForceExpire(runtimeRoomKey(config.AppCode, config.RoomID)) + if _, err := directory.EnsureOwner(ctx, runtimeRoomKey(config.AppCode, config.RoomID), "room-node-b", now, 10*time.Second); err != nil { + t.Fatalf("other node takeover: %v", err) + } + owned, ownerNodeID, err = svc.claimRobotRoomOwnership(ctx, config) + if err != nil || owned || ownerNodeID != "room-node-b" { + t.Fatalf("valid lease on another node must not be claimed: owned=%v owner=%q err=%v", owned, ownerNodeID, err) + } +} + +func TestStartRobotRoomRuntimeSkipsWhenOwnedElsewhere(t *testing.T) { + ctx := context.Background() + now := time.Date(2026, 7, 12, 9, 0, 0, 0, time.UTC) + directory := router.NewMemoryDirectory() + svc := &Service{ + nodeID: "room-node-a", + leaseTTL: 10 * time.Second, + clock: fixedRoomTestClock{now: now}, + directory: directory, + } + config := activeRobotRoomConfig("robot-owned-elsewhere") + if _, err := directory.EnsureOwner(ctx, runtimeRoomKey(config.AppCode, config.RoomID), "room-node-b", now, 10*time.Second); err != nil { + t.Fatalf("seed other-node lease: %v", err) + } + + // repository 故意留空:非 owner 分支必须在触达任何房间命令/仓储调用前返回。 + svc.startRobotRoomRuntime(ctx, config) + if len(svc.robotRuntimes) != 0 { + t.Fatalf("non-owner node must not start robot room runtime, got %d", len(svc.robotRuntimes)) + } +} + +func TestStartActiveRobotRoomsReconcilesOwnershipAndInactiveRooms(t *testing.T) { + ctx := context.Background() + now := time.Date(2026, 7, 12, 9, 0, 0, 0, time.UTC) + directory := router.NewMemoryDirectory() + ownedConfig := activeRobotRoomConfig("robot-owned") + lostConfig := activeRobotRoomConfig("robot-lease-lost") + + lostCancelled := false + staleCancelled := false + ownedCancelled := false + svc := &Service{ + nodeID: "room-node-a", + leaseTTL: 10 * time.Second, + clock: fixedRoomTestClock{now: now}, + directory: directory, + repository: &robotRoomScanRepository{configs: []RobotRoomConfig{ownedConfig, lostConfig}}, + robotRuntimes: map[string]robotRoomRuntime{ + ownedConfig.RoomID: {cancel: func() { ownedCancelled = true }, token: "owned-token"}, + lostConfig.RoomID: {cancel: func() { lostCancelled = true }, token: "lost-token"}, + "robot-stale": {cancel: func() { staleCancelled = true }, token: "stale-token"}, + }, + } + if _, err := directory.EnsureOwner(ctx, runtimeRoomKey(ownedConfig.AppCode, ownedConfig.RoomID), svc.nodeID, now, svc.leaseTTL); err != nil { + t.Fatalf("seed self lease: %v", err) + } + if _, err := directory.EnsureOwner(ctx, runtimeRoomKey(lostConfig.AppCode, lostConfig.RoomID), "room-node-b", now, svc.leaseTTL); err != nil { + t.Fatalf("seed other-node lease: %v", err) + } + + svc.startActiveRobotRooms(ctx) + + if _, exists := svc.robotRuntimes[ownedConfig.RoomID]; !exists || ownedCancelled { + t.Fatalf("runtime owned by this node must keep running: exists=%v cancelled=%v", exists, ownedCancelled) + } + if _, exists := svc.robotRuntimes[lostConfig.RoomID]; exists || !lostCancelled { + t.Fatalf("runtime whose lease moved to another node must stop: exists=%v cancelled=%v", exists, lostCancelled) + } + if _, exists := svc.robotRuntimes["robot-stale"]; exists || !staleCancelled { + t.Fatalf("runtime without active config must stop: exists=%v cancelled=%v", exists, staleCancelled) + } +} + +func TestRobotRoomLeaseKeeperStopsRuntimeWhenOwnershipMoves(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + now := time.Date(2026, 7, 12, 9, 0, 0, 0, time.UTC) + directory := router.NewMemoryDirectory() + svc := &Service{ + nodeID: "room-node-a", + leaseTTL: 30 * time.Millisecond, + clock: fixedRoomTestClock{now: now}, + directory: directory, + } + config := activeRobotRoomConfig("robot-keeper") + key := runtimeRoomKey(config.AppCode, config.RoomID) + if _, err := directory.EnsureOwner(ctx, key, svc.nodeID, now, svc.leaseTTL); err != nil { + t.Fatalf("seed self lease: %v", err) + } + + runtimeCancelled := make(chan struct{}) + svc.robotRuntimes = map[string]robotRoomRuntime{ + config.RoomID: {cancel: func() { close(runtimeCancelled) }, token: "keeper-token"}, + } + go svc.runRobotRoomLeaseKeeper(ctx, config, "keeper-token") + + // 续租周期内 keeper 必须保持 owner 且不打断 runtime。 + time.Sleep(50 * time.Millisecond) + select { + case <-runtimeCancelled: + t.Fatal("keeper must not stop runtime while this node still owns the lease") + default: + } + if lease, exists, err := directory.Lookup(ctx, key); err != nil || !exists || lease.NodeID != svc.nodeID || !lease.ValidAt(now) { + t.Fatalf("keeper should keep renewing the lease: lease=%+v exists=%v err=%v", lease, exists, err) + } + + // keeper 会在 ForceExpire 和另一节点抢占之间继续续租,重试直到 node-b 真正拿到有效 lease。 + takeoverDeadline := time.Now().Add(2 * time.Second) + for { + directory.ForceExpire(key) + if _, err := directory.EnsureOwner(ctx, key, "room-node-b", now, time.Minute); err != nil { + t.Fatalf("other node takeover: %v", err) + } + if lease, exists, err := directory.Lookup(ctx, key); err == nil && exists && lease.NodeID == "room-node-b" { + break + } + if time.Now().After(takeoverDeadline) { + t.Fatal("other node could not take over the lease") + } + } + + select { + case <-runtimeCancelled: + case <-time.After(2 * time.Second): + t.Fatal("keeper must stop the local runtime after ownership moved to another node") + } + svc.robotRuntimeMu.Lock() + _, exists := svc.robotRuntimes[config.RoomID] + svc.robotRuntimeMu.Unlock() + if exists { + t.Fatal("keeper must remove the runtime entry after ownership loss") + } +} diff --git a/services/room-service/internal/room/service/admin_robot_room_runtime_restore_test.go b/services/room-service/internal/room/service/admin_robot_room_runtime_restore_test.go index 98de44a2..3e1c910b 100644 --- a/services/room-service/internal/room/service/admin_robot_room_runtime_restore_test.go +++ b/services/room-service/internal/room/service/admin_robot_room_runtime_restore_test.go @@ -89,6 +89,101 @@ func TestRobotRoomRuntimeStartRestoresLeftRobots(t *testing.T) { } } +func TestRobotRoomRuntimeStartRestoresHighIndexOwnerToRealSeat(t *testing.T) { + ctx := appcode.WithContext(context.Background(), appcode.Default) + repository := mysqltest.NewRepository(t) + svc := roomservice.New(roomservice.Config{ + NodeID: "node-robot-runtime-high-index-restore-test", + LeaseTTL: 10 * time.Second, + RankLimit: 20, + SnapshotEveryN: 1, + }, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher()) + + // 候选池有 12 个机器人而房间只有 10 个麦位;owner 排序后位于第 12 位。 + // bootstrap 会把 active owner 放到真实 seat 1,运行时恢复不能再按候选池下标猜 seat 12。 + candidates := make([]int64, 12) + for index := range candidates { + candidates[index] = int64(2001 + index) + } + ownerID := candidates[len(candidates)-1] + created, err := svc.AdminCreateRobotRoom(ctx, &roomv1.AdminCreateRobotRoomRequest{ + Meta: roomservice.NewRequestMeta("", 9001), + OwnerRobotUserId: ownerID, + CandidateRobotUserIds: candidates, + MinRobotCount: 2, + MaxRobotCount: 2, + SeatCount: 10, + RoomName: "Robot Runtime High Index Restore", + RoomAvatar: testRoomCoverURL, + VisibleRegionId: 686, + GiftRule: &roomv1.AdminRobotRoomGiftRule{ + GiftIds: []string{"normal_gift"}, + LuckyGiftIds: []string{"lucky_gift"}, + NormalGiftIntervalMs: 10000, + LuckyComboMin: 1, + LuckyComboMax: 1, + LuckyPauseMinMs: 5000, + LuckyPauseMaxMs: 5000, + }, + AdminId: 9001, + }) + if err != nil { + t.Fatalf("create high-index-owner robot room failed: %v", err) + } + roomID := created.GetRoom().GetRoomId() + robotIDs := created.GetRoom().GetRobotUserIds() + if len(robotIDs) != len(candidates) || robotIDs[len(robotIDs)-1] != ownerID { + t.Fatalf("test requires owner at preferred seat 12, got owner=%d robots=%v", ownerID, robotIDs) + } + + initial, err := svc.GetRoomSnapshot(ctx, &roomv1.GetRoomSnapshotRequest{ + Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID}, + RoomId: roomID, + ViewerUserId: ownerID, + }) + if err != nil { + t.Fatalf("initial high-index-owner snapshot failed: %v", err) + } + if _, err := svc.AdminSetRobotRoomStatus(ctx, &roomv1.AdminSetRobotRoomStatusRequest{ + Meta: roomservice.NewRequestMeta(roomID, 9001), + Status: "stopped", + AdminId: 9001, + }); err != nil { + t.Fatalf("stop high-index-owner robot room failed: %v", err) + } + for _, user := range initial.GetRoom().GetOnlineUsers() { + if _, err := svc.LeaveRoom(ctx, &roomv1.LeaveRoomRequest{Meta: roomservice.NewRequestMeta(roomID, user.GetUserId())}); err != nil { + t.Fatalf("leave initial robot %d failed: %v", user.GetUserId(), err) + } + } + + if _, err := svc.AdminSetRobotRoomStatus(ctx, &roomv1.AdminSetRobotRoomStatusRequest{ + Meta: roomservice.NewRequestMeta(roomID, 9001), + Status: "active", + AdminId: 9001, + }); err != nil { + t.Fatalf("restart high-index-owner robot room failed: %v", err) + } + restored, err := svc.GetRoomSnapshot(ctx, &roomv1.GetRoomSnapshotRequest{ + Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID}, + RoomId: roomID, + ViewerUserId: ownerID, + }) + if err != nil { + t.Fatalf("snapshot after high-index-owner restore failed: %v", err) + } + ownerSeat := int32(0) + for _, seat := range restored.GetRoom().GetMicSeats() { + if seat.GetUserId() == ownerID { + ownerSeat = seat.GetSeatNo() + break + } + } + if ownerSeat <= 0 || ownerSeat > int32(len(restored.GetRoom().GetMicSeats())) { + t.Fatalf("high-index owner must be restored to a real snapshot seat, got seat=%d", ownerSeat) + } +} + func snapshotContainsUser(snapshot *roomv1.RoomSnapshot, userID int64) bool { for _, user := range snapshot.GetOnlineUsers() { if user.GetUserId() == userID { diff --git a/services/room-service/internal/room/service/admin_robot_room_runtime_test.go b/services/room-service/internal/room/service/admin_robot_room_runtime_test.go index 39c0a0fc..8588bc90 100644 --- a/services/room-service/internal/room/service/admin_robot_room_runtime_test.go +++ b/services/room-service/internal/room/service/admin_robot_room_runtime_test.go @@ -54,7 +54,10 @@ func TestRobotRoomStalePresenceKeepsConfiguredRobots(t *testing.T) { } } -func TestReplayRoomHeartbeatAfterLeaveIsIgnored(t *testing.T) { +func TestReplayRoomHeartbeatWithoutPresenceRecreatesIt(t *testing.T) { + // 线上心跳会对丢失的 presence 静默恢复后才提交命令日志;回放遇到"用户不存在"的 + // 已提交心跳必须重现恢复(含 Version++),并且绝不能让整个 Room Cell 恢复失败。 + // 旧语义的"忽略跳过"会让恢复出的 OnlineUsers/版本与快照谱系分叉。 current := &state.RoomState{ OnlineUsers: map[int64]*state.RoomUserState{}, } @@ -67,13 +70,14 @@ func TestReplayRoomHeartbeatAfterLeaveIsIgnored(t *testing.T) { }, }, 1781675913212) if err != nil { - t.Fatalf("stale heartbeat after leave must not break recovery: %v", err) + t.Fatalf("heartbeat without presence must not break recovery: %v", err) } - if len(current.OnlineUsers) != 0 { - t.Fatalf("stale heartbeat must not recreate presence: %+v", current.OnlineUsers) + recovered, exists := current.OnlineUsers[123456] + if !exists || recovered.Role != "audience" || recovered.LastSeenAtMS != 1781675912713 { + t.Fatalf("replayed heartbeat heal must recreate presence from SentAtMS: %+v", current.OnlineUsers) } - if current.Version != 0 { - t.Fatalf("ignored heartbeat must not advance room version, got %d", current.Version) + if current.Version != 1 { + t.Fatalf("replayed heartbeat heal must advance room version exactly once, got %d", current.Version) } } @@ -119,6 +123,51 @@ func TestNormalizeRobotRoomGiftRuleAppliesBehaviorDefaults(t *testing.T) { } } +func TestRobotRestoreSeatNoFallsBackWhenPreferredExceedsSnapshot(t *testing.T) { + robots := make([]int64, 12) + for index := range robots { + robots[index] = int64(1001 + index) + } + seats := make([]*roomv1.SeatState, 10) + for index := range seats { + seats[index] = &roomv1.SeatState{SeatNo: int32(index + 1)} + } + seats[0].UserId = robots[0] + + // 候选池第 12 个机器人历史 preferred=12,但当前房间只有 10 个麦位; + // 恢复必须选择真实存在的首个空麦 2,而不是把 seat=12 交给 Room Cell。 + got := robotRestoreSeatNo( + RobotRoomConfig{RobotUserIDs: robots, SeatCount: 10}, + robots[11], + seats, + map[int32]bool{1: true}, + ) + if got != 2 { + t.Fatalf("out-of-range preferred seat must fall back to first free snapshot seat: got %d want 2", got) + } + + got = robotRestoreSeatNo( + RobotRoomConfig{RobotUserIDs: robots, SeatCount: 10}, + robots[11], + seats, + map[int32]bool{1: true, 2: true}, + ) + if got != 3 { + t.Fatalf("same-pass occupied seat must not be selected twice: got %d want 3", got) + } + + seats[2].Locked = true + got = robotRestoreSeatNo( + RobotRoomConfig{RobotUserIDs: robots, SeatCount: 10}, + robots[11], + seats, + map[int32]bool{1: true, 2: true}, + ) + if got != 4 { + t.Fatalf("locked snapshot seat is not available for restore: got %d want 4", got) + } +} + func TestRobotRoomActivityPrefersOtherReplacement(t *testing.T) { activity := newRobotRoomActivity([]int64{1001, 1002, 1003}) activity.markActive(1001) diff --git a/services/room-service/internal/room/service/background.go b/services/room-service/internal/room/service/background.go index fbbf3585..643d4335 100644 --- a/services/room-service/internal/room/service/background.go +++ b/services/room-service/internal/room/service/background.go @@ -28,6 +28,10 @@ func (s *Service) SaveRoomBackground(ctx context.Context, req *roomv1.SaveRoomBa if err := s.requireRoomOwnerMeta(ctx, roomID, actorUserID); err != nil { return nil, err } + if err := s.requireCustomRoomBackgroundBenefit(ctx, req.GetMeta().GetRequestId(), actorUserID); err != nil { + // 保存素材本身就会形成持久数据,P1 App 必须在写库前校验当前 effective benefit。 + return nil, err + } nowMS := s.clock.Now().UnixMilli() background, err := s.repository.SaveRoomBackground(ctx, RoomBackgroundImage{ @@ -107,6 +111,10 @@ func (s *Service) SetRoomBackground(ctx context.Context, req *roomv1.SetRoomBack if current.OwnerUserID != cmd.ActorUserID() { return mutationResult{}, nil, xerr.New(xerr.PermissionDenied, "permission denied") } + if err := s.requireCustomRoomBackgroundBenefit(ctx, req.GetMeta().GetRequestId(), cmd.ActorUserID()); err != nil { + // 放在 mutate 回调内可让同一 command_id 的已提交重试先命中幂等,不会因 VIP 后续到期改写旧命令结果。 + return mutationResult{}, nil, err + } if current.RoomExt == nil { current.RoomExt = make(map[string]string) } diff --git a/services/room-service/internal/room/service/create_room.go b/services/room-service/internal/room/service/create_room.go index 6b5334b1..e36a1b6f 100644 --- a/services/room-service/internal/room/service/create_room.go +++ b/services/room-service/internal/room/service/create_room.go @@ -145,7 +145,7 @@ func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest) if lease.NodeID != s.nodeID { // 有其他有效 owner 时当前节点不能创建或接管该房间。 - return nil, xerr.New(xerr.Conflict, fmt.Sprintf("room is owned by %s", lease.NodeID)) + return nil, roomOwnedByOtherNodeError(lease.NodeID) } if _, exists, err := s.repository.GetRoomMeta(ctx, cmd.RoomID()); err != nil { diff --git a/services/room-service/internal/room/service/follow_test.go b/services/room-service/internal/room/service/follow_test.go index 9e1e3012..22994159 100644 --- a/services/room-service/internal/room/service/follow_test.go +++ b/services/room-service/internal/room/service/follow_test.go @@ -32,6 +32,20 @@ func (followTestWallet) GrantResourceGroup(context.Context, *walletv1.GrantResou return &walletv1.ResourceGrantResponse{Grant: &walletv1.ResourceGrant{GrantId: "grant-follow-test"}}, nil } +func (followTestWallet) GetMyVip(_ context.Context, req *walletv1.GetMyVipRequest) (*walletv1.GetMyVipResponse, error) { + return legacyVIPResponse(req.GetUserId()), nil +} + +func legacyVIPResponse(userID int64) *walletv1.GetMyVipResponse { + // 大多数既有 room-service 测试运行在 Lalu;显式返回 legacy program,验证新增门禁不会暗改旧 App 行为。 + return &walletv1.GetMyVipResponse{ + State: &walletv1.VipState{ + ProgramConfig: &walletv1.VipProgramConfig{AppCode: appcode.Default, ProgramType: "legacy_timed", Status: "active"}, + EffectiveVip: &walletv1.UserVip{UserId: userID}, + }, + } +} + func TestRoomFollowAffectsSnapshotAndFollowedFeed(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) diff --git a/services/room-service/internal/room/service/gift.go b/services/room-service/internal/room/service/gift.go index 0cf241b4..f6037d18 100644 --- a/services/room-service/internal/room/service/gift.go +++ b/services/room-service/internal/room/service/gift.go @@ -11,6 +11,11 @@ import ( roomv1 "hyapp.local/api/proto/room/v1" ) +const ( + maxGiftTargetsPerCommand = 30 + maxGiftComboSessionIDLength = 128 +) + // SendGift 先同步调用 wallet,再在 Room Cell 内完成热度、榜单和房间事件落地。 func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*roomv1.SendGiftResponse, error) { return s.sendGift(ctx, req, giftSendOptions{}) @@ -32,8 +37,19 @@ type giftSendOptions struct { } func (s *Service) sendGift(ctx context.Context, req *roomv1.SendGiftRequest, options giftSendOptions) (*roomv1.SendGiftResponse, error) { + return s.sendGiftWithRecovery(ctx, req, options, GiftOperation{}) +} + +func (s *Service) sendGiftWithRecovery(ctx context.Context, req *roomv1.SendGiftRequest, options giftSendOptions, recovery GiftOperation) (*roomv1.SendGiftResponse, error) { ctx = contextFromMeta(ctx, req.GetMeta()) flow := newGiftFlow(s, ctx, req, options) + if recovery.CommandID != "" { + if err := flow.resumeGiftOperation(recovery); err != nil { + return nil, err + } + } else if err := flow.loadGiftOperation(); err != nil { + return nil, err + } result, err := s.mutateRoom(ctx, flow.cmd, true, func(now time.Time, current *state.RoomState, localRank *rank.LocalRank) (mutationResult, []outbox.Record, error) { // 阶段顺序保持原 sendGift 同步主链路:先校验房间态,再取房间事实,再扣费/抽奖,最后提交 Room Cell 状态和构造事件。 @@ -43,6 +59,9 @@ func (s *Service) sendGift(ctx context.Context, req *roomv1.SendGiftRequest, opt if err := flow.prepareRoomFacts(); err != nil { return mutationResult{}, nil, err } + if err := flow.ensureGiftOperation(now); err != nil { + return mutationResult{}, nil, err + } if err := flow.debitAndSettle(now); err != nil { return mutationResult{}, nil, err } @@ -52,6 +71,7 @@ func (s *Service) sendGift(ctx context.Context, req *roomv1.SendGiftRequest, opt return flow.buildMutationResult(now, current) }) if err != nil { + flow.handleGiftOperationFailure(err) return nil, err } @@ -67,5 +87,6 @@ func (s *Service) sendGift(ctx context.Context, req *roomv1.SendGiftRequest, opt CoinBalanceAfter: result.coinBalanceAfter, GiftIncomeBalanceAfter: result.giftIncomeBalanceAfter, BatchDisplay: result.batchDisplay, + V2: result.giftV2, }, nil } diff --git a/services/room-service/internal/room/service/gift_billing.go b/services/room-service/internal/room/service/gift_billing.go index c5676229..9af8701a 100644 --- a/services/room-service/internal/room/service/gift_billing.go +++ b/services/room-service/internal/room/service/gift_billing.go @@ -2,10 +2,12 @@ package service import ( "context" + "crypto/sha256" "fmt" walletv1 "hyapp.local/api/proto/wallet/v1" "hyapp/pkg/appcode" + "hyapp/pkg/giftlimits" "hyapp/pkg/xerr" "hyapp/services/room-service/internal/room/command" ) @@ -122,8 +124,15 @@ func giftTargetCommandID(commandID string, targetUserID int64, targetCount int) if targetCount <= 1 { return commandID } - // 钱包以 command_id 做幂等主键;多目标必须按接收方派生稳定子命令,才能在同一批内保存独立交易。 - return fmt.Sprintf("%s:target:%d", commandID, targetUserID) + // 短主键沿用首版派生格式,保证滚动发布期间已经进入 saga 的请求仍命中原 wallet/lucky 幂等事实。 + legacy := fmt.Sprintf("%s:target:%d", commandID, targetUserID) + if len(legacy) <= giftlimits.MaxCommandIDBytes { + return legacy + } + // room 主 command_id 本身允许占满 128 bytes,不能再直接拼接 target。固定域分隔哈希同时绑定 + // parent 和 target,长度恒定且重试稳定;wallet 子交易与 lucky draw 继续共用这一把幂等键。 + digest := sha256.Sum256([]byte(fmt.Sprintf("room-gift-target-v2\x00%s\x00%d", commandID, targetUserID))) + return fmt.Sprintf("gift-target:v2:%x", digest) } func giftTargetBillingsFromBatch(resp *walletv1.BatchDebitGiftResponse, targetUserIDs []int64) ([]giftTargetBilling, error) { diff --git a/services/room-service/internal/room/service/gift_billing_internal_test.go b/services/room-service/internal/room/service/gift_billing_internal_test.go new file mode 100644 index 00000000..03a0ef6d --- /dev/null +++ b/services/room-service/internal/room/service/gift_billing_internal_test.go @@ -0,0 +1,36 @@ +package service + +import ( + "strings" + "testing" + + "hyapp/pkg/giftlimits" +) + +func TestGiftTargetCommandIDBoundsMaxLengthParentAndStaysDeterministic(t *testing.T) { + parent := strings.Repeat("p", giftlimits.MaxCommandIDBytes) + first := giftTargetCommandID(parent, 9001, 2) + retry := giftTargetCommandID(parent, 9001, 2) + otherTarget := giftTargetCommandID(parent, 9002, 2) + if first != retry { + t.Fatalf("same parent/target must derive a stable child command: %q != %q", first, retry) + } + if len(first) > giftlimits.MaxCommandIDBytes { + t.Fatalf("derived child command has %d bytes, max %d", len(first), giftlimits.MaxCommandIDBytes) + } + if first == otherTarget { + t.Fatalf("different targets must not share a child command: %q", first) + } + if !strings.HasPrefix(first, "gift-target:v2:") { + t.Fatalf("overflowing legacy key must use the bounded v2 derivation: %q", first) + } +} + +func TestGiftTargetCommandIDPreservesCompatibleKeys(t *testing.T) { + if got := giftTargetCommandID("existing-command", 42, 1); got != "existing-command" { + t.Fatalf("single target command changed: %q", got) + } + if got := giftTargetCommandID("existing-command", 42, 2); got != "existing-command:target:42" { + t.Fatalf("existing bounded multi-target command changed: %q", got) + } +} diff --git a/services/room-service/internal/room/service/gift_flow.go b/services/room-service/internal/room/service/gift_flow.go index e3a4965d..7f1e1c5d 100644 --- a/services/room-service/internal/room/service/gift_flow.go +++ b/services/room-service/internal/room/service/gift_flow.go @@ -11,6 +11,7 @@ import ( roomv1 "hyapp.local/api/proto/room/v1" walletv1 "hyapp.local/api/proto/wallet/v1" "hyapp/pkg/appcode" + "hyapp/pkg/giftlimits" "hyapp/pkg/tencentim" "hyapp/pkg/xerr" "hyapp/services/room-service/internal/room/command" @@ -52,6 +53,13 @@ type giftFlow struct { batchDisplay *roomv1.SendGiftBatchDisplay batchDisplayEnabled bool suppressDirectIMEventTypes map[string]bool + + // operationStarted 后任何失败都必须由服务端更新 saga;客户端断线不能成为扣款后完成结算的必要条件。 + operationStarted bool + debitCompleted bool + drawCompleted bool + recovering bool + operation GiftOperation } func newGiftFlow(s *Service, ctx context.Context, req *roomv1.SendGiftRequest, options giftSendOptions) *giftFlow { @@ -70,6 +78,8 @@ func newGiftFlow(s *Service, ctx context.Context, req *roomv1.SendGiftRequest, o EntitlementID: strings.TrimSpace(req.GetEntitlementId()), Source: strings.TrimSpace(req.GetSource()), DisplayMode: displayMode, + ComboSessionID: strings.TrimSpace(req.GetComboSessionId()), + BatchSeq: req.GetBatchSeq(), SenderCountryID: req.GetSenderCountryId(), SenderRegionID: req.GetSenderRegionId(), TargetIsHost: req.GetTargetIsHost(), @@ -96,9 +106,11 @@ func newGiftFlow(s *Service, ctx context.Context, req *roomv1.SendGiftRequest, o } func (f *giftFlow) validateBeforeDebit(current *state.RoomState) error { - if _, exists := current.OnlineUsers[f.cmd.ActorUserID()]; !exists { - // 送礼者必须在房间业务 presence 内;没有进入 Room Cell 的用户不能触发钱包扣费。 - return xerr.New(xerr.NotFound, "sender not in room") + if !f.recovering { + if _, exists := current.OnlineUsers[f.cmd.ActorUserID()]; !exists { + // 送礼者必须在房间业务 presence 内;没有进入 Room Cell 的用户不能触发钱包扣费。 + return xerr.New(xerr.NotFound, "sender not in room") + } } if f.cmd.TargetType != "user" { // v1 HTTP 契约已预留 all_mic/all_room/couple;账务拆单和房间表现策略补齐前先 fail-close。 @@ -107,7 +119,10 @@ func (f *giftFlow) validateBeforeDebit(current *state.RoomState) error { if len(f.cmd.TargetUserIDs) == 0 || f.cmd.TargetUserID <= 0 { return xerr.New(xerr.InvalidArgument, "target_user_ids is required") } - if f.batchDisplayEnabled { + if f.recovering { + // operation 只会在首次 presence/目标校验通过后创建;扣款未知或已完成时必须使用当时固化的实际目标继续结算, + // 不能因用户随后离房而永久卡住已扣款 saga。 + } else if f.batchDisplayEnabled { if len(f.cmd.RequestedTargetUserIDs) < 2 { // batch-send 只承载“全部人/多人”语义;过滤后可以只剩一个真实送达目标,但原始请求不能退化成单人入口。 return xerr.New(xerr.InvalidArgument, "batch target_user_ids requires at least two users") @@ -124,7 +139,7 @@ func (f *giftFlow) validateBeforeDebit(current *state.RoomState) error { } } } - if f.robot.Enabled { + if f.robot.Enabled && !f.recovering { if err := requireRobotGiftParticipants(current, f.cmd.ActorUserID(), f.cmd.TargetUserIDs, f.robot.RobotUserIDs, !f.robot.RealRoomHeat); err != nil { return err } @@ -133,6 +148,19 @@ func (f *giftFlow) validateBeforeDebit(current *state.RoomState) error { // 礼物 ID 和数量是钱包查服务端价格的最小输入,客户端不再提交礼物单价。 return xerr.New(xerr.InvalidArgument, "gift_id and gift_count must be valid") } + if len(f.cmd.TargetUserIDs) > maxGiftTargetsPerCommand { + // 限制必须由 Room owner 再执行一次,不能只相信 gateway;内部调用、旧网关或回放入口 + // 都可能绕过 HTTP 层。gift_count 和目标数分别限制,避免分配异常大的账务请求。 + return xerr.New(xerr.InvalidArgument, "gift command exceeds owner limits") + } + if _, ok := giftlimits.WorkUnits(int64(f.cmd.GiftCount), len(f.cmd.TargetUserIDs)); !ok { + // 幸运礼物按“数量 x 目标数”逐份推进奖池状态,真实成本不是 HTTP 请求数;总单位上限 + // 防止一个合法外形的大批次独占 Room Cell 和 lucky-gift 数据库事务。 + return xerr.New(xerr.InvalidArgument, "gift command units exceed owner limit") + } + if len(f.cmd.ComboSessionID) > maxGiftComboSessionIDLength || f.cmd.BatchSeq < 0 || (f.cmd.ComboSessionID != "" && f.cmd.BatchSeq <= 0) { + return xerr.New(xerr.InvalidArgument, "gift combo metadata is invalid") + } if strings.EqualFold(f.cmd.Source, "bag") && strings.TrimSpace(f.cmd.EntitlementID) == "" { // 背包送礼只改变扣费来源,不改变房间事件;因此必须在扣费前明确指定要扣的权益,不能让 wallet 猜测某个库存。 return xerr.New(xerr.InvalidArgument, "entitlement_id is required for bag gift") @@ -193,25 +221,31 @@ func (f *giftFlow) prepareRoomFacts() error { } f.roomMeta = roomMeta if f.cmd.PoolID != "" && !f.cmd.SyntheticLuckyGift { - // 客户端显式传 pool_id 时必须先检查活动规则;规则未启用就拒绝,避免先扣费再发现不能抽奖。 - if f.s.luckyGift == nil { - return xerr.New(xerr.Unavailable, "lucky gift service is not configured") + if f.recovering { + // saga 只在首次 CheckLuckyGift 已通过后创建;恢复阶段直接复用稳定 command_id 开奖, + // 避免规则临时下线把已经扣款的操作卡在预检查上。 + f.luckyEnabled = true + } else { + // 客户端显式传 pool_id 时必须先检查活动规则;规则未启用就拒绝,避免先扣费再发现不能抽奖。 + if f.s.luckyGift == nil { + return xerr.New(xerr.Unavailable, "lucky gift service is not configured") + } + checkResp, err := f.s.luckyGift.CheckLuckyGift(f.ctx, &luckygiftv1.CheckLuckyGiftRequest{ + Meta: luckyGiftMetaFromRoom(f.ctx, f.req.GetMeta()), + UserId: f.cmd.ActorUserID(), + RoomId: f.cmd.RoomID(), + GiftId: f.cmd.GiftID, + PoolId: f.cmd.PoolID, + }) + if err != nil { + return err + } + if checkResp == nil || !checkResp.GetEnabled() { + return xerr.New(xerr.RuleNotActive, "lucky gift rule is not active") + } + // Check 只说明“允许按该 pool 抽奖”,真实价格、区域可用性和扣费仍以 wallet-service 为准。 + f.luckyEnabled = true } - checkResp, err := f.s.luckyGift.CheckLuckyGift(f.ctx, &luckygiftv1.CheckLuckyGiftRequest{ - Meta: luckyGiftMetaFromRoom(f.ctx, f.req.GetMeta()), - UserId: f.cmd.ActorUserID(), - RoomId: f.cmd.RoomID(), - GiftId: f.cmd.GiftID, - PoolId: f.cmd.PoolID, - }) - if err != nil { - return err - } - if checkResp == nil || !checkResp.GetEnabled() { - return xerr.New(xerr.RuleNotActive, "lucky gift rule is not active") - } - // Check 只说明“允许按该 pool 抽奖”,真实价格、区域可用性和扣费仍以 wallet-service 为准。 - f.luckyEnabled = true } rocketConfig, err := f.s.roomRocketConfig(f.ctx) if err != nil { @@ -241,6 +275,10 @@ func (f *giftFlow) debitAndSettle(now time.Time) error { f.settledCommand.GiftTypeCode = f.billing.GetGiftTypeCode() f.settledCommand.HostPeriodDiamondAdded = f.billing.GetHostPeriodDiamondAdded() f.settledCommand.HostPeriodCycleKey = f.billing.GetHostPeriodCycleKey() + if err := f.markGiftOperationDebited(now); err != nil { + // wallet 已返回稳定回执但 saga 落盘失败时不能继续抽奖;后台会用相同 command_id 重取钱包回执后推进。 + return err + } if f.cmd.SyntheticLuckyGift { f.luckyGift = syntheticLuckyGiftResult(f.cmd, f.targetBillings, now) if f.luckyGift != nil { @@ -261,6 +299,10 @@ func (f *giftFlow) debitAndSettle(now time.Time) error { f.luckyGift = aggregateLuckyGiftResults(f.cmd.ID(), f.luckyGifts) } } + if err := f.markGiftOperationDrawn(now); err != nil { + // 抽奖 owner 已按 command_id 幂等;即使进度写入失败,恢复也只会重放同一结果,不会再次产生奖金。 + return err + } return nil }) } @@ -488,6 +530,27 @@ func (f *giftFlow) toMutationResult(now time.Time, current *state.RoomState) mut }, }, } + result.giftV2 = &roomv1.SendGiftResultV2{ + ApiVersion: 2, + CommandId: f.cmd.ID(), + ComboSessionId: f.cmd.ComboSessionID, + BatchSeq: f.cmd.BatchSeq, + IdempotentReplay: false, + CommittedRoomVersion: current.Version, + BillingReceiptId: f.billing.GetBillingReceiptId(), + CoinBalanceAfter: finalCoinBalanceAfter, + GiftIncomeBalanceAfter: f.billing.GetGiftIncomeBalanceAfter(), + SettledGiftCount: f.cmd.GiftCount, + SettledTargetUserIds: append([]int64(nil), f.cmd.TargetUserIDs...), + BalanceVersion: f.billing.GetBalanceVersion(), + LuckyGift: f.luckyGift, + LuckyGifts: append([]*roomv1.LuckyGiftDrawResult(nil), f.luckyGifts...), + LuckyHits: luckyHitsFromResults(f.luckyGifts), + BatchDisplay: f.batchDisplay, + RoomHeat: current.Heat, + GiftRank: cloneProtoRank(current.GiftRank), + OperationStatus: GiftOperationStatusCommitted, + } if f.cmd.RobotGift { // 机器人房间可以增加房间热度和麦位收礼热度,但不进入跨房礼物榜和房间送礼统计。 result.roomGiftLeaderboard = nil diff --git a/services/room-service/internal/room/service/gift_lucky.go b/services/room-service/internal/room/service/gift_lucky.go index 39598342..345618c2 100644 --- a/services/room-service/internal/room/service/gift_lucky.go +++ b/services/room-service/internal/room/service/gift_lucky.go @@ -92,6 +92,11 @@ func luckyGiftMetaForTarget(ctx context.Context, meta *roomv1.RequestMeta, cmd c PoolId: cmd.PoolID, VisibleRegionId: roomMeta.VisibleRegionID, CountryId: cmd.SenderCountryID, + // 展示资料来自 gateway 本次送礼入口的用户快照,随抽奖事实持久化后,下游不需要在异步消费时反查当前资料。 + SenderName: giftDisplayName(cmd.SenderDisplayProfile), + SenderAvatar: strings.TrimSpace(cmd.SenderDisplayProfile.Avatar), + SenderDisplayUserId: strings.TrimSpace(cmd.SenderDisplayProfile.DisplayUserID), + SenderPrettyDisplayUserId: strings.TrimSpace(cmd.SenderDisplayProfile.PrettyDisplayUserID), } } @@ -156,7 +161,7 @@ func luckyGiftResultFromProto(result *luckygiftv1.LuckyGiftDrawResult, targetUse if result == nil { return nil } - return &roomv1.LuckyGiftDrawResult{ + mapped := &roomv1.LuckyGiftDrawResult{ Enabled: true, DrawId: result.GetDrawId(), CommandId: result.GetCommandId(), @@ -177,6 +182,16 @@ func luckyGiftResultFromProto(result *luckygiftv1.LuckyGiftDrawResult, targetUse CoinBalanceAfter: 0, TargetUserId: targetUserID, } + for _, hit := range result.GetHits() { + if hit == nil || hit.GetRewardCoins() <= 0 { + continue + } + mapped.Hits = append(mapped.Hits, &roomv1.SendGiftLuckyHit{ + GiftIndex: hit.GetGiftIndex(), DrawId: hit.GetDrawId(), TargetUserId: targetUserID, + SelectedTierId: hit.GetSelectedTierId(), MultiplierPpm: hit.GetMultiplierPpm(), RewardCoins: hit.GetRewardCoins(), + }) + } + return mapped } func aggregateLuckyGiftResults(commandID string, results []*roomv1.LuckyGiftDrawResult) *roomv1.LuckyGiftDrawResult { @@ -198,6 +213,7 @@ func aggregateLuckyGiftResults(commandID string, results []*roomv1.LuckyGiftDraw aggregate.StageFeedback = false aggregate.HighMultiplier = false aggregate.CoinBalanceAfter = 0 + aggregate.Hits = nil for _, result := range results { if result == nil { continue @@ -210,6 +226,7 @@ func aggregateLuckyGiftResults(commandID string, results []*roomv1.LuckyGiftDraw aggregate.EffectiveRewardCoins += result.GetEffectiveRewardCoins() aggregate.StageFeedback = aggregate.StageFeedback || result.GetStageFeedback() aggregate.HighMultiplier = aggregate.HighMultiplier || result.GetHighMultiplier() + aggregate.Hits = append(aggregate.Hits, result.GetHits()...) switch result.GetRewardStatus() { case "pending": aggregate.RewardStatus = "pending" @@ -233,9 +250,10 @@ func syntheticLuckyGiftResult(cmd command.SendGift, targetBillings []giftTargetB } rewardCoins := firstPositiveInt64(cmd.SyntheticLuckyRewardCoins, targetBillings[0].Billing.GetCoinSpent()*5) multiplier := firstPositiveInt64(cmd.SyntheticLuckyMultiplierPPM, 5000000) + drawID := fmt.Sprintf("robot_lucky_%s_%d", cmd.ID(), now.UnixMilli()) return &roomv1.LuckyGiftDrawResult{ Enabled: true, - DrawId: fmt.Sprintf("robot_lucky_%s_%d", cmd.ID(), now.UnixMilli()), + DrawId: drawID, CommandId: cmd.ID(), PoolId: cmd.PoolID, GiftId: cmd.GiftID, @@ -247,6 +265,9 @@ func syntheticLuckyGiftResult(cmd command.SendGift, targetBillings []giftTargetB HighMultiplier: multiplier >= 5000000, CreatedAtMs: now.UnixMilli(), TargetUserId: cmd.TargetUserID, + Hits: []*roomv1.SendGiftLuckyHit{{ + GiftIndex: 1, DrawId: drawID, TargetUserId: cmd.TargetUserID, MultiplierPpm: multiplier, RewardCoins: rewardCoins, + }}, } } diff --git a/services/room-service/internal/room/service/gift_operation.go b/services/room-service/internal/room/service/gift_operation.go new file mode 100644 index 00000000..2f32b009 --- /dev/null +++ b/services/room-service/internal/room/service/gift_operation.go @@ -0,0 +1,357 @@ +package service + +import ( + "bytes" + "context" + "errors" + "fmt" + "log/slog" + "strings" + "sync" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + roomv1 "hyapp.local/api/proto/room/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/room-service/internal/room/command" +) + +const ( + giftOperationPersistTimeout = 2 * time.Second + giftOperationRetryDelay = time.Second + // 每条恢复任务从进入 wallet/lucky/Room Cell 到返回都受独立 deadline 约束;不能复用 worker 的长生命周期 ctx。 + giftOperationAttemptTimeout = 8 * time.Second + // claim 必须覆盖尝试窗口、Room Cell 已扣费后的受保护提交和失败状态落盘,并保留抖动余量。 + giftOperationClaimTTL = 20 * time.Second +) + +func (f *giftFlow) loadGiftOperation() error { + operation, exists, err := f.s.repository.GetGiftOperation(f.ctx, f.cmd.RoomID(), f.cmd.ID()) + if err != nil || !exists { + return err + } + currentPayload, err := command.IdempotencyPayloadForCommand(f.cmd) + if err != nil { + return err + } + storedPayload, err := command.IdempotencyPayload(operation.CommandPayload) + if err != nil { + return err + } + if !bytes.Equal(currentPayload, storedPayload) { + return xerr.New(xerr.Conflict, "command_id payload conflict") + } + if operation.Status == GiftOperationStatusCommitted { + // committed 与 command log 在同一 MySQL 事务写入;只出现一边说明事实被人工破坏,不能再调用钱包重建。 + if _, exists, err := f.s.repository.GetCommand(f.ctx, f.cmd.RoomID(), f.cmd.ID()); err != nil { + return err + } else if !exists { + return xerr.New(xerr.Internal, "committed gift operation is missing command result") + } + return nil + } + if operation.Status != GiftOperationStatusPending && operation.Status != GiftOperationStatusDebited && operation.Status != GiftOperationStatusDrawn { + return nil + } + return f.resumeGiftOperation(operation) +} + +func (f *giftFlow) resumeGiftOperation(operation GiftOperation) error { + var storedRequest roomv1.SendGiftRequest + if err := proto.Unmarshal(operation.RequestPayload, &storedRequest); err != nil { + return xerr.New(xerr.Internal, "stored send gift request cannot be decoded") + } + storedCommand, err := command.Deserialize((command.SendGift{}).Type(), operation.CommandPayload) + if err != nil { + return xerr.New(xerr.Internal, "stored send gift command cannot be decoded") + } + giftCommand, ok := storedCommand.(*command.SendGift) + if !ok || giftCommand == nil { + return xerr.New(xerr.Internal, "stored send gift command type is invalid") + } + f.req = &storedRequest + f.cmd = *giftCommand + f.settledCommand = *giftCommand + f.operation = operation + f.operationStarted = true + f.recovering = true + f.debitCompleted = operation.Status == GiftOperationStatusDebited || operation.Status == GiftOperationStatusDrawn + f.drawCompleted = operation.Status == GiftOperationStatusDrawn + return nil +} + +func (f *giftFlow) ensureGiftOperation(now time.Time) error { + if f.operationStarted { + return nil + } + commandPayload, err := command.Serialize(f.cmd) + if err != nil { + return err + } + request := proto.Clone(f.req).(*roomv1.SendGiftRequest) + // batch 首次校验可能裁掉已离房目标;恢复只能结算首次确认的实际目标,不能因之后 presence 变化重新扩张或缩小账单。 + request.TargetUserIds = append([]int64(nil), f.cmd.TargetUserIDs...) + request.TargetUserId = f.cmd.TargetUserID + request.TargetHostScopes = filterGiftTargetHostScopesProto(request.GetTargetHostScopes(), f.cmd.TargetUserIDs) + request.TargetDisplayProfiles = filterGiftDisplayProfilesProto(request.GetTargetDisplayProfiles(), f.cmd.TargetUserIDs) + requestPayload, err := proto.Marshal(request) + if err != nil { + return xerr.New(xerr.Internal, "send gift recovery request cannot be encoded") + } + operation, created, err := f.s.repository.EnsureGiftOperation(f.ctx, GiftOperation{ + AppCode: appcode.FromContext(f.ctx), + RoomID: f.cmd.RoomID(), + CommandID: f.cmd.ID(), + ActorUserID: f.cmd.ActorUserID(), + CommandPayload: commandPayload, + RequestPayload: requestPayload, + Status: GiftOperationStatusPending, + CreatedAtMS: now.UnixMilli(), + UpdatedAtMS: now.UnixMilli(), + }) + if err != nil { + return err + } + storedIDPayload, err := command.IdempotencyPayload(operation.CommandPayload) + if err != nil { + return err + } + currentIDPayload, err := command.IdempotencyPayload(commandPayload) + if err != nil { + return err + } + if !bytes.Equal(storedIDPayload, currentIDPayload) { + return xerr.New(xerr.Conflict, "command_id payload conflict") + } + if operation.Status == GiftOperationStatusCompensated { + // Reactivation is deliberately after the normalized payload comparison above. Reversing this + // order would let a conflicting request expose the old stored request to the background worker, + // which could turn an HTTP Conflict into an unrelated wallet debit. + operation, _, err = f.s.repository.ReactivateCompensatedGiftOperation(f.ctx, f.cmd.RoomID(), f.cmd.ID(), now.UnixMilli()) + if err != nil { + return err + } + if operation.Status == GiftOperationStatusCompensated { + return xerr.New(xerr.Unavailable, "gift operation reactivation did not advance") + } + } + if !created { + // loadGiftOperation executes before entering the Cell and can legitimately miss a row created by + // an overlapping request. Once Ensure returns that row, all financial calls must use its exact + // stored request/command snapshots. The idempotency comparison intentionally ignores gateway- + // resolved region/host/display fields, but wallet request_hash does not; continuing with this + // caller's refreshed snapshots would turn a valid retry into a ledger conflict. + return f.resumeGiftOperation(operation) + } + f.operation = operation + f.operationStarted = true + return nil +} + +func (f *giftFlow) markGiftOperationDebited(now time.Time) error { + f.debitCompleted = true + billingPayload, err := proto.Marshal(f.billing) + if err != nil { + return xerr.New(xerr.Internal, "gift billing result cannot be encoded") + } + if err := f.s.repository.SaveGiftOperationProgress(f.ctx, GiftOperationProgress{ + RoomID: f.cmd.RoomID(), + CommandID: f.cmd.ID(), + Status: GiftOperationStatusDebited, + BillingPayload: billingPayload, + UpdatedAtMS: now.UnixMilli(), + }); err != nil { + return err + } + f.operation.Status = GiftOperationStatusDebited + f.operation.BillingPayload = billingPayload + return nil +} + +func (f *giftFlow) markGiftOperationDrawn(now time.Time) error { + f.drawCompleted = true + luckyPayload, err := proto.Marshal(&roomv1.SendGiftResultV2{ + ApiVersion: 2, + CommandId: f.cmd.ID(), + LuckyGift: f.luckyGift, + LuckyGifts: append([]*roomv1.LuckyGiftDrawResult(nil), f.luckyGifts...), + }) + if err != nil { + return xerr.New(xerr.Internal, "gift lucky result cannot be encoded") + } + if err := f.s.repository.SaveGiftOperationProgress(f.ctx, GiftOperationProgress{ + RoomID: f.cmd.RoomID(), + CommandID: f.cmd.ID(), + Status: GiftOperationStatusDrawn, + LuckyPayload: luckyPayload, + UpdatedAtMS: now.UnixMilli(), + }); err != nil { + return err + } + f.operation.Status = GiftOperationStatusDrawn + f.operation.LuckyPayload = luckyPayload + return nil +} + +func (f *giftFlow) handleGiftOperationFailure(err error) { + if !f.operationStarted || err == nil { + return + } + ctx, cancel := context.WithTimeout(context.WithoutCancel(f.ctx), giftOperationPersistTimeout) + defer cancel() + now := f.s.clock.Now() + if !f.debitCompleted && !giftOperationErrorRetryable(err) && !isRoomOwnedByOtherNodeError(err) { + // wallet 明确拒绝(余额不足、配置非法等)保证没有扣款,pending 可以安全关闭;同载荷的用户主动重试会重新激活。 + // 跨节点执行权冲突不在此列:它只说明本节点不是 owner,首次尝试可能已经扣款,必须留给 owner 节点重试发现回执。 + if persistErr := f.s.repository.MarkGiftOperationCompensated(ctx, f.cmd.RoomID(), f.cmd.ID(), err.Error(), now.UnixMilli()); persistErr != nil { + slog.WarnContext(ctx, "mark gift operation compensated failed", "room_id", f.cmd.RoomID(), "command_id", f.cmd.ID(), "error", persistErr) + } + return + } + nextRetryAtMS := now.Add(giftOperationRetryDelay).UnixMilli() + preserveLease := giftOperationFailureMustKeepClaim(f.recovering, err) + if persistErr := f.s.repository.MarkGiftOperationRetry(ctx, f.cmd.RoomID(), f.cmd.ID(), err.Error(), nextRetryAtMS, now.UnixMilli(), preserveLease); persistErr != nil { + slog.WarnContext(ctx, "mark gift operation retry failed", "room_id", f.cmd.RoomID(), "command_id", f.cmd.ID(), "error", persistErr) + } +} + +func giftOperationFailureMustKeepClaim(recovering bool, err error) bool { + if !recovering || err == nil { + return false + } + if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { + return true + } + switch status.Code(err) { + case codes.DeadlineExceeded, codes.Canceled: + return true + default: + return false + } +} + +func giftOperationErrorRetryable(err error) bool { + code := xerr.CodeOf(err) + if _, ok := status.FromError(err); ok { + code = xerr.ReasonFromGRPC(err) + } + if code == xerr.Internal || code == xerr.Unavailable { + // unknown/timeout may have reached wallet; retrying the same command_id is the only safe way to discover the stable receipt. + return true + } + return xerr.SpecOf(code).Retryable +} + +func (s *Service) ensureNoRecoverableGiftOperations(ctx context.Context, roomID string) error { + open, err := s.repository.HasRecoverableGiftOperations(ctx, roomID) + if err != nil { + return err + } + if open { + // 关闭/删除会让恢复请求无法通过房间生命周期校验,并且硬删除还会抹掉 saga;Room Cell 串行保证 + // 本检查与 SendGift 创建 operation 不会交错,因此明确阻止生命周期终态直到 worker 完成结算。 + return xerr.New(xerr.Conflict, "room has recovering gift operations") + } + return nil +} + +func filterGiftTargetHostScopesProto(scopes []*roomv1.SendGiftTargetHostScope, targets []int64) []*roomv1.SendGiftTargetHostScope { + allowed := positiveInt64Set(targets) + out := make([]*roomv1.SendGiftTargetHostScope, 0, len(scopes)) + for _, scope := range scopes { + if scope != nil && allowed[scope.GetTargetUserId()] { + out = append(out, proto.Clone(scope).(*roomv1.SendGiftTargetHostScope)) + } + } + return out +} + +func filterGiftDisplayProfilesProto(profiles []*roomv1.SendGiftDisplayProfile, targets []int64) []*roomv1.SendGiftDisplayProfile { + allowed := positiveInt64Set(targets) + out := make([]*roomv1.SendGiftDisplayProfile, 0, len(profiles)) + for _, profile := range profiles { + if profile != nil && allowed[profile.GetUserId()] { + out = append(out, proto.Clone(profile).(*roomv1.SendGiftDisplayProfile)) + } + } + return out +} + +func positiveInt64Set(values []int64) map[int64]bool { + out := make(map[int64]bool, len(values)) + for _, value := range values { + if value > 0 { + out[value] = true + } + } + return out +} + +// RunGiftOperationRecoveryWorker 持续完成可能已经扣款的 saga;它只复用稳定 command_id 调 owner 接口,不自行改房间状态。 +func (s *Service) RunGiftOperationRecoveryWorker(ctx context.Context, interval time.Duration) { + if interval <= 0 { + interval = time.Second + } + workerID := fmt.Sprintf("%s-gift-recovery", strings.TrimSpace(s.nodeID)) + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + s.recoverGiftOperationsOnce(ctx, workerID) + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + } +} + +func (s *Service) recoverGiftOperationsOnce(ctx context.Context, workerID string) { + now := s.clock.Now() + operations, err := s.repository.ClaimRecoverableGiftOperations(ctx, workerID, 20, now.UnixMilli(), now.Add(giftOperationClaimTTL).UnixMilli()) + if err != nil { + slog.WarnContext(ctx, "claim gift operation recovery failed", "error", err) + return + } + // 被同一批 claim 的任务必须立即开始计时;若继续串行执行,队尾可能尚未进入 Room Cell 就超过租约, + // 被其他实例重复领取。不同房间天然可并行,同房间仍由 Room Cell 串行且等待受各自 deadline 限制。 + var wait sync.WaitGroup + for _, operation := range operations { + operation := operation + wait.Add(1) + go func() { + defer wait.Done() + s.recoverGiftOperation(ctx, operation) + }() + } + wait.Wait() +} + +func (s *Service) recoverGiftOperation(workerCtx context.Context, operation GiftOperation) { + baseCtx := appcode.WithContext(workerCtx, operation.AppCode) + operationCtx, cancel := context.WithTimeout(baseCtx, giftOperationAttemptTimeout) + defer cancel() + + now := s.clock.Now() + if ownerNodeID, ownedElsewhere, err := s.roomLeaseOwnedElsewhere(operationCtx, operation.AppCode, operation.RoomID); err == nil && ownedElsewhere { + // 非 owner 节点执行恢复只会在 ensureCell 得到跨节点 CONFLICT;释放 claim 让 owner 节点的 + // worker 在下一轮完成结算,避免恢复任务在错误节点空转。目录查询失败时按本地可执行处理, + // ensureCell 会再做权威判定。 + if persistErr := s.repository.MarkGiftOperationRetry(operationCtx, operation.RoomID, operation.CommandID, roomOwnedByOtherNodeMsgPrefix+ownerNodeID, now.Add(giftOperationRetryDelay).UnixMilli(), now.UnixMilli(), false); persistErr != nil { + slog.WarnContext(operationCtx, "defer gift operation to owner failed", "room_id", operation.RoomID, "command_id", operation.CommandID, "error", persistErr) + } + return + } + var req roomv1.SendGiftRequest + if err := proto.Unmarshal(operation.RequestPayload, &req); err != nil { + _ = s.repository.MarkGiftOperationRetry(operationCtx, operation.RoomID, operation.CommandID, "stored request decode failed", now.Add(time.Minute).UnixMilli(), now.UnixMilli(), false) + return + } + options := giftSendOptions{BatchDisplay: strings.EqualFold(strings.TrimSpace(req.GetDisplayMode()), "batch")} + if _, err := s.sendGiftWithRecovery(operationCtx, &req, options, operation); err != nil { + // sendGiftWithRecovery 用同一 command_id 记录 retry;deadline 后下一轮只发现/重放首次 wallet/lucky 结果,绝不新建账单。 + slog.WarnContext(operationCtx, "recover gift operation failed", "room_id", operation.RoomID, "command_id", operation.CommandID, "status", operation.Status, "error", err) + } +} diff --git a/services/room-service/internal/room/service/gift_operation_internal_test.go b/services/room-service/internal/room/service/gift_operation_internal_test.go new file mode 100644 index 00000000..b509d65b --- /dev/null +++ b/services/room-service/internal/room/service/gift_operation_internal_test.go @@ -0,0 +1,251 @@ +package service + +import ( + "context" + "errors" + "testing" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + roomv1 "hyapp.local/api/proto/room/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/room-service/internal/room/command" + "hyapp/services/room-service/internal/router" +) + +type giftOperationOwnershipRepository struct { + Repository + retries []giftOperationRetryCall + compensated []giftOperationRetryCall +} + +type giftOperationRetryCall struct { + roomID string + commandID string + lastError string + preserveLease bool +} + +func (r *giftOperationOwnershipRepository) MarkGiftOperationRetry(_ context.Context, roomID string, commandID string, lastError string, _ int64, _ int64, preserveLease bool) error { + r.retries = append(r.retries, giftOperationRetryCall{roomID: roomID, commandID: commandID, lastError: lastError, preserveLease: preserveLease}) + return nil +} + +func (r *giftOperationOwnershipRepository) MarkGiftOperationCompensated(_ context.Context, roomID string, commandID string, lastError string, _ int64) error { + r.compensated = append(r.compensated, giftOperationRetryCall{roomID: roomID, commandID: commandID, lastError: lastError}) + return nil +} + +type compensatedGiftOperationRepository struct { + Repository + operation GiftOperation + reactivateCalls int +} + +func (r *compensatedGiftOperationRepository) EnsureGiftOperation(_ context.Context, _ GiftOperation) (GiftOperation, bool, error) { + return r.operation, false, nil +} + +func (r *compensatedGiftOperationRepository) ReactivateCompensatedGiftOperation(_ context.Context, _, _ string, updatedAtMS int64) (GiftOperation, bool, error) { + r.reactivateCalls++ + if r.operation.Status == GiftOperationStatusCompensated { + r.operation.Status = GiftOperationStatusPending + r.operation.LastError = "" + r.operation.UpdatedAtMS = updatedAtMS + return r.operation, true, nil + } + return r.operation, false, nil +} + +func TestGiftOperationRecoveryAttemptFitsInsideClaimLease(t *testing.T) { + // mutateRoom 在钱包可能已经扣费后允许一个独立的持久提交窗口,失败处理也会脱离过期请求落 retry; + // claim 租约必须覆盖三段最坏边界,否则第二实例会在第一实例仍收尾时重复施压。 + maxBoundedWork := giftOperationAttemptTimeout + roomMutationCommitTimeout + giftOperationPersistTimeout + if maxBoundedWork >= giftOperationClaimTTL { + t.Fatalf("recovery bounded work %s must be shorter than claim lease %s", maxBoundedWork, giftOperationClaimTTL) + } +} + +func TestGiftOperationRecoveryDeadlineKeepsClaimUntilCellCommitBudgetEnds(t *testing.T) { + for _, err := range []error{ + context.DeadlineExceeded, + context.Canceled, + status.Error(codes.DeadlineExceeded, "wallet timed out"), + status.Error(codes.Canceled, "attempt canceled"), + } { + if !giftOperationFailureMustKeepClaim(true, err) { + t.Fatalf("recovery timeout must preserve its claim: %v", err) + } + } + if giftOperationFailureMustKeepClaim(false, context.DeadlineExceeded) { + t.Fatal("foreground request has no recovery claim to preserve") + } + if giftOperationFailureMustKeepClaim(true, errors.New("wallet unavailable")) { + t.Fatal("completed retryable failures may release the claim for the next attempt") + } +} + +func TestGiftOperationFailureOwnershipConflictRetriesInsteadOfCompensating(t *testing.T) { + ctx := appcode.WithContext(context.Background(), appcode.Default) + now := time.Date(2026, 7, 12, 9, 0, 0, 0, time.UTC) + req := &roomv1.SendGiftRequest{ + Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: "room-owner-conflict", CommandId: "cmd-owner-conflict", ActorUserId: 101}, + TargetType: "user", + TargetUserId: 202, + GiftId: "rose", + GiftCount: 1, + } + + repository := &giftOperationOwnershipRepository{} + flow := newGiftFlow(&Service{repository: repository, clock: fixedRoomTestClock{now: now}}, ctx, req, giftSendOptions{}) + flow.operationStarted = true + flow.recovering = true + flow.handleGiftOperationFailure(roomOwnedByOtherNodeError("room-node-b")) + if len(repository.compensated) != 0 { + t.Fatalf("cross-node ownership conflict must not compensate a pending saga: %+v", repository.compensated) + } + if len(repository.retries) != 1 || repository.retries[0].preserveLease { + t.Fatalf("ownership conflict should release the claim for the owner node to retry: %+v", repository.retries) + } + + repository = &giftOperationOwnershipRepository{} + flow = newGiftFlow(&Service{repository: repository, clock: fixedRoomTestClock{now: now}}, ctx, req, giftSendOptions{}) + flow.operationStarted = true + flow.handleGiftOperationFailure(xerr.New(xerr.Conflict, "command_id payload conflict")) + if len(repository.compensated) != 1 || len(repository.retries) != 0 { + t.Fatalf("business conflict before debit must stay compensated: compensated=%+v retries=%+v", repository.compensated, repository.retries) + } +} + +func TestRecoverGiftOperationDefersToOwnerNode(t *testing.T) { + ctx := context.Background() + now := time.Date(2026, 7, 12, 9, 0, 0, 0, time.UTC) + directory := router.NewMemoryDirectory() + repository := &giftOperationOwnershipRepository{} + svc := &Service{ + nodeID: "room-node-a", + leaseTTL: 10 * time.Second, + clock: fixedRoomTestClock{now: now}, + directory: directory, + repository: repository, + } + operation := GiftOperation{ + AppCode: appcode.Default, + RoomID: "room-recovery-owner", + CommandID: "cmd-recovery-owner", + Status: GiftOperationStatusPending, + } + if _, err := directory.EnsureOwner(ctx, runtimeRoomKey(operation.AppCode, operation.RoomID), "room-node-b", now, 10*time.Second); err != nil { + t.Fatalf("seed other-node lease: %v", err) + } + + // RequestPayload 故意留空:非 owner 分支必须在解码存量请求、进入 Room Cell 之前把任务让给 owner。 + svc.recoverGiftOperation(ctx, operation) + if len(repository.retries) != 1 { + t.Fatalf("non-owner recovery must defer exactly once, got %+v", repository.retries) + } + if repository.retries[0].preserveLease || repository.retries[0].lastError != roomOwnedByOtherNodeMsgPrefix+"room-node-b" { + t.Fatalf("defer must release the claim and record the current owner: %+v", repository.retries[0]) + } + if len(repository.compensated) != 0 { + t.Fatalf("defer must not compensate the operation: %+v", repository.compensated) + } +} + +func TestIsRoomOwnedByOtherNodeError(t *testing.T) { + if !isRoomOwnedByOtherNodeError(roomOwnedByOtherNodeError("room-node-b")) { + t.Fatal("ownership conflict error must be recognized") + } + for _, err := range []error{ + xerr.New(xerr.Conflict, "command_id payload conflict"), + xerr.New(xerr.Unavailable, roomOwnedByOtherNodeMsgPrefix+"room-node-b"), + errors.New(roomOwnedByOtherNodeMsgPrefix + "room-node-b"), + } { + if isRoomOwnedByOtherNodeError(err) { + t.Fatalf("must not treat %v as cross-node ownership conflict", err) + } + } +} + +func TestCompensatedGiftOperationReactivatesOnlyAfterPayloadMatch(t *testing.T) { + ctx := appcode.WithContext(context.Background(), appcode.Default) + originalRequest := &roomv1.SendGiftRequest{ + Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: "room-reactivate", CommandId: "cmd-reactivate", ActorUserId: 101}, + TargetType: "user", TargetUserId: 202, GiftId: "rose", GiftCount: 1, SenderRegionId: 10, + TargetIsHost: true, TargetHostRegionId: 20, TargetAgencyOwnerUserId: 303, + SenderDisplayProfile: &roomv1.SendGiftDisplayProfile{UserId: 101, Username: "first sender"}, + } + // Build the stored command through the same normalizer used by production so this test exercises + // the semantic idempotency comparison instead of depending on hand-written JSON field defaults. + originalFlow := newGiftFlow(&Service{}, ctx, originalRequest, giftSendOptions{}) + storedPayload, err := command.Serialize(originalFlow.cmd) + if err != nil { + t.Fatalf("serialize stored gift command: %v", err) + } + storedRequestPayload, err := proto.Marshal(originalRequest) + if err != nil { + t.Fatalf("serialize stored gift request: %v", err) + } + + t.Run("conflicting payload stays compensated", func(t *testing.T) { + repository := &compensatedGiftOperationRepository{operation: GiftOperation{ + AppCode: appcode.Default, RoomID: "room-reactivate", CommandID: "cmd-reactivate", + CommandPayload: storedPayload, RequestPayload: storedRequestPayload, + Status: GiftOperationStatusCompensated, LastError: "insufficient balance", + }} + service := &Service{repository: repository} + conflictingRequest := &roomv1.SendGiftRequest{ + Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: "room-reactivate", CommandId: "cmd-reactivate", ActorUserId: 101}, + TargetType: "user", TargetUserId: 202, GiftId: "different-gift", GiftCount: 1, + } + flow := newGiftFlow(service, ctx, conflictingRequest, giftSendOptions{}) + if err := flow.ensureGiftOperation(time.UnixMilli(2_000)); !xerr.IsCode(err, xerr.Conflict) { + t.Fatalf("conflicting command must fail before reactivation: %v", err) + } + if repository.reactivateCalls != 0 || repository.operation.Status != GiftOperationStatusCompensated { + t.Fatalf("conflict exposed stored work to recovery: calls=%d operation=%+v", repository.reactivateCalls, repository.operation) + } + }) + + t.Run("identical payload CAS reactivates", func(t *testing.T) { + repository := &compensatedGiftOperationRepository{operation: GiftOperation{ + AppCode: appcode.Default, RoomID: "room-reactivate", CommandID: "cmd-reactivate", + CommandPayload: storedPayload, RequestPayload: storedRequestPayload, + Status: GiftOperationStatusCompensated, LastError: "insufficient balance", + }} + service := &Service{repository: repository} + flow := newGiftFlow(service, ctx, originalRequest, giftSendOptions{}) + if err := flow.ensureGiftOperation(time.UnixMilli(2_000)); err != nil { + t.Fatalf("identical command should reactivate: %v", err) + } + if repository.reactivateCalls != 1 || repository.operation.Status != GiftOperationStatusPending || !flow.operationStarted { + t.Fatalf("identical command did not enter pending exactly once: calls=%d operation=%+v", repository.reactivateCalls, repository.operation) + } + }) + + t.Run("load miss duplicate resumes first gateway snapshots", func(t *testing.T) { + repository := &compensatedGiftOperationRepository{operation: GiftOperation{ + AppCode: appcode.Default, RoomID: "room-reactivate", CommandID: "cmd-reactivate", + CommandPayload: storedPayload, RequestPayload: storedRequestPayload, Status: GiftOperationStatusPending, + }} + service := &Service{repository: repository} + refreshedRequest := proto.Clone(originalRequest).(*roomv1.SendGiftRequest) + refreshedRequest.SenderRegionId = 99 + refreshedRequest.TargetHostRegionId = 88 + refreshedRequest.TargetAgencyOwnerUserId = 404 + refreshedRequest.SenderDisplayProfile = &roomv1.SendGiftDisplayProfile{UserId: 101, Username: "renamed sender"} + flow := newGiftFlow(service, ctx, refreshedRequest, giftSendOptions{}) + if err := flow.ensureGiftOperation(time.UnixMilli(2_000)); err != nil { + t.Fatalf("idempotency-equivalent duplicate should resume stored operation: %v", err) + } + if !flow.recovering || flow.cmd.SenderRegionID != 10 || flow.req.GetSenderRegionId() != 10 || flow.cmd.TargetHostRegionID != 20 || flow.req.GetTargetHostRegionId() != 20 || giftDisplayName(flow.cmd.SenderDisplayProfile) != "first sender" { + t.Fatalf("duplicate continued with refreshed gateway snapshots: command=%+v request=%+v", flow.cmd, flow.req) + } + if repository.reactivateCalls != 0 { + t.Fatalf("already-pending duplicate must not reactivate: %d", repository.reactivateCalls) + } + }) +} diff --git a/services/room-service/internal/room/service/gift_result_v2.go b/services/room-service/internal/room/service/gift_result_v2.go new file mode 100644 index 00000000..f2983d7c --- /dev/null +++ b/services/room-service/internal/room/service/gift_result_v2.go @@ -0,0 +1,83 @@ +package service + +import ( + "time" + + "google.golang.org/protobuf/proto" + roomv1 "hyapp.local/api/proto/room/v1" + "hyapp/pkg/xerr" + "hyapp/services/room-service/internal/room/command" +) + +func marshalMutationResult(result mutationResult) ([]byte, error) { + if result.giftV2 == nil { + return nil, nil + } + payload, err := proto.Marshal(result.giftV2) + if err != nil { + return nil, xerr.New(xerr.Internal, "send gift v2 result cannot be encoded") + } + return payload, nil +} + +func replayMutationResult(record CommandRecord, snapshot *roomv1.RoomSnapshot) (mutationResult, error) { + result := mutationResult{snapshot: snapshot} + if record.CommandType != (command.SendGift{}).Type() || len(record.ResultPayload) == 0 { + // 历史命令没有 typed result;继续返回当前 snapshot,保持 V1 已上线行为,不伪造账务字段。 + return result, nil + } + var stored roomv1.SendGiftResultV2 + if err := proto.Unmarshal(record.ResultPayload, &stored); err != nil { + // 已提交结果损坏时必须 fail-close,不能重跑 wallet 或幸运礼物来“修复”一个已提交命令。 + return mutationResult{}, xerr.New(xerr.Internal, "stored send gift result cannot be decoded") + } + replayed := proto.Clone(&stored).(*roomv1.SendGiftResultV2) + replayed.IdempotentReplay = true + replayed.OperationStatus = GiftOperationStatusCommitted + result.billingReceiptID = replayed.GetBillingReceiptId() + result.coinBalanceAfter = replayed.GetCoinBalanceAfter() + result.giftIncomeBalanceAfter = replayed.GetGiftIncomeBalanceAfter() + result.roomHeat = replayed.GetRoomHeat() + result.giftRank = cloneGiftRankItems(replayed.GetGiftRank()) + result.luckyGift = replayed.GetLuckyGift() + result.luckyGifts = append([]*roomv1.LuckyGiftDrawResult(nil), replayed.GetLuckyGifts()...) + result.batchDisplay = replayed.GetBatchDisplay() + result.giftV2 = replayed + return result, nil +} + +func cloneGiftRankItems(items []*roomv1.RankItem) []*roomv1.RankItem { + out := make([]*roomv1.RankItem, 0, len(items)) + for _, item := range items { + if item != nil { + out = append(out, proto.Clone(item).(*roomv1.RankItem)) + } + } + return out +} + +func luckyHitsFromResults(results []*roomv1.LuckyGiftDrawResult) []*roomv1.SendGiftLuckyHit { + hits := make([]*roomv1.SendGiftLuckyHit, 0) + for _, result := range results { + if result == nil { + continue + } + for _, hit := range result.GetHits() { + if hit != nil && hit.GetRewardCoins() > 0 { + hits = append(hits, proto.Clone(hit).(*roomv1.SendGiftLuckyHit)) + } + } + } + return hits +} + +func giftOperationCommitForResult(cmd command.Command, resultPayload []byte, now time.Time) *GiftOperationCommit { + if cmd == nil || cmd.Type() != (command.SendGift{}).Type() || len(resultPayload) == 0 { + return nil + } + return &GiftOperationCommit{ + CommandID: cmd.ID(), + ResultPayload: resultPayload, + UpdatedAtMS: now.UnixMilli(), + } +} diff --git a/services/room-service/internal/room/service/gift_v2_test.go b/services/room-service/internal/room/service/gift_v2_test.go new file mode 100644 index 00000000..46ca0025 --- /dev/null +++ b/services/room-service/internal/room/service/gift_v2_test.go @@ -0,0 +1,247 @@ +package service_test + +import ( + "context" + "sync/atomic" + "testing" + "time" + + luckygiftv1 "hyapp.local/api/proto/luckygift/v1" + roomv1 "hyapp.local/api/proto/room/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/room-service/internal/integration" + roomservice "hyapp/services/room-service/internal/room/service" + "hyapp/services/room-service/internal/router" + "hyapp/services/room-service/internal/testutil/mysqltest" +) + +type concurrentV2Wallet struct { + followTestWallet + calls atomic.Int32 + started chan struct{} + release chan struct{} +} + +func (w *concurrentV2Wallet) DebitGift(ctx context.Context, _ *walletv1.DebitGiftRequest) (*walletv1.DebitGiftResponse, error) { + if w.calls.Add(1) == 1 && w.started != nil { + close(w.started) + } + if w.release != nil { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-w.release: + } + } + return &walletv1.DebitGiftResponse{ + BillingReceiptId: "receipt-v2-stable", + TransactionId: "wallet-tx-v2-stable", + CoinSpent: 100, + ChargeAmount: 100, + HeatValue: 100, + BalanceAfter: 900, + BalanceVersion: 17, + GiftIncomeBalanceAfter: 25, + GiftTypeCode: "normal", + }, nil +} + +func TestSendGiftConcurrentCommandReplaysOriginalV2AndKeepsV1Fields(t *testing.T) { + ctx := appcode.WithContext(context.Background(), appcode.Default) + repository := mysqltest.NewRepository(t) + wallet := &concurrentV2Wallet{started: make(chan struct{}), release: make(chan struct{})} + svc := roomservice.New(roomservice.Config{ + NodeID: "node-gift-v2-race", + LeaseTTL: 10 * time.Second, + RankLimit: 20, + SnapshotEveryN: 1, + }, router.NewMemoryDirectory(), repository, wallet, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher()) + + roomID := "room-gift-v2-race" + senderID, targetID := int64(101), int64(202) + createRocketRoom(t, ctx, svc, roomID, senderID, 9001) + joinRocketRoom(t, ctx, svc, roomID, targetID) + req := &roomv1.SendGiftRequest{ + Meta: &roomv1.RequestMeta{RequestId: "req-v2-race", CommandId: "combo-v2-race:1", ActorUserId: senderID, RoomId: roomID, AppCode: appcode.Default}, + TargetType: "user", TargetUserId: targetID, GiftId: "rose", GiftCount: 5, + ComboSessionId: "combo-v2-race", BatchSeq: 1, + } + + responses := make(chan *roomv1.SendGiftResponse, 2) + errs := make(chan error, 2) + go func() { + resp, err := svc.SendGift(ctx, req) + responses <- resp + errs <- err + }() + <-wallet.started + go func() { + resp, err := svc.SendGift(ctx, req) + responses <- resp + errs <- err + }() + close(wallet.release) + + var got []*roomv1.SendGiftResponse + for range 2 { + if err := <-errs; err != nil { + t.Fatalf("concurrent SendGift failed: %v", err) + } + got = append(got, <-responses) + } + if calls := wallet.calls.Load(); calls != 1 { + t.Fatalf("same command_id must debit once, got %d wallet calls", calls) + } + replays := 0 + for _, resp := range got { + if resp.GetV2() == nil { + t.Fatalf("V2 result is missing: %+v", resp) + } + if resp.GetV2().GetIdempotentReplay() { + replays++ + } + if resp.GetV2().GetBillingReceiptId() != "receipt-v2-stable" || resp.GetV2().GetCoinBalanceAfter() != 900 || resp.GetV2().GetBalanceVersion() != 17 || resp.GetV2().GetSettledGiftCount() != 5 || resp.GetV2().GetCommittedRoomVersion() <= 0 { + t.Fatalf("V2 stable result mismatch: %+v", resp.GetV2()) + } + // 旧 Flutter 仍读取顶层字段;首次响应和幂等重放都必须回填相同回执、余额和热度。 + if resp.GetBillingReceiptId() != resp.GetV2().GetBillingReceiptId() || resp.GetCoinBalanceAfter() != resp.GetV2().GetCoinBalanceAfter() || resp.GetRoomHeat() != resp.GetV2().GetRoomHeat() { + t.Fatalf("V1 compatibility fields drifted: response=%+v v2=%+v", resp, resp.GetV2()) + } + } + if replays != 1 { + t.Fatalf("exactly one overlapping request should be replayed, got %d", replays) + } + if heat := got[0].GetRoom().GetHeat(); heat != 100 { + t.Fatalf("same command_id changed room heat more than once: %d", heat) + } +} + +type recoverableLuckyClient struct { + calls atomic.Int32 +} + +func (*recoverableLuckyClient) CheckLuckyGift(context.Context, *luckygiftv1.CheckLuckyGiftRequest) (*luckygiftv1.CheckLuckyGiftResponse, error) { + return &luckygiftv1.CheckLuckyGiftResponse{Enabled: true, RuleVersion: 1, PoolId: "lucky-pool"}, nil +} + +func (c *recoverableLuckyClient) ExecuteLuckyGiftDraw(_ context.Context, req *luckygiftv1.ExecuteLuckyGiftDrawRequest) (*luckygiftv1.ExecuteLuckyGiftDrawResponse, error) { + if c.calls.Add(1) == 1 { + return nil, xerr.New(xerr.Unavailable, "temporary lucky outage") + } + meta := req.GetLuckyGift() + return &luckygiftv1.ExecuteLuckyGiftDrawResponse{Result: &luckygiftv1.LuckyGiftDrawResult{ + DrawId: "draw-recovered", CommandId: meta.GetCommandId(), GiftId: meta.GetGiftId(), PoolId: meta.GetPoolId(), + MultiplierPpm: 2_000_000, EffectiveRewardCoins: 200, RewardStatus: "granted", CoinBalanceAfter: 1100, + Hits: []*luckygiftv1.LuckyGiftHit{{GiftIndex: 1, DrawId: "draw-recovered", SelectedTierId: "2x", MultiplierPpm: 2_000_000, RewardCoins: 200}}, + }}, nil +} + +func (c *recoverableLuckyClient) BatchExecuteLuckyGiftDraw(ctx context.Context, req *luckygiftv1.BatchExecuteLuckyGiftDrawRequest) (*luckygiftv1.BatchExecuteLuckyGiftDrawResponse, error) { + results := make([]*luckygiftv1.LuckyGiftDrawResult, 0, len(req.GetLuckyGifts())) + for _, meta := range req.GetLuckyGifts() { + resp, err := c.ExecuteLuckyGiftDraw(ctx, &luckygiftv1.ExecuteLuckyGiftDrawRequest{LuckyGift: meta}) + if err != nil { + return nil, err + } + results = append(results, resp.GetResult()) + } + return &luckygiftv1.BatchExecuteLuckyGiftDrawResponse{Results: results}, nil +} + +func TestGiftOperationWorkerFinishesDebitedRequestWithoutClientRetry(t *testing.T) { + ctx := appcode.WithContext(context.Background(), appcode.Default) + repository := mysqltest.NewRepository(t) + wallet := &concurrentV2Wallet{} + lucky := &recoverableLuckyClient{} + svc := roomservice.New(roomservice.Config{ + NodeID: "node-gift-v2-recovery", + LeaseTTL: 10 * time.Second, + RankLimit: 20, + SnapshotEveryN: 1, + }, router.NewMemoryDirectory(), repository, wallet, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher(), lucky) + + roomID := "room-gift-v2-recovery" + senderID, targetID := int64(301), int64(302) + createRocketRoom(t, ctx, svc, roomID, senderID, 9001) + joinRocketRoom(t, ctx, svc, roomID, targetID) + req := &roomv1.SendGiftRequest{ + Meta: &roomv1.RequestMeta{RequestId: "req-v2-recovery", CommandId: "cmd-v2-recovery", ActorUserId: senderID, RoomId: roomID, AppCode: appcode.Default}, + TargetType: "user", TargetUserId: targetID, GiftId: "lucky-rose", GiftCount: 1, PoolId: "lucky-pool", + } + if _, err := svc.SendGift(ctx, req); !xerr.IsCode(err, xerr.Unavailable) { + t.Fatalf("first draw should fail after debit, got %v", err) + } + operation, exists, err := repository.GetGiftOperation(ctx, roomID, req.GetMeta().GetCommandId()) + if err != nil || !exists || operation.Status != roomservice.GiftOperationStatusDebited { + t.Fatalf("debited saga was not persisted: exists=%v operation=%+v err=%v", exists, operation, err) + } + if _, err := svc.CloseRoom(ctx, &roomv1.CloseRoomRequest{ + Meta: &roomv1.RequestMeta{CommandId: "close-during-gift-recovery", ActorUserId: senderID, RoomId: roomID, AppCode: appcode.Default}, + }); !xerr.IsCode(err, xerr.Conflict) { + t.Fatalf("room close must wait for debited gift recovery, got %v", err) + } + + workerCtx, cancel := context.WithCancel(ctx) + defer cancel() + go svc.RunGiftOperationRecoveryWorker(workerCtx, 10*time.Millisecond) + deadline := time.Now().Add(3 * time.Second) + for { + record, committed, err := repository.GetCommand(ctx, roomID, req.GetMeta().GetCommandId()) + if err != nil { + t.Fatalf("read recovered command: %v", err) + } + if committed { + if len(record.ResultPayload) == 0 { + t.Fatal("recovered command did not persist V2 result") + } + break + } + if time.Now().After(deadline) { + t.Fatal("server recovery worker did not commit debited gift") + } + time.Sleep(20 * time.Millisecond) + } + + replayed, err := svc.SendGift(ctx, req) + if err != nil { + t.Fatalf("replay recovered gift failed: %v", err) + } + // HTTP 送礼回执只返回 wallet debit 的稳定账后余额;幸运返奖由异步钱包/IM 事实随后推进, + // 不能把 lucky-service 的临时 coin_balance_after 冒充同一 sender 版本的扣费余额。 + if !replayed.GetV2().GetIdempotentReplay() || replayed.GetLuckyGift().GetDrawId() != "draw-recovered" || replayed.GetCoinBalanceAfter() != 900 || len(replayed.GetV2().GetLuckyHits()) != 1 || replayed.GetV2().GetLuckyHits()[0].GetGiftIndex() != 1 { + t.Fatalf("recovered original result was not replayed: %+v", replayed) + } + if _, err := svc.CloseRoom(ctx, &roomv1.CloseRoomRequest{ + Meta: &roomv1.RequestMeta{CommandId: "close-after-gift-recovery", ActorUserId: senderID, RoomId: roomID, AppCode: appcode.Default}, + }); err != nil { + t.Fatalf("room close should proceed after gift saga commits: %v", err) + } +} + +func TestSendGiftOwnerRejectsExcessiveUnitsBeforeWallet(t *testing.T) { + ctx := appcode.WithContext(context.Background(), appcode.Default) + repository := mysqltest.NewRepository(t) + wallet := &concurrentV2Wallet{} + svc := roomservice.New(roomservice.Config{NodeID: "node-gift-v2-limit", LeaseTTL: 10 * time.Second, SnapshotEveryN: 1}, router.NewMemoryDirectory(), repository, wallet, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher()) + roomID := "room-gift-v2-limit" + createRocketRoom(t, ctx, svc, roomID, 401, 9001) + targets := []int64{402, 403, 404, 405, 406, 407} + for _, targetID := range targets { + joinRocketRoom(t, ctx, svc, roomID, targetID) + } + _, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{ + Meta: &roomv1.RequestMeta{CommandId: "cmd-v2-limit", ActorUserId: 401, RoomId: roomID, AppCode: appcode.Default}, + TargetType: "user", TargetUserIds: targets, GiftId: "rose", GiftCount: 999, + }) + if !xerr.IsCode(err, xerr.InvalidArgument) { + t.Fatalf("owner total unit limit should reject request, got %v", err) + } + if wallet.calls.Load() != 0 { + t.Fatalf("rejected request reached wallet %d times", wallet.calls.Load()) + } +} + +var _ integration.WalletClient = (*concurrentV2Wallet)(nil) +var _ integration.LuckyGiftClient = (*recoverableLuckyClient)(nil) diff --git a/services/room-service/internal/room/service/helpers.go b/services/room-service/internal/room/service/helpers.go index 67e31e0e..3d4a6e33 100644 --- a/services/room-service/internal/room/service/helpers.go +++ b/services/room-service/internal/room/service/helpers.go @@ -130,6 +130,22 @@ func runtimeRoomKeyFromContext(ctx context.Context, roomID string) string { return runtimeRoomKey(appcode.FromContext(ctx), roomID) } +// roomLeaseOwnedElsewhere 判断房间执行权当前是否由其他节点持有有效 lease;只读判定,不接管也不续租。 +func (s *Service) roomLeaseOwnedElsewhere(ctx context.Context, appCode string, roomID string) (string, bool, error) { + if s.directory == nil { + // 测试中的极简 Service 可能没有装配 directory;生产 HealthCheck 会拒绝这种配置。 + return "", false, nil + } + lease, exists, err := s.directory.Lookup(ctx, runtimeRoomKey(appCode, roomID)) + if err != nil { + return "", false, err + } + if exists && lease.ValidAt(s.clock.Now()) && lease.NodeID != s.nodeID { + return lease.NodeID, true, nil + } + return "", false, nil +} + func (s *Service) loadedRoomLease(ctx context.Context, roomRef loadedRoomRef, now time.Time) (router.Lease, bool, error) { // 后台 worker 通过 EnsureOwner 续租当前 Cell,不能在没有 lease 的情况下读取旧内存态。 lease, err := s.directory.EnsureOwner(ctx, runtimeRoomKey(roomRef.AppCode, roomRef.RoomID), s.nodeID, now, s.leaseTTL) diff --git a/services/room-service/internal/room/service/lifecycle.go b/services/room-service/internal/room/service/lifecycle.go index e482c7a2..7b2a3ed3 100644 --- a/services/room-service/internal/room/service/lifecycle.go +++ b/services/room-service/internal/room/service/lifecycle.go @@ -90,6 +90,9 @@ func (s *Service) CloseRoom(ctx context.Context, req *roomv1.CloseRoomRequest) ( return mutationResult{}, nil, err } } + if err := s.ensureNoRecoverableGiftOperations(ctx, current.RoomID); err != nil { + return mutationResult{}, nil, err + } kickTargets := closeRoomKickTargets(current) if len(kickTargets) > 0 { diff --git a/services/room-service/internal/room/service/list.go b/services/room-service/internal/room/service/list.go index 328749d1..57239330 100644 --- a/services/room-service/internal/room/service/list.go +++ b/services/room-service/internal/room/service/list.go @@ -40,6 +40,7 @@ type roomListCursor struct { CountryCode string `json:"country_code,omitempty"` ViewerCountry string `json:"viewer_country,omitempty"` AllRegions bool `json:"all_regions,omitempty"` + FilterRegion int64 `json:"filter_region_id,omitempty"` SortScore int64 `json:"sort_score,omitempty"` CreatedAtMS int64 `json:"created_at_ms,omitempty"` UpdatedAtMS int64 `json:"updated_at_ms,omitempty"` @@ -73,14 +74,25 @@ func (s *Service) ListRooms(ctx context.Context, req *roomv1.ListRoomsRequest) ( countryCode := normalizeRoomCountryCode(req.GetCountryCode()) viewerCountryCode := normalizeRoomCountryCode(req.GetViewerCountryCode()) allVisibleRegions := req.GetAllVisibleRegions() - cursor, err := decodeRoomListCursor(tab, query, countryCode, viewerCountryCode, allVisibleRegions, req.GetCursor()) + filterRegionID := req.GetFilterRegionId() + if filterRegionID < 0 || (filterRegionID > 0 && (allVisibleRegions || countryCode != "")) { + // 区域、国家和 All 是三种互斥的公共列表范围;拒绝混合语义避免游标跨范围漏数。 + return nil, xerr.New(xerr.InvalidArgument, "room list filter is invalid") + } + cursor, err := decodeRoomListCursor(tab, query, countryCode, viewerCountryCode, allVisibleRegions, filterRegionID, req.GetCursor()) if err != nil { return nil, err } + effectiveRegionID := normalizeVisibleRegionID(req.GetVisibleRegionId()) + if filterRegionID > 0 { + // 缓存和 MySQL 读模型共用 VisibleRegionID 作为实际查询桶;游标仍单独保存 FilterRegionID 区分客户筛选。 + effectiveRegionID = filterRegionID + } entries, err := s.listRoomListEntries(ctx, RoomListQuery{ AppCode: appcode.FromContext(ctx), - VisibleRegionID: normalizeVisibleRegionID(req.GetVisibleRegionId()), + VisibleRegionID: effectiveRegionID, + FilterRegionID: filterRegionID, AllVisibleRegions: allVisibleRegions, Tab: tab, Query: query, @@ -100,7 +112,7 @@ func (s *Service) ListRooms(ctx context.Context, req *roomv1.ListRoomsRequest) ( return nil, err } - return roomListResponseFromEntries(tab, query, countryCode, viewerCountryCode, allVisibleRegions, entries, limit), nil + return roomListResponseFromEntries(tab, query, countryCode, viewerCountryCode, allVisibleRegions, filterRegionID, entries, limit), nil } func (s *Service) ListRoomsByOwners(ctx context.Context, req *roomv1.ListRoomsByOwnersRequest) (*roomv1.ListRoomsResponse, error) { @@ -115,14 +127,14 @@ func (s *Service) ListRoomsByOwners(ctx context.Context, req *roomv1.ListRoomsBy } ownerUserIDs := normalizeRoomOwnerFilters(req.GetOwnerUserIds()) if len(ownerUserIDs) == 0 { - return roomListResponseFromEntries(tab, "", "", "", false, nil, normalizeRoomListLimit(req.GetLimit())), nil + return roomListResponseFromEntries(tab, "", "", "", false, 0, nil, normalizeRoomListLimit(req.GetLimit())), nil } limit := normalizeRoomListLimit(req.GetLimit()) query, err := normalizeRoomListQuery(req.GetQuery()) if err != nil { return nil, err } - cursor, err := decodeRoomListCursor(tab, query, "", "", false, req.GetCursor()) + cursor, err := decodeRoomListCursor(tab, query, "", "", false, 0, req.GetCursor()) if err != nil { return nil, err } @@ -139,7 +151,7 @@ func (s *Service) ListRoomsByOwners(ctx context.Context, req *roomv1.ListRoomsBy if err != nil { return nil, err } - return roomListResponseFromEntries(tab, query, "", "", false, entries, limit), nil + return roomListResponseFromEntries(tab, query, "", "", false, 0, entries, limit), nil } func (s *Service) listRoomListEntries(ctx context.Context, query RoomListQuery) ([]RoomListEntry, error) { @@ -157,8 +169,8 @@ func (s *Service) listRoomListEntries(ctx context.Context, query RoomListQuery) } func roomListCacheEligible(query RoomListQuery) bool { - // 搜索、全区白名单和内部定向查询仍走 MySQL,避免为了低频条件维护过宽的 Redis 索引。 - if strings.TrimSpace(query.Query) != "" || query.AllVisibleRegions || query.OwnerUserID > 0 || len(query.OwnerUserIDs) > 0 { + // 搜索和内部定向查询仍走 MySQL;All/region/country 则都有可重建的 Redis 桶,可以承载首页高频读。 + if strings.TrimSpace(query.Query) != "" || query.OwnerUserID > 0 || len(query.OwnerUserIDs) > 0 { return false } return query.Tab == roomListTabHot || query.Tab == roomListTabNew @@ -183,7 +195,7 @@ func (s *Service) ListRoomFeeds(ctx context.Context, req *roomv1.ListRoomFeedsRe if err != nil { return nil, err } - cursor, err := decodeRoomListCursor(tab, query, "", "", false, req.GetCursor()) + cursor, err := decodeRoomListCursor(tab, query, "", "", false, 0, req.GetCursor()) if err != nil { return nil, err } @@ -231,7 +243,7 @@ func (s *Service) ListRoomFeeds(ctx context.Context, req *roomv1.ListRoomFeedsRe return nil, err } - return roomListResponseFromEntries(tab, query, "", "", false, entries, limit), nil + return roomListResponseFromEntries(tab, query, "", "", false, 0, entries, limit), nil } // GetMyRoom 查询当前用户自己创建的房间,不依赖 room_list_entries 投影命中。 @@ -264,12 +276,12 @@ func (s *Service) GetMyRoom(ctx context.Context, req *roomv1.GetMyRoomRequest) ( }, nil } -func roomListResponseFromEntries(tab string, query string, countryCode string, viewerCountryCode string, allRegions bool, entries []RoomListEntry, limit int) *roomv1.ListRoomsResponse { +func roomListResponseFromEntries(tab string, query string, countryCode string, viewerCountryCode string, allRegions bool, filterRegionID int64, entries []RoomListEntry, limit int) *roomv1.ListRoomsResponse { nextCursor := "" if len(entries) > limit { // 多查一条用于判断是否存在下一页;返回数据只包含请求的 limit 条。 last := entries[limit-1] - nextCursor = encodeRoomListCursor(tab, query, countryCode, viewerCountryCode, allRegions, last) + nextCursor = encodeRoomListCursor(tab, query, countryCode, viewerCountryCode, allRegions, filterRegionID, last) entries = entries[:limit] } @@ -521,10 +533,10 @@ func normalizeRoomCountryCode(countryCode string) string { } // decodeRoomListCursor 校验不透明 cursor 和当前 tab 一致,禁止客户端跨排序维度复用游标。 -func decodeRoomListCursor(tab string, query string, countryCode string, viewerCountryCode string, allRegions bool, encoded string) (roomListCursor, error) { +func decodeRoomListCursor(tab string, query string, countryCode string, viewerCountryCode string, allRegions bool, filterRegionID int64, encoded string) (roomListCursor, error) { encoded = strings.TrimSpace(encoded) if encoded == "" { - return roomListCursor{Tab: tab, Query: query, CountryCode: countryCode, ViewerCountry: viewerCountryCode, AllRegions: allRegions}, nil + return roomListCursor{Tab: tab, Query: query, CountryCode: countryCode, ViewerCountry: viewerCountryCode, AllRegions: allRegions, FilterRegion: filterRegionID}, nil } payload, err := base64.RawURLEncoding.DecodeString(encoded) @@ -536,7 +548,7 @@ func decodeRoomListCursor(tab string, query string, countryCode string, viewerCo if err := json.Unmarshal(payload, &cursor); err != nil { return roomListCursor{}, xerr.New(xerr.InvalidArgument, "cursor is invalid") } - if cursor.Tab != tab || cursor.Query != query || cursor.CountryCode != countryCode || cursor.ViewerCountry != viewerCountryCode || cursor.AllRegions != allRegions || cursor.RoomID == "" { + if cursor.Tab != tab || cursor.Query != query || cursor.CountryCode != countryCode || cursor.ViewerCountry != viewerCountryCode || cursor.AllRegions != allRegions || cursor.FilterRegion != filterRegionID || cursor.RoomID == "" { return roomListCursor{}, xerr.New(xerr.InvalidArgument, "cursor is invalid") } @@ -545,13 +557,14 @@ func decodeRoomListCursor(tab string, query string, countryCode string, viewerCo // encodeRoomListCursor 把最后一条列表记录编码成下一页边界。 // cursor 不暴露契约语义,后续调整排序字段时可以只兼容服务端解析逻辑。 -func encodeRoomListCursor(tab string, query string, countryCode string, viewerCountryCode string, allRegions bool, entry RoomListEntry) string { +func encodeRoomListCursor(tab string, query string, countryCode string, viewerCountryCode string, allRegions bool, filterRegionID int64, entry RoomListEntry) string { cursor := roomListCursor{ Tab: tab, Query: query, CountryCode: countryCode, ViewerCountry: viewerCountryCode, AllRegions: allRegions, + FilterRegion: filterRegionID, SortScore: entry.SortScore, CreatedAtMS: entry.CreatedAtMS, UpdatedAtMS: entry.UpdatedAtMS, diff --git a/services/room-service/internal/room/service/lucky_gift_display.go b/services/room-service/internal/room/service/lucky_gift_display.go new file mode 100644 index 00000000..1f967fc5 --- /dev/null +++ b/services/room-service/internal/room/service/lucky_gift_display.go @@ -0,0 +1,113 @@ +package service + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "google.golang.org/protobuf/proto" + luckygifteventsv1 "hyapp.local/api/proto/events/luckygift/v1" + roomeventsv1 "hyapp.local/api/proto/events/room/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/luckygiftmq" + "hyapp/services/room-service/internal/room/outbox" +) + +const roomLuckyGiftDrawnEventType = "RoomLuckyGiftDrawn" + +// HandleLuckyGiftOutboxEvent 把 lucky-gift-service 已完成返奖的 owner 事实持久化为房间展示 outbox。 +// 中奖展示不改变房间状态,因此不能伪造 Room Cell 命令、推进 room_version 或写 command log; +// room_outbox 的稳定 event_id 同时承担本服务消费幂等和腾讯云 IM 失败补偿。 +func (s *Service) HandleLuckyGiftOutboxEvent(ctx context.Context, envelope *luckygifteventsv1.EventEnvelope) error { + if s == nil || s.repository == nil { + return errors.New("room repository is not configured") + } + if envelope == nil || envelope.GetEventType() != luckygiftmq.EventTypeLuckyGiftDrawn { + return errors.New("unexpected lucky gift outbox event") + } + + var drawn luckygifteventsv1.LuckyGiftDrawn + if err := proto.Unmarshal(envelope.GetBody(), &drawn); err != nil { + return fmt.Errorf("decode lucky gift drawn body: %w", err) + } + if err := validateLuckyGiftDrawnEnvelope(envelope, &drawn); err != nil { + return err + } + + // owner 发布全部正向返奖事实;房间金色飘屏只消费达到统一 10x 门槛的 granted 中奖。 + // 低倍事实没有房间副作用,重复投递仍保持同样的 no-op 结果。 + if !strings.EqualFold(strings.TrimSpace(drawn.GetRewardStatus()), "granted") || + drawn.GetEffectiveRewardCoins() <= 0 || + drawn.GetMultiplierPpm() < luckygiftmq.LuckyGiftDisplayMinMultiplierPPM { + return nil + } + + body := &roomeventsv1.RoomLuckyGiftDrawn{ + DrawId: drawn.GetDrawId(), + CommandId: drawn.GetCommandId(), + PoolId: drawn.GetPoolId(), + GiftId: drawn.GetGiftId(), + GiftCount: drawn.GetGiftCount(), + SenderUserId: drawn.GetSenderUserId(), + TargetUserId: drawn.GetTargetUserId(), + SenderName: drawn.GetSenderName(), + SenderAvatar: drawn.GetSenderAvatar(), + SenderDisplayUserId: drawn.GetSenderDisplayUserId(), + SenderPrettyDisplayUserId: drawn.GetSenderPrettyDisplayUserId(), + VisibleRegionId: drawn.GetVisibleRegionId(), + CountryId: drawn.GetCountryId(), + CoinSpent: drawn.GetCoinSpent(), + RuleVersion: drawn.GetRuleVersion(), + ExperiencePool: drawn.GetExperiencePool(), + SelectedTierId: drawn.GetSelectedTierId(), + MultiplierPpm: drawn.GetMultiplierPpm(), + BaseRewardCoins: drawn.GetBaseRewardCoins(), + EffectiveRewardCoins: drawn.GetEffectiveRewardCoins(), + StageFeedback: drawn.GetStageFeedback(), + HighMultiplier: drawn.GetHighMultiplier(), + RewardStatus: "granted", + WalletTransactionId: drawn.GetWalletTransactionId(), + DrawCreatedAtMs: drawn.GetDrawCreatedAtMs(), + RewardGrantedAtMs: drawn.GetRewardGrantedAtMs(), + } + record, err := outbox.Build(drawn.GetRoomId(), roomLuckyGiftDrawnEventType, 0, time.UnixMilli(envelope.GetOccurredAtMs()).UTC(), body) + if err != nil { + return err + } + // owner event_id 是跨服务唯一事实 ID;覆盖 room 本地随机 ID 后,MQ 重投只能命中同一条 room_outbox。 + record.AppCode = strings.TrimSpace(envelope.GetAppCode()) + record.EventID = strings.TrimSpace(envelope.GetEventId()) + record.Envelope.AppCode = record.AppCode + record.Envelope.EventId = record.EventID + ctx = appcode.WithContext(ctx, record.AppCode) + if err := s.repository.SaveOutbox(ctx, []outbox.Record{record}); err != nil { + return err + } + + // 唤醒必须发生在 SaveOutbox 提交之后;重复事实最多产生一个多余唤醒,不会产生第二条 IM outbox。 + s.notifyOutboxCommitted([]outbox.Record{record}) + return nil +} + +func validateLuckyGiftDrawnEnvelope(envelope *luckygifteventsv1.EventEnvelope, drawn *luckygifteventsv1.LuckyGiftDrawn) error { + if envelope == nil || drawn == nil { + return errors.New("lucky gift drawn fact is required") + } + appCode := strings.TrimSpace(envelope.GetAppCode()) + drawID := strings.TrimSpace(drawn.GetDrawId()) + eventID := strings.TrimSpace(envelope.GetEventId()) + if appCode == "" || drawID == "" || drawID != strings.TrimSpace(envelope.GetDrawId()) || + strings.TrimSpace(drawn.GetRoomId()) == "" || strings.TrimSpace(drawn.GetCommandId()) == "" || + strings.TrimSpace(drawn.GetGiftId()) == "" || drawn.GetGiftCount() <= 0 || + drawn.GetSenderUserId() <= 0 || drawn.GetTargetUserId() <= 0 || + envelope.GetOccurredAtMs() <= 0 || drawn.GetDrawCreatedAtMs() <= 0 || drawn.GetRewardGrantedAtMs() <= 0 { + return errors.New("lucky gift drawn fact is incomplete") + } + if eventID != "lucky_gift_drawn:"+drawID { + // 同一 draw 只能映射到一个房间 IM 幂等键;不能接受 producer 临时生成的随机 event_id。 + return errors.New("lucky gift drawn event_id does not match draw_id") + } + return nil +} diff --git a/services/room-service/internal/room/service/lucky_gift_display_internal_test.go b/services/room-service/internal/room/service/lucky_gift_display_internal_test.go new file mode 100644 index 00000000..85f01bf2 --- /dev/null +++ b/services/room-service/internal/room/service/lucky_gift_display_internal_test.go @@ -0,0 +1,147 @@ +package service + +import ( + "context" + "sync" + "testing" + + "google.golang.org/protobuf/proto" + luckygifteventsv1 "hyapp.local/api/proto/events/luckygift/v1" + roomeventsv1 "hyapp.local/api/proto/events/room/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/luckygiftmq" + "hyapp/services/room-service/internal/room/outbox" +) + +type luckyGiftDisplayRepository struct { + Repository + + mu sync.Mutex + records map[string]outbox.Record +} + +func (r *luckyGiftDisplayRepository) SaveOutbox(_ context.Context, records []outbox.Record) error { + r.mu.Lock() + defer r.mu.Unlock() + if r.records == nil { + r.records = make(map[string]outbox.Record) + } + for _, record := range records { + key := record.AppCode + "\x00" + record.EventID + if _, exists := r.records[key]; !exists { + r.records[key] = record + } + } + return nil +} + +func (r *luckyGiftDisplayRepository) snapshot() []outbox.Record { + r.mu.Lock() + defer r.mu.Unlock() + result := make([]outbox.Record, 0, len(r.records)) + for _, record := range r.records { + result = append(result, record) + } + return result +} + +func TestHandleLuckyGiftOutboxEventPersistsOneDerivedRoomDisplayOutbox(t *testing.T) { + repository := &luckyGiftDisplayRepository{} + svc := &Service{repository: repository, outboxWake: make(chan struct{}, 4)} + envelope := luckyGiftDisplayEnvelope(t, luckygiftmq.LuckyGiftDisplayMinMultiplierPPM) + ctx := appcode.WithContext(context.Background(), "lalu") + + if err := svc.HandleLuckyGiftOutboxEvent(ctx, envelope); err != nil { + t.Fatalf("HandleLuckyGiftOutboxEvent failed: %v", err) + } + if err := svc.HandleLuckyGiftOutboxEvent(ctx, envelope); err != nil { + t.Fatalf("replayed HandleLuckyGiftOutboxEvent failed: %v", err) + } + + records := repository.snapshot() + if len(records) != 1 { + t.Fatalf("owner event replay must keep one room outbox row, got %d", len(records)) + } + record := records[0] + if record.AppCode != "lalu" || record.EventID != "lucky_gift_drawn:lucky_draw_room_1" || record.EventType != roomLuckyGiftDrawnEventType || record.RoomID != "room-lucky-display" || record.Envelope.GetRoomVersion() != 0 { + t.Fatalf("derived room outbox identity mismatch: %+v envelope=%+v", record, record.Envelope) + } + var body roomeventsv1.RoomLuckyGiftDrawn + if err := proto.Unmarshal(record.Envelope.GetBody(), &body); err != nil { + t.Fatalf("decode derived RoomLuckyGiftDrawn failed: %v", err) + } + if body.GetDrawId() != "lucky_draw_room_1" || body.GetSenderName() != "Sender" || body.GetMultiplierPpm() != luckygiftmq.LuckyGiftDisplayMinMultiplierPPM || body.GetRewardStatus() != "granted" { + t.Fatalf("derived RoomLuckyGiftDrawn mismatch: %+v", &body) + } +} + +func TestHandleLuckyGiftOutboxEventSkipsRewardBelowDisplayThreshold(t *testing.T) { + repository := &luckyGiftDisplayRepository{} + svc := &Service{repository: repository, outboxWake: make(chan struct{}, 1)} + envelope := luckyGiftDisplayEnvelope(t, luckygiftmq.LuckyGiftDisplayMinMultiplierPPM-1) + + if err := svc.HandleLuckyGiftOutboxEvent(context.Background(), envelope); err != nil { + t.Fatalf("below-threshold owner fact should be acknowledged: %v", err) + } + if records := repository.snapshot(); len(records) != 0 { + t.Fatalf("below-threshold owner fact must have no room display side effect: %+v", records) + } +} + +func TestHandleLuckyGiftOutboxEventRejectsUnstableEventID(t *testing.T) { + repository := &luckyGiftDisplayRepository{} + svc := &Service{repository: repository, outboxWake: make(chan struct{}, 1)} + envelope := luckyGiftDisplayEnvelope(t, luckygiftmq.LuckyGiftDisplayMinMultiplierPPM) + envelope.EventId = "evt_random" + + if err := svc.HandleLuckyGiftOutboxEvent(context.Background(), envelope); err == nil { + t.Fatal("owner fact with random event_id must be rejected") + } + if records := repository.snapshot(); len(records) != 0 { + t.Fatalf("invalid owner fact must not persist room display outbox: %+v", records) + } +} + +func luckyGiftDisplayEnvelope(t *testing.T, multiplierPPM int64) *luckygifteventsv1.EventEnvelope { + t.Helper() + drawn := &luckygifteventsv1.LuckyGiftDrawn{ + DrawId: "lucky_draw_room_1", + CommandId: "cmd_lucky_room_1", + RoomId: "room-lucky-display", + PoolId: "advanced", + GiftId: "43", + GiftCount: 1, + SenderUserId: 123456, + TargetUserId: 654321, + SenderName: "Sender", + SenderAvatar: "https://cdn.example.test/sender.png", + SenderDisplayUserId: "123456", + SenderPrettyDisplayUserId: "VIP123456", + VisibleRegionId: 1, + CountryId: 86, + CoinSpent: 100, + RuleVersion: 3, + ExperiencePool: "normal", + SelectedTierId: "10x", + MultiplierPpm: multiplierPPM, + BaseRewardCoins: 1000, + EffectiveRewardCoins: 1000, + HighMultiplier: true, + RewardStatus: "granted", + WalletTransactionId: "wallet-lucky-room-1", + DrawCreatedAtMs: 1_783_940_398_274, + RewardGrantedAtMs: 1_783_940_398_300, + } + body, err := proto.Marshal(drawn) + if err != nil { + t.Fatalf("marshal LuckyGiftDrawn failed: %v", err) + } + return &luckygifteventsv1.EventEnvelope{ + EventId: "lucky_gift_drawn:lucky_draw_room_1", + EventType: luckygiftmq.EventTypeLuckyGiftDrawn, + AppCode: "lalu", + DrawId: drawn.GetDrawId(), + OccurredAtMs: drawn.GetDrawCreatedAtMs(), + Body: body, + } +} diff --git a/services/room-service/internal/room/service/lucky_gift_test.go b/services/room-service/internal/room/service/lucky_gift_test.go index 6e387621..94d80a35 100644 --- a/services/room-service/internal/room/service/lucky_gift_test.go +++ b/services/room-service/internal/room/service/lucky_gift_test.go @@ -152,6 +152,13 @@ func TestSendGiftReturnsLuckyGiftDrawResult(t *testing.T) { GiftId: "rose", GiftCount: 1, PoolId: "super_lucky", + SenderDisplayProfile: &roomv1.SendGiftDisplayProfile{ + UserId: ownerID, + Username: "lucky sender", + Avatar: "https://cdn.example.com/sender.png", + DisplayUserId: "123456", + PrettyDisplayUserId: "HY-123456", + }, }) if err != nil { t.Fatalf("send lucky gift failed: %v", err) @@ -173,7 +180,11 @@ func TestSendGiftReturnsLuckyGiftDrawResult(t *testing.T) { drawMeta.GetCoinSpent() != 100 || drawMeta.GetDeviceId() != "device-session-1" || drawMeta.GetAnchorId() != "101" || - drawMeta.GetVisibleRegionId() != 9001 { + drawMeta.GetVisibleRegionId() != 9001 || + drawMeta.GetSenderName() != "lucky sender" || + drawMeta.GetSenderAvatar() != "https://cdn.example.com/sender.png" || + drawMeta.GetSenderDisplayUserId() != "123456" || + drawMeta.GetSenderPrettyDisplayUserId() != "HY-123456" { t.Fatalf("lucky draw meta mismatch: %+v", drawMeta) } } diff --git a/services/room-service/internal/room/service/moderation.go b/services/room-service/internal/room/service/moderation.go index b93e1894..d222eaa0 100644 --- a/services/room-service/internal/room/service/moderation.go +++ b/services/room-service/internal/room/service/moderation.go @@ -40,6 +40,12 @@ func (s *Service) MuteUser(ctx context.Context, req *roomv1.MuteUserRequest) (*r if err := canManageTarget(current, cmd.ActorUserID(), cmd.TargetUserID); err != nil { return mutationResult{}, nil, err } + if cmd.Muted { + if err := s.requireTargetWithoutVIPBenefit(ctx, req.GetMeta().GetRequestId(), cmd.TargetUserID, vipBenefitAntiMute); err != nil { + // anti_mute 只阻止普通房主/管理员新增禁言;解除既有禁言必须始终允许。 + return mutationResult{}, nil, err + } + } if current.MuteUsers[cmd.TargetUserID] == cmd.Muted { // 重复设置同一禁言状态是业务 no-op,仍由 pipeline 写 command log 做幂等保护。 return mutationResult{snapshot: current.ToProto()}, nil, nil @@ -117,6 +123,10 @@ func (s *Service) KickUser(ctx context.Context, req *roomv1.KickUserRequest) (*r if err := canManageTarget(current, cmd.ActorUserID(), cmd.TargetUserID); err != nil { return mutationResult{}, nil, err } + if err := s.requireTargetWithoutVIPBenefit(ctx, req.GetMeta().GetRequestId(), cmd.TargetUserID, vipBenefitAntiKick); err != nil { + // 平台封禁、风控和后台治理使用独立 SystemEvictUser;只有普通房间治理受 anti_kick 保护。 + return mutationResult{}, nil, err + } _, inRoom := current.OnlineUsers[cmd.TargetUserID] _, onSeat := current.SeatByUser(cmd.TargetUserID) diff --git a/services/room-service/internal/room/service/outbox_worker.go b/services/room-service/internal/room/service/outbox_worker.go index 55966e31..e0b21332 100644 --- a/services/room-service/internal/room/service/outbox_worker.go +++ b/services/room-service/internal/room/service/outbox_worker.go @@ -2,6 +2,7 @@ package service import ( "context" + "errors" "fmt" "log/slog" "sort" @@ -198,21 +199,33 @@ func (s *Service) processPendingOutbox(ctx context.Context, options OutboxWorker if options.RetryStrategy != outboxRetryExponentialBackoff { return fmt.Errorf("unsupported outbox retry_strategy %q", options.RetryStrategy) } - - // 补偿 worker 先抢占 pending/retryable 事件,再投递,避免多机同时处理同一批。 - records, err := s.claimPendingOutbox(ctx, options) - if err != nil { + appCodes, err := s.roomOutboxAppCodes(ctx) + if err != nil || len(appCodes) == 0 { return err } - sortOutboxRecords(records) - for _, record := range records { + for processed := 0; processed < options.BatchSize; processed++ { select { case <-ctx.Done(): - // 退出时不再领取新事件;已抢占但未处理完的事件会在 lock_until_ms 到期后重新可见。 + // 退出时不再领取新事件;已领取的单条事实会在有界 lease 后重新可见。 return ctx.Err() default: } + + // RocketMQ/direct publisher 都是同步调用,因此每次只 claim 一条。旧实现一次 + // claim 整批却只给一个 publish_timeout 的 lease,队尾尚未开始发送就可能被 + // 其他实例接管并发重复发布。逐条 claim 同时避免把崩溃恢复时间放大为 + // batch_size*publish_timeout,吞吐由配置的 worker concurrency 横向提供。 + claimOptions := options + claimOptions.BatchSize = 1 + records, err := s.claimPendingOutboxForApps(ctx, claimOptions, appCodes) + if err != nil { + return err + } + if len(records) == 0 { + return nil + } + record := records[0] if record.RetryCount >= options.MaxRetryCount { // 已达到最大失败次数的历史事件直接转死信,防止每轮 worker 继续打外部系统。 markCtx, markCancel := context.WithTimeout(appcode.WithContext(context.Background(), record.AppCode), options.PublishTimeout) @@ -235,7 +248,7 @@ func (s *Service) processPendingOutbox(ctx context.Context, options OutboxWorker publishCtx, cancel := context.WithTimeout(appcode.WithContext(context.Background(), record.AppCode), options.PublishTimeout) // 发布上下文不继承 worker ctx,避免 shutdown 取消导致已开始的单条外部投递无界处于未知状态。 - err := s.publishOutboxRecord(publishCtx, record) + err = s.publishOutboxRecord(publishCtx, record) cancel() if err != nil { // 投递失败转为 retryable,并写入指数退避时间,等待下一轮 worker 到期后再抢占。 @@ -282,7 +295,8 @@ func (s *Service) processPendingOutbox(ctx context.Context, options OutboxWorker } func sortOutboxRecords(records []outbox.Record) { - // DB claim 负责并发安全;批内稳定排序只把礼物、上下麦和进退房展示事件排到热度/榜单前面,降低发送方可感知尾延迟。 + // Direct IM 和机器人展示仍会在内存批次内调用此排序;主 MQ worker 改为 + // 逐条 claim 后不依赖批内排序,但保留统一优先级可避免展示路径语义漂移。 sort.SliceStable(records, func(i, j int) bool { leftPriority := outboxEventPriority(records[i].EventType) rightPriority := outboxEventPriority(records[j].EventType) @@ -308,9 +322,71 @@ func outboxEventPriority(eventType string) int { } func (s *Service) claimPendingOutbox(ctx context.Context, options OutboxWorkerOptions) ([]outbox.Record, error) { - lockUntilMS := time.Now().UTC().Add(options.PublishTimeout).UnixMilli() + appCodes, err := s.roomOutboxAppCodes(ctx) + if err != nil { + return nil, err + } + return s.claimPendingOutboxForApps(ctx, options, appCodes) +} + +func (s *Service) claimPendingOutboxForApps(ctx context.Context, options OutboxWorkerOptions, appCodes []string) ([]outbox.Record, error) { + if len(appCodes) == 0 { + return nil, nil + } + lockUntilMS := time.Now().UTC().Add(outboxSingleRecordLease(options.PublishTimeout)).UnixMilli() workerID := firstNonEmpty(strings.TrimSpace(options.WorkerID), s.nodeID) - return s.repository.ClaimPendingOutbox(ctx, workerID, options.BatchSize, lockUntilMS) + + // 主 worker 每次只 claim 一条;轮转 App 起点可保留单条 lease 语义,同时避免某个高流量 App 独占所有轮次。 + start := int(s.outboxClaimCursor.Add(1)-1) % len(appCodes) + for offset := 0; offset < len(appCodes); offset++ { + appCode := appCodes[(start+offset)%len(appCodes)] + records, err := s.repository.ClaimPendingOutbox(appcode.WithContext(ctx, appCode), workerID, options.BatchSize, lockUntilMS) + if err != nil { + return nil, err + } + if len(records) > 0 { + return records, nil + } + } + return nil, nil +} + +type roomOutboxAppCodeLister interface { + ListRoomOutboxAppCodes(ctx context.Context) ([]string, error) +} + +func (s *Service) roomOutboxAppCodes(ctx context.Context) ([]string, error) { + // memory/fake repository 没有跨 App 枚举能力时沿用调用上下文,保持隔离测试和旧实现兼容。 + lister, ok := s.repository.(roomOutboxAppCodeLister) + if !ok { + return []string{appcode.FromContext(ctx)}, nil + } + appCodes, err := lister.ListRoomOutboxAppCodes(ctx) + if err != nil { + return nil, err + } + seen := make(map[string]struct{}, len(appCodes)) + normalized := make([]string, 0, len(appCodes)) + for _, appCode := range appCodes { + appCode = appcode.Normalize(appCode) + if _, exists := seen[appCode]; exists { + continue + } + seen[appCode] = struct{}{} + normalized = append(normalized, appCode) + } + sort.Strings(normalized) + return normalized, nil +} + +func outboxSingleRecordLease(publishTimeout time.Duration) time.Duration { + if publishTimeout <= 0 { + publishTimeout = defaultOutboxWorkerOptions().PublishTimeout + } + // 一条事实最多经历一次外部发布 deadline 和一次状态更新 deadline;额外 + // 预留一个 deadline 吸收调度延迟。at-least-once 的 publish 成功/ACK 失败 + // 窗口仍由下游 event_id 幂等收敛,但不会因批内队尾 lease 过期主动放大重复。 + return 3 * publishTimeout } func (s *Service) markOutboxDelivered(ctx context.Context, eventID string) error { @@ -326,6 +402,17 @@ func (s *Service) markOutboxDead(ctx context.Context, eventID string, lastErr st } func (s *Service) publishOutboxRecord(ctx context.Context, record outbox.Record) error { + if record.EventType == roomLuckyGiftDrawnEventType { + // 真人中奖展示是 lucky-gift owner 事实在 room-service 的本地派生 outbox:只投腾讯云房间群。 + // 若继续走通用 outboxPublisher,会把派生事件重新发到 hyapp_room_outbox,造成事实 owner 混淆和循环 fanout。 + if record.Envelope == nil || record.Envelope.GetEventType() != roomLuckyGiftDrawnEventType { + return errors.New("room lucky gift display envelope is invalid") + } + if s.roomDisplayPublisher == nil { + return errors.New("room display publisher is not configured") + } + return s.roomDisplayPublisher.PublishOutboxEvent(ctx, record.Envelope) + } // Ignited 在 outbox worker 内安排延迟发射,保证 MQ 调度失败能通过 room_outbox retry;客户端 IM 展示已在 Room Cell 提交后直发。 if err := s.scheduleRoomRocketLaunchFromEnvelope(ctx, record.Envelope); err != nil { return err diff --git a/services/room-service/internal/room/service/outbox_worker_internal_test.go b/services/room-service/internal/room/service/outbox_worker_internal_test.go index a6be334c..98cb701d 100644 --- a/services/room-service/internal/room/service/outbox_worker_internal_test.go +++ b/services/room-service/internal/room/service/outbox_worker_internal_test.go @@ -1,20 +1,74 @@ package service import ( + "context" "testing" + "time" + roomeventsv1 "hyapp.local/api/proto/events/room/v1" + "hyapp/pkg/appcode" "hyapp/services/room-service/internal/room/outbox" ) +type countingOutboxPublisher struct { + events []*roomeventsv1.EventEnvelope +} + +type crossAppOutboxRepository struct { + Repository + claims []string +} + +func (r *crossAppOutboxRepository) ListRoomOutboxAppCodes(context.Context) ([]string, error) { + return []string{"fami", "lalu"}, nil +} + +func (r *crossAppOutboxRepository) ClaimPendingOutbox(ctx context.Context, _ string, _ int, _ int64) ([]outbox.Record, error) { + appCode := appcode.FromContext(ctx) + r.claims = append(r.claims, appCode) + if appCode != "fami" { + return nil, nil + } + return []outbox.Record{{AppCode: "fami", EventID: "lucky_gift_drawn:fami-draw"}}, nil +} + +func (p *countingOutboxPublisher) PublishOutboxEvent(_ context.Context, envelope *roomeventsv1.EventEnvelope) error { + p.events = append(p.events, envelope) + return nil +} + +func TestOutboxSingleRecordLeaseCoversPublishAndStateWrite(t *testing.T) { + if got := outboxSingleRecordLease(15 * time.Second); got != 45*time.Second { + t.Fatalf("single-record lease = %s, want 45s", got) + } + if got := outboxSingleRecordLease(0); got != 9*time.Second { + t.Fatalf("default single-record lease = %s, want 9s", got) + } +} + +func TestClaimPendingOutboxFindsNonDefaultApp(t *testing.T) { + repository := &crossAppOutboxRepository{} + svc := &Service{nodeID: "room-cross-app", repository: repository} + records, err := svc.claimPendingOutbox(context.Background(), OutboxWorkerOptions{BatchSize: 1, PublishTimeout: time.Second}) + if err != nil { + t.Fatalf("claim cross-app outbox: %v", err) + } + if len(records) != 1 || records[0].AppCode != "fami" { + t.Fatalf("non-default app outbox was not claimed: %+v", records) + } + if len(repository.claims) == 0 || repository.claims[len(repository.claims)-1] != "fami" { + t.Fatalf("claim contexts did not reach fami: %v", repository.claims) + } +} + func TestSortOutboxRecordsPrioritizesGiftDisplay(t *testing.T) { - // 低优先级事件故意给更早 created_at_ms,验证 worker 不会让热度/榜单压住礼物展示。 + // Direct IM/robot display 仍使用批内排序;低优先级事件故意更早,验证礼物展示优先。 records := []outbox.Record{ {EventID: "rank", EventType: "RoomRankChanged", CreatedAtMS: 1}, {EventID: "gift", EventType: "RoomGiftSent", CreatedAtMS: 2}, {EventID: "other", EventType: "RoomNoticeCreated", CreatedAtMS: 0}, {EventID: "heat", EventType: "RoomHeatChanged", CreatedAtMS: 0}, } - sortOutboxRecords(records) got := []string{records[0].EventID, records[1].EventID, records[2].EventID, records[3].EventID} @@ -25,3 +79,42 @@ func TestSortOutboxRecordsPrioritizesGiftDisplay(t *testing.T) { } } } + +func TestPublishOutboxRecordRoutesLuckyGiftDisplayOnlyToRoomIM(t *testing.T) { + display := &countingOutboxPublisher{} + fanout := &countingOutboxPublisher{} + svc := &Service{roomDisplayPublisher: display, outboxPublisher: fanout} + record := outbox.Record{ + EventID: "lucky_gift_drawn:draw-1", + EventType: roomLuckyGiftDrawnEventType, + RoomID: "room-1", + Envelope: &roomeventsv1.EventEnvelope{ + EventId: "lucky_gift_drawn:draw-1", + EventType: roomLuckyGiftDrawnEventType, + RoomId: "room-1", + }, + } + + if err := svc.publishOutboxRecord(context.Background(), record); err != nil { + t.Fatalf("publish lucky display outbox failed: %v", err) + } + if len(display.events) != 1 { + t.Fatalf("lucky display must reach room IM publisher once, got %d", len(display.events)) + } + if len(fanout.events) != 0 { + t.Fatalf("derived lucky display must not return to room MQ fanout: %d", len(fanout.events)) + } +} + +func TestPublishOutboxRecordKeepsLuckyGiftPendingWithoutRoomIMPublisher(t *testing.T) { + svc := &Service{outboxPublisher: &countingOutboxPublisher{}} + err := svc.publishOutboxRecord(context.Background(), outbox.Record{ + EventID: "lucky_gift_drawn:draw-2", + EventType: roomLuckyGiftDrawnEventType, + RoomID: "room-2", + Envelope: &roomeventsv1.EventEnvelope{EventId: "lucky_gift_drawn:draw-2", EventType: roomLuckyGiftDrawnEventType, RoomId: "room-2"}, + }) + if err == nil { + t.Fatal("lucky display without room IM publisher must stay retryable") + } +} diff --git a/services/room-service/internal/room/service/pin_test.go b/services/room-service/internal/room/service/pin_test.go index ef761441..39e333fa 100644 --- a/services/room-service/internal/room/service/pin_test.go +++ b/services/room-service/internal/room/service/pin_test.go @@ -155,6 +155,169 @@ func TestPinnedRoomListOrdersRegionCountryThenCountryBucketsForNew(t *testing.T) } } +func TestAllRegionRoomListKeepsPinsThenCountryPresenceBuckets(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + svc := roomservice.New(roomservice.Config{ + NodeID: "node-all-region-order-test", + LeaseTTL: 10 * time.Second, + RankLimit: 20, + SnapshotEveryN: 1, + }, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher()) + + createPinnedListRoom(t, ctx, svc, "room-global-pin", "global-pin", 3221, 9002, "AE") + createPinnedListRoom(t, ctx, svc, "room-region-pin-all", "region-pin-all", 3222, 9002, "AE") + createPinnedListRoom(t, ctx, svc, "room-country-pin-all", "country-pin-all", 3223, 9001, "US") + createPinnedListRoom(t, ctx, svc, "room-viewer-online-all", "viewer-online-all", 3224, 9001, "US") + createPinnedListRoom(t, ctx, svc, "room-other-online-all", "other-online-all", 3225, 9002, "AE") + createPinnedListRoom(t, ctx, svc, "room-viewer-empty-all", "viewer-empty-all", 3226, 9002, "US") + createPinnedListRoom(t, ctx, svc, "room-other-empty-all", "other-empty-all", 3227, 9001, "AE") + for index, roomID := range []string{ + "room-global-pin", + "room-region-pin-all", + "room-country-pin-all", + "room-viewer-online-all", + "room-other-online-all", + "room-viewer-empty-all", + "room-other-empty-all", + } { + repository.SetRoomSortScore(roomID, int64(1000-index)) + } + repository.SetRoomPresence("room-viewer-online-all", 1, 0) + repository.SetRoomPresence("room-other-online-all", 1, 0) + repository.SetRoomPresence("room-viewer-empty-all", 0, 0) + repository.SetRoomPresence("room-other-empty-all", 0, 0) + expiresAtMS := time.Now().UTC().Add(24 * time.Hour).UnixMilli() + repository.PinRoomWithType("room-global-pin", "global", 0, 100, expiresAtMS) + repository.PinRoomWithType("room-region-pin-all", "region", 9002, 90, expiresAtMS) + repository.PinRoomWithType("room-country-pin-all", "country", 9001, 80, expiresAtMS) + + page, err := svc.ListRooms(ctx, &roomv1.ListRoomsRequest{ + Meta: &roomv1.RequestMeta{AppCode: appcode.Default}, + ViewerUserId: 4001, + VisibleRegionId: 9001, + ViewerCountryCode: "US", + AllVisibleRegions: true, + Tab: "hot", + Limit: 10, + }) + if err != nil { + t.Fatalf("list all-region rooms failed: %v", err) + } + want := []string{ + "room-global-pin", + "room-region-pin-all", + "room-country-pin-all", + "room-viewer-online-all", + "room-other-online-all", + "room-viewer-empty-all", + "room-other-empty-all", + } + if got := roomIDs(page.GetRooms()); len(got) != len(want) { + t.Fatalf("all-region room count mismatch: got=%v want=%v", got, want) + } else { + for index := range want { + if got[index] != want[index] { + t.Fatalf("all-region order mismatch: got=%v want=%v", got, want) + } + } + } +} + +func TestAllRegionPinTypePrecedenceMatchesRedisFallbackOrder(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + svc := roomservice.New(roomservice.Config{ + NodeID: "node-pin-type-precedence-test", + LeaseTTL: 10 * time.Second, + RankLimit: 20, + SnapshotEveryN: 1, + }, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher()) + + createPinnedListRoom(t, ctx, svc, "room-pin-a", "pin-a", 3241, 9001, "US") + createPinnedListRoom(t, ctx, svc, "room-pin-b", "pin-b", 3242, 9001, "US") + expiresAtMS := time.Now().UTC().Add(24 * time.Hour).UnixMilli() + // MySQL CASE 对同房间固定先选 global=10,不会让 region=100 覆盖;因此 B(region=50) 必须排在 A 前。 + repository.PinRoomWithType("room-pin-a", "global", 0, 10, expiresAtMS) + repository.PinRoomWithType("room-pin-a", "region", 9001, 100, expiresAtMS) + repository.PinRoomWithType("room-pin-b", "region", 9001, 50, expiresAtMS) + + page, err := svc.ListRooms(ctx, &roomv1.ListRoomsRequest{ + Meta: &roomv1.RequestMeta{AppCode: appcode.Default}, + ViewerUserId: 4001, + VisibleRegionId: 9001, + AllVisibleRegions: true, + Tab: "hot", + Limit: 10, + }) + if err != nil { + t.Fatalf("list fallback pin precedence failed: %v", err) + } + if got := roomIDs(page.GetRooms()); len(got) != 2 || got[0] != "room-pin-b" || got[1] != "room-pin-a" { + t.Fatalf("MySQL fallback must order B(region=50) before A(global=10 suppressing region=100), got=%v", got) + } +} + +func TestRoomListRegionFilterBindsCursorAndCountryFilterSpansRegions(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + svc := roomservice.New(roomservice.Config{ + NodeID: "node-room-filter-test", + LeaseTTL: 10 * time.Second, + RankLimit: 20, + SnapshotEveryN: 1, + }, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher()) + + createPinnedListRoom(t, ctx, svc, "room-region-2-newer", "region-2-newer", 3231, 9002, "AE") + createPinnedListRoom(t, ctx, svc, "room-region-2-older", "region-2-older", 3232, 9002, "AE") + createPinnedListRoom(t, ctx, svc, "room-region-3-ae", "region-3-ae", 3233, 9003, "AE") + repository.SetRoomSortScore("room-region-2-newer", 300) + repository.SetRoomSortScore("room-region-2-older", 200) + repository.SetRoomSortScore("room-region-3-ae", 100) + + firstPage, err := svc.ListRooms(ctx, &roomv1.ListRoomsRequest{ + Meta: &roomv1.RequestMeta{AppCode: appcode.Default}, + ViewerUserId: 4001, + VisibleRegionId: 9001, + FilterRegionId: 9002, + Tab: "hot", + Limit: 1, + }) + if err != nil { + t.Fatalf("list region-filtered first page failed: %v", err) + } + if got := roomIDs(firstPage.GetRooms()); len(got) != 1 || got[0] != "room-region-2-newer" || firstPage.GetNextCursor() == "" { + t.Fatalf("region-filtered first page mismatch: rooms=%v cursor=%q", got, firstPage.GetNextCursor()) + } + if _, err := svc.ListRooms(ctx, &roomv1.ListRoomsRequest{ + Meta: &roomv1.RequestMeta{AppCode: appcode.Default}, + ViewerUserId: 4001, + VisibleRegionId: 9001, + FilterRegionId: 9003, + Tab: "hot", + Limit: 1, + Cursor: firstPage.GetNextCursor(), + }); err == nil { + t.Fatal("region cursor must not be reusable for another explicit region") + } + + countryPage, err := svc.ListRooms(ctx, &roomv1.ListRoomsRequest{ + Meta: &roomv1.RequestMeta{AppCode: appcode.Default}, + ViewerUserId: 4001, + VisibleRegionId: 9001, + AllVisibleRegions: true, + CountryCode: "AE", + Tab: "hot", + Limit: 10, + }) + if err != nil { + t.Fatalf("list all-region country rooms failed: %v", err) + } + if got := roomIDs(countryPage.GetRooms()); len(got) != 3 { + t.Fatalf("country filter must span all mapped room regions, got=%v", got) + } +} + func TestCountryPinOnlyAppliesToSelectedCountryTab(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) diff --git a/services/room-service/internal/room/service/pipeline.go b/services/room-service/internal/room/service/pipeline.go index 2239acdb..92a50496 100644 --- a/services/room-service/internal/room/service/pipeline.go +++ b/services/room-service/internal/room/service/pipeline.go @@ -3,10 +3,12 @@ package service import ( "bytes" "context" + "errors" "fmt" "time" "hyapp/pkg/appcode" + "hyapp/pkg/giftlimits" "hyapp/pkg/xerr" "hyapp/services/room-service/internal/room/command" "hyapp/services/room-service/internal/room/outbox" @@ -15,7 +17,10 @@ import ( "hyapp/services/room-service/internal/router" ) -const roomMutationCommitTimeout = 5 * time.Second +const ( + roomMutationCommitTimeout = 5 * time.Second + roomMutationConfirmCommitTimeout = 2 * time.Second +) func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayable bool, mutate func(now time.Time, current *state.RoomState, localRank *rank.LocalRank) (mutationResult, []outbox.Record, error)) (mutationResult, error) { startedAt := time.Now() @@ -28,25 +33,31 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl // command meta 是幂等、路由、审计和权限判断的共同基础,缺一不可。 return mutationResult{}, xerr.New(xerr.InvalidArgument, "command meta is incomplete") } + if !giftlimits.ValidCommandID(cmd.ID()) { + // room_command_log.command_id 是 VARCHAR(128);入口显式拒绝比依赖 MySQL 截断/报错更安全, + // 同时确保 wallet、lucky-gift 和 Room Cell 使用完全相同的稳定幂等键。 + return mutationResult{}, xerr.New(xerr.InvalidArgument, "command_id is too long") + } if s.isDraining() { // 下线中的节点只让已经进入执行链路的命令收尾,不再接收新的 Room Cell 命令。 return mutationResult{}, xerr.New(xerr.Unavailable, "room service is draining") } idempotencyStartedAt := time.Now() - payload, seen, err := s.commandPayloadForIdempotency(ctx, cmd) + payload, existingCommand, seen, err := s.commandRecordForIdempotency(ctx, cmd) idempotencyMS = elapsedMS(idempotencyStartedAt) if err != nil { return mutationResult{}, err } if seen { - // 幂等命令不重新执行 mutate,也不会再次调用 wallet 或写 outbox。 + // 幂等命令不重新执行 mutate,也不会再次调用 wallet 或写 outbox;SendGift V2 从首次 result_payload + // 重放账务和幸运礼物结果,旧命令没有结果时才维持历史 snapshot-only 兼容语义。 snapshot, err := s.currentSnapshot(ctx, cmd.RoomID()) if err != nil { return mutationResult{}, err } - return mutationResult{snapshot: snapshot}, nil + return replayMutationResult(existingCommand, snapshot) } // 确保当前节点持有房间 lease,并在没有内存 Cell 时完成恢复。 @@ -58,6 +69,15 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl } value, err := roomCell.Do(ctx, func(current *state.RoomState, currentRank *rank.LocalRank) (any, error) { + // 外层预检与进入 Room Cell 之间可能已有相同 command_id 完成提交;必须在串行临界区内权威重查, + // 否则第二个请求会复用 wallet 幂等回执却再次增加房间热度、榜单和 outbox。 + _, committedCommand, alreadyCommitted, err := s.commandRecordForIdempotency(ctx, cmd) + if err != nil { + return nil, err + } + if alreadyCommitted { + return replayMutationResult(committedCommand, snapshotWithApp(ctx, current.ToProto())) + } // 在副本上执行命令,只有 command log/outbox 保存成功后才整体替换当前状态。 nextState := current.Clone() nextRank := currentRank.Clone() @@ -94,19 +114,26 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl directIMRecords = append(directIMRecords, result.directIMRecords...) saveStartedAt := time.Now() commitCtx, commitCancel := roomMutationCommitContext(ctx) - err := s.repository.SaveMutation(commitCtx, MutationCommit{ - Command: CommandRecord{ - AppCode: appcode.FromContext(ctx), - RoomID: cmd.RoomID(), - RoomVersion: nextState.Version, - CommandID: cmd.ID(), - CommandType: cmd.Type(), - Payload: commandPayloadForRecord(payload, result), - Replayable: replayable, - OwnerNodeID: s.nodeID, - LeaseToken: lease.LeaseToken, - CreatedAtMS: now.UnixMilli(), - }, + resultPayload, err := marshalMutationResult(result) + if err != nil { + commitCancel() + return nil, err + } + commandRecord := CommandRecord{ + AppCode: appcode.FromContext(ctx), + RoomID: cmd.RoomID(), + RoomVersion: nextState.Version, + CommandID: cmd.ID(), + CommandType: cmd.Type(), + Payload: commandPayloadForRecord(payload, result), + ResultPayload: resultPayload, + Replayable: replayable, + OwnerNodeID: s.nodeID, + LeaseToken: lease.LeaseToken, + CreatedAtMS: now.UnixMilli(), + } + err = s.repository.SaveMutation(commitCtx, MutationCommit{ + Command: commandRecord, OutboxRecords: durableOutboxRecords, DeleteRoom: result.deleteRoom, RoomStatus: result.roomStatus, @@ -118,10 +145,29 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl RoomUserGiftStats: result.roomUserGiftStats, RoomUserContributionPeriodStats: result.roomUserContributionPeriodStats, RoomWeeklyContribution: result.roomWeeklyContribution, + GiftOperation: giftOperationCommitForResult(cmd, resultPayload, now), }) commitCancel() + if errors.Is(err, ErrCommandAlreadyCommitted) { + // MySQL 唯一键是跨进程最后防线。事务已经整体回滚,此分支只读取首次结果,绝不能替换当前 Cell 副本。 + committed, exists, loadErr := s.loadCommandAfterMutationError(ctx, cmd.RoomID(), cmd.ID()) + if loadErr != nil { + return nil, loadErr + } + if !exists { + return nil, err + } + return replayMutationResult(committed, snapshotWithApp(ctx, current.ToProto())) + } if err != nil { - return nil, err + // COMMIT can succeed in MySQL while its acknowledgement is lost to a connection reset. The gift + // operation is then already committed and recovery workers correctly stop claiming it, so returning + // the transport error without advancing this Cell would leave memory permanently behind its owner DB. + // Only the exact record produced above, including owner lease, room version and typed result, is + // sufficient proof; all earlier SQL failures still return the original error unchanged. + if !s.mutationCommitConfirmed(ctx, commandRecord) { + return nil, err + } } saveMutationMS += elapsedMS(saveStartedAt) // 保存已提交 outbox 记录,出 Cell 锁后由 outbox worker 异步投递 activity/MQ 业务事实,避免外部慢调用占住房间串行执行队列。 @@ -148,22 +194,21 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl } saveStartedAt := time.Now() commitCtx, commitCancel := roomMutationCommitContext(ctx) - err := s.repository.SaveMutation(commitCtx, MutationCommit{ - Command: CommandRecord{ - AppCode: appcode.FromContext(ctx), - RoomID: cmd.RoomID(), - RoomVersion: current.Version, - CommandID: cmd.ID(), - CommandType: cmd.Type(), - Payload: payload, - Replayable: false, - OwnerNodeID: s.nodeID, - LeaseToken: lease.LeaseToken, - CreatedAtMS: now.UnixMilli(), - }, - }) + commandRecord := CommandRecord{ + AppCode: appcode.FromContext(ctx), + RoomID: cmd.RoomID(), + RoomVersion: current.Version, + CommandID: cmd.ID(), + CommandType: cmd.Type(), + Payload: payload, + Replayable: false, + OwnerNodeID: s.nodeID, + LeaseToken: lease.LeaseToken, + CreatedAtMS: now.UnixMilli(), + } + err := s.repository.SaveMutation(commitCtx, MutationCommit{Command: commandRecord}) commitCancel() - if err != nil { + if err != nil && !s.mutationCommitConfirmed(ctx, commandRecord) { return nil, err } saveMutationMS += elapsedMS(saveStartedAt) @@ -211,46 +256,81 @@ func roomMutationCommitContext(ctx context.Context) (context.Context, context.Ca return context.WithTimeout(context.WithoutCancel(ctx), roomMutationCommitTimeout) } +func (s *Service) loadCommandAfterMutationError(ctx context.Context, roomID string, commandID string) (CommandRecord, bool, error) { + // A caller disconnect or the expired recovery-attempt budget must not cancel the one authoritative + // read that resolves an unknown MySQL COMMIT result. The short independent deadline prevents this + // safety check from becoming another way to hold the Room Cell indefinitely. + confirmCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), roomMutationConfirmCommitTimeout) + defer cancel() + return s.repository.GetCommand(confirmCtx, roomID, commandID) +} + +func (s *Service) mutationCommitConfirmed(ctx context.Context, expected CommandRecord) bool { + committed, exists, err := s.loadCommandAfterMutationError(ctx, expected.RoomID, expected.CommandID) + if err != nil || !exists { + return false + } + // Exact byte equality is intentional: SendGift replaces its command payload with the settled wallet + // facts and stores a protobuf typed result. Idempotency-equivalent input alone cannot prove that this + // particular nextState/outbox transaction committed under the lease currently owned by the Cell. + return committed.AppCode == expected.AppCode && + committed.RoomID == expected.RoomID && + committed.RoomVersion == expected.RoomVersion && + committed.CommandID == expected.CommandID && + committed.CommandType == expected.CommandType && + committed.OwnerNodeID == expected.OwnerNodeID && + committed.LeaseToken == expected.LeaseToken && + committed.Replayable == expected.Replayable && + committed.CreatedAtMS == expected.CreatedAtMS && + bytes.Equal(committed.Payload, expected.Payload) && + bytes.Equal(committed.ResultPayload, expected.ResultPayload) +} + func (s *Service) commandPayloadForIdempotency(ctx context.Context, cmd command.Command) ([]byte, bool, error) { + payload, _, seen, err := s.commandRecordForIdempotency(ctx, cmd) + return payload, seen, err +} + +func (s *Service) commandRecordForIdempotency(ctx context.Context, cmd command.Command) ([]byte, CommandRecord, bool, error) { // 先序列化当前命令,后续无论首次提交还是冲突校验都使用同一份 payload。 payload, err := command.Serialize(cmd) if err != nil { - return nil, false, err + return nil, CommandRecord{}, false, err } // command_id 在同一 room_id 内唯一;已有记录表示客户端重试或 command_id 被错误复用。 record, exists, err := s.repository.GetCommand(ctx, cmd.RoomID(), cmd.ID()) if err != nil { - return nil, false, err + return nil, CommandRecord{}, false, err } if !exists { - return payload, false, nil + return payload, CommandRecord{}, false, nil } if cmd.Type() == (command.SetRoomPassword{}).Type() && record.CommandType == (command.SetRoomPassword{}).Type() { // 密码命令的持久 payload 只有哈希,必须走专门逻辑比较 transient 明文和已存哈希。 matched, err := setRoomPasswordIdempotencyMatches(cmd, payload, record.Payload) if err != nil { - return nil, true, err + return nil, record, true, err } if !matched { - return nil, true, xerr.New(xerr.Conflict, "command_id payload conflict") + return nil, record, true, xerr.New(xerr.Conflict, "command_id payload conflict") } - return payload, true, nil + return payload, record, true, nil } currentIDPayload, err := command.IdempotencyPayload(payload) if err != nil { - return nil, true, err + return nil, record, true, err } storedIDPayload, err := command.IdempotencyPayload(record.Payload) if err != nil { - return nil, true, err + return nil, record, true, err } if record.CommandType != cmd.Type() || !bytes.Equal(storedIDPayload, currentIDPayload) { // 同一个 command_id 携带不同业务载荷必须显式冲突,不能返回旧命令结果掩盖客户端错误。 - return nil, true, xerr.New(xerr.Conflict, "command_id payload conflict") + return nil, record, true, xerr.New(xerr.Conflict, "command_id payload conflict") } - return payload, true, nil + return payload, record, true, nil } func setRoomPasswordIdempotencyMatches(cmd command.Command, currentPayload []byte, storedPayload []byte) (bool, error) { diff --git a/services/room-service/internal/room/service/pipeline_v2_internal_test.go b/services/room-service/internal/room/service/pipeline_v2_internal_test.go new file mode 100644 index 00000000..1145c28b --- /dev/null +++ b/services/room-service/internal/room/service/pipeline_v2_internal_test.go @@ -0,0 +1,204 @@ +package service + +import ( + "context" + "errors" + "sync" + "sync/atomic" + "testing" + "time" + + roomv1 "hyapp.local/api/proto/room/v1" + "hyapp/pkg/appcode" + "hyapp/services/room-service/internal/integration" + "hyapp/services/room-service/internal/room/cell" + "hyapp/services/room-service/internal/room/command" + "hyapp/services/room-service/internal/room/outbox" + "hyapp/services/room-service/internal/room/rank" + "hyapp/services/room-service/internal/room/state" + "hyapp/services/room-service/internal/router" +) + +// pipelineRaceRepository embeds the wide production boundary so this unit test can focus on the +// command-log contract. Any unexpected repository call still panics through the nil embedded interface. +type pipelineRaceRepository struct { + Repository + mu sync.Mutex + commands map[string]CommandRecord + getCalls atomic.Int32 + saveCalls atomic.Int32 + thirdGet chan struct{} + // loseCommitAck simulates MySQL committing the transaction and then losing the COMMIT response. + loseCommitAck atomic.Bool +} + +func (r *pipelineRaceRepository) GetCommand(_ context.Context, roomID string, commandID string) (CommandRecord, bool, error) { + if r.getCalls.Add(1) == 3 && r.thirdGet != nil { + close(r.thirdGet) + } + r.mu.Lock() + defer r.mu.Unlock() + record, exists := r.commands[roomID+"\x00"+commandID] + return record, exists, nil +} + +func (r *pipelineRaceRepository) SaveMutation(_ context.Context, commit MutationCommit) error { + r.saveCalls.Add(1) + r.mu.Lock() + key := commit.Command.RoomID + "\x00" + commit.Command.CommandID + if _, exists := r.commands[key]; exists { + r.mu.Unlock() + return ErrCommandAlreadyCommitted + } + record := commit.Command + record.Payload = append([]byte(nil), record.Payload...) + record.ResultPayload = append([]byte(nil), record.ResultPayload...) + r.commands[key] = record + r.mu.Unlock() + if r.loseCommitAck.CompareAndSwap(true, false) { + return errors.New("mysql commit acknowledgement lost") + } + return nil +} + +func TestMutateRoomRechecksCommandInsideCellAndReplaysTypedResult(t *testing.T) { + ctx := appcode.WithContext(context.Background(), appcode.Default) + repository := &pipelineRaceRepository{commands: make(map[string]CommandRecord), thirdGet: make(chan struct{})} + directory := router.NewMemoryDirectory() + svc := New(Config{NodeID: "node-pipeline-v2", LeaseTTL: 10 * time.Second, SnapshotEveryN: 1}, directory, repository, + nil, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher()) + + roomID := "room-pipeline-v2" + initial := state.NewRoomState(roomID, 101, 8, "voice") + initial.Version = 1 + lease, err := directory.EnsureOwner(ctx, runtimeRoomKeyFromContext(ctx, roomID), svc.nodeID, svc.clock.Now(), svc.leaseTTL) + if err != nil { + t.Fatalf("acquire test lease: %v", err) + } + svc.installCell(ctx, roomID, cell.New(initial, rank.New(20)), lease.LeaseToken) + + cmd := command.SendGift{Base: command.Base{AppCode: appcode.Default, CommandID: "cmd-pipeline-v2", ActorID: 101, Room: roomID}, + TargetType: "user", TargetUserID: 202, TargetUserIDs: []int64{202}, RequestedTargetUserIDs: []int64{202}, GiftID: "rose", GiftCount: 1} + started := make(chan struct{}) + release := make(chan struct{}) + var mutateCalls atomic.Int32 + mutate := func(_ time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { + if mutateCalls.Add(1) == 1 { + close(started) + <-release + } + current.Heat += 100 + current.Version++ + return mutationResult{ + snapshot: current.ToProto(), + deleteRoom: true, // 跳过与本测试无关的 snapshot/list projections;Cell 本身仍正常提交。 + giftV2: &roomv1.SendGiftResultV2{ + ApiVersion: 2, CommandId: cmd.ID(), BillingReceiptId: "receipt-once", CoinBalanceAfter: 900, + SettledGiftCount: 1, SettledTargetUserIds: []int64{202}, CommittedRoomVersion: current.Version, + RoomHeat: current.Heat, OperationStatus: GiftOperationStatusCommitted, + }, + }, nil, nil + } + + results := make(chan mutationResult, 2) + errors := make(chan error, 2) + go func() { + result, err := svc.mutateRoom(ctx, cmd, true, mutate) + results <- result + errors <- err + }() + <-started + go func() { + result, err := svc.mutateRoom(ctx, cmd, true, mutate) + results <- result + errors <- err + }() + // 第三次读取是第二个请求的 Cell 外预检;确认它已经 miss 后再放开首请求,稳定复现历史竞态窗口。 + <-repository.thirdGet + close(release) + + replays := 0 + for range 2 { + if err := <-errors; err != nil { + t.Fatalf("mutateRoom failed: %v", err) + } + result := <-results + if result.giftV2 == nil || result.giftV2.GetBillingReceiptId() != "receipt-once" || result.giftV2.GetCoinBalanceAfter() != 900 { + t.Fatalf("typed result was not preserved: %+v", result.giftV2) + } + if result.giftV2.GetIdempotentReplay() { + replays++ + } + } + if mutateCalls.Load() != 1 || replays != 1 { + t.Fatalf("same command must mutate once and replay once: mutate_calls=%d replays=%d", mutateCalls.Load(), replays) + } + roomState, _, err := svc.cells[runtimeRoomKeyFromContext(ctx, roomID)].roomCell.Snapshot(ctx) + if err != nil { + t.Fatalf("read room state: %v", err) + } + if roomState.Heat != 100 || roomState.Version != 2 { + t.Fatalf("duplicate command changed Cell twice: heat=%d version=%d", roomState.Heat, roomState.Version) + } +} + +func TestMutateRoomConvergesCellAfterCommittedTransactionLosesAck(t *testing.T) { + ctx := appcode.WithContext(context.Background(), appcode.Default) + repository := &pipelineRaceRepository{commands: make(map[string]CommandRecord)} + repository.loseCommitAck.Store(true) + directory := router.NewMemoryDirectory() + svc := New(Config{NodeID: "node-pipeline-commit-unknown", LeaseTTL: 10 * time.Second, SnapshotEveryN: 1}, directory, repository, + nil, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher()) + + roomID := "room-pipeline-commit-unknown" + initial := state.NewRoomState(roomID, 101, 8, "voice") + initial.Version = 1 + lease, err := directory.EnsureOwner(ctx, runtimeRoomKeyFromContext(ctx, roomID), svc.nodeID, svc.clock.Now(), svc.leaseTTL) + if err != nil { + t.Fatalf("acquire test lease: %v", err) + } + svc.installCell(ctx, roomID, cell.New(initial, rank.New(20)), lease.LeaseToken) + + cmd := command.SendGift{Base: command.Base{AppCode: appcode.Default, CommandID: "cmd-commit-unknown", ActorID: 101, Room: roomID}, + TargetType: "user", TargetUserID: 202, TargetUserIDs: []int64{202}, RequestedTargetUserIDs: []int64{202}, GiftID: "rose", GiftCount: 1} + var debitAndMutationCalls atomic.Int32 + mutate := func(_ time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { + debitAndMutationCalls.Add(1) + current.Heat += 100 + current.Version++ + return mutationResult{ + snapshot: current.ToProto(), + deleteRoom: true, // Isolate the Cell/command-log contract from snapshot and list projections. + giftV2: &roomv1.SendGiftResultV2{ + ApiVersion: 2, CommandId: cmd.ID(), BillingReceiptId: "receipt-commit-unknown", CoinBalanceAfter: 900, + BalanceVersion: 17, SettledGiftCount: 1, SettledTargetUserIds: []int64{202}, + CommittedRoomVersion: current.Version, RoomHeat: current.Heat, OperationStatus: GiftOperationStatusCommitted, + }, + }, nil, nil + } + + first, err := svc.mutateRoom(ctx, cmd, true, mutate) + if err != nil { + t.Fatalf("lost COMMIT acknowledgement should converge from the stored command: %v", err) + } + if !first.applied || first.giftV2 == nil || first.giftV2.GetBalanceVersion() != 17 { + t.Fatalf("first typed result was not retained after commit confirmation: %+v", first.giftV2) + } + replay, err := svc.mutateRoom(ctx, cmd, true, mutate) + if err != nil { + t.Fatalf("retry should replay the confirmed typed result: %v", err) + } + if replay.giftV2 == nil || !replay.giftV2.GetIdempotentReplay() || replay.giftV2.GetBillingReceiptId() != "receipt-commit-unknown" || replay.giftV2.GetBalanceVersion() != 17 { + t.Fatalf("typed replay drifted after unknown commit: %+v", replay.giftV2) + } + if debitAndMutationCalls.Load() != 1 || repository.saveCalls.Load() != 1 { + t.Fatalf("unknown COMMIT must not debit or persist twice: mutate=%d save=%d", debitAndMutationCalls.Load(), repository.saveCalls.Load()) + } + roomState, _, err := svc.cells[runtimeRoomKeyFromContext(ctx, roomID)].roomCell.Snapshot(ctx) + if err != nil { + t.Fatalf("read converged room state: %v", err) + } + if roomState.Heat != 100 || roomState.Version != 2 { + t.Fatalf("Cell did not converge exactly once: heat=%d version=%d", roomState.Heat, roomState.Version) + } +} diff --git a/services/room-service/internal/room/service/presence.go b/services/room-service/internal/room/service/presence.go index b475ac33..b9b06c0f 100644 --- a/services/room-service/internal/room/service/presence.go +++ b/services/room-service/internal/room/service/presence.go @@ -36,6 +36,7 @@ func (s *Service) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*r ActorCountryID: req.GetActorCountryId(), ActorRegionID: req.GetActorRegionId(), ActorIsRobot: req.GetActorIsRobot(), + PresenceRecovery: req.GetPresenceRecovery(), ActorDisplayProfile: userDisplayProfileFromProto(req.GetActorDisplayProfile()), // entry_vehicle 来自 gateway 入房瞬间的 wallet 快照,写入 command log 后可稳定重放。 EntryVehicle: entryVehicleSnapshotFromProto(req.GetEntryVehicle()), @@ -45,6 +46,20 @@ func (s *Service) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*r // 客户端不传角色时按普通观众进入房间。 cmd.Role = "audience" } + // 进房展示只查询一次 wallet effective state,并在响应、首次 outbox 与重复进房 direct IM 之间复用。 + // 查询失败不阻断核心 presence:VIP 入场表现可以降级,但房间可用性不能被纯展示依赖拖垮。 + joinVIP := effectiveVIPAccess{} + if !cmd.ActorIsRobot && cmd.ActorUserID() > 0 && cmd.RoomID() != "" && cmd.ID() != "" { + if loadedVIP, lookupErr := s.loadEffectiveVIPAccess(ctx, cmd.RequestID, cmd.ActorUserID()); lookupErr != nil { + logx.Warn(ctx, "room_join_vip_lookup_failed", + slog.String("room_id", cmd.RoomID()), + slog.Int64("user_id", cmd.ActorUserID()), + slog.String("error", lookupErr.Error()), + ) + } else { + joinVIP = loadedVIP + } + } reactivateClosedRoom, err := s.roomEntryPolicy(ctx, cmd.RoomID(), cmd.ActorUserID()) if err != nil { return nil, err @@ -84,13 +99,21 @@ func (s *Service) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*r joinRole = "owner" } if existing, exists := current.OnlineUsers[cmd.ActorUserID()]; exists { - // 重复 JoinRoom 表示用户重新打开房间页或重连:业务 presence 只刷新 last_seen, - // 但房间 UI 仍需要一条入房飘窗。这里把事件放进 directIMRecords,不进入 durable outbox, - // 避免统计服务把同一个在线用户重复计为进房活跃。 existing.LastSeenAtMS = now.UnixMilli() current.Version++ + if cmd.PresenceRecovery { + // 恢复请求与正常 heartbeat 可能并发;presence 已经存在时仍只刷新 + // last_seen,不能因竞态补发入场飘窗。 + snapshot := current.ToProto() + result.snapshot = snapshot + result.user = findProtoUser(snapshot, cmd.ActorUserID()) + return result, nil, nil + } - joinedDisplayEvent, err := outbox.Build(current.RoomID, "RoomUserJoined", current.Version, now, roomUserJoinedEventFromCommand(cmd, firstNonEmpty(existing.Role, cmd.Role), current.VisibleRegionID)) + // 普通重复 JoinRoom 表示用户重新打开房间页或重连:业务 presence 只刷新 + // last_seen,但房间 UI 仍需要一条入房飘窗。该事件只进 direct IM,不进入 durable + // outbox,避免统计服务把同一在线用户重复计为进房活跃。 + joinedDisplayEvent, err := outbox.Build(current.RoomID, "RoomUserJoined", current.Version, now, roomUserJoinedEventFromCommand(cmd, firstNonEmpty(existing.Role, cmd.Role), current.VisibleRegionID, joinVIP)) if err != nil { return mutationResult{}, nil, err } @@ -107,9 +130,18 @@ func (s *Service) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*r LastSeenAtMS: now.UnixMilli(), } current.Version++ + if cmd.PresenceRecovery { + // stale worker 只清理了 room-service 的业务 presence,客户端 IM/TRTC 会话仍在。 + // 这里只补回 Room Cell 状态并返回快照;不创建 sync IM、direct IM 或 durable + // outbox,否则全房会重复看到进房横幅/座驾,统计也会把恢复误当新的 RoomUserJoined。 + snapshot := current.ToProto() + result.snapshot = snapshot + result.user = findProtoUser(snapshot, cmd.ActorUserID()) + return result, nil, nil + } // JoinRoom 成功需要 outbox 事件,腾讯云 IM 房间系统消息由 worker 异步投递。 - joinedEvent, err := outbox.Build(current.RoomID, "RoomUserJoined", current.Version, now, roomUserJoinedEventFromCommand(cmd, joinRole, current.VisibleRegionID)) + joinedEvent, err := outbox.Build(current.RoomID, "RoomUserJoined", current.Version, now, roomUserJoinedEventFromCommand(cmd, joinRole, current.VisibleRegionID, joinVIP)) if err != nil { return mutationResult{}, nil, err } @@ -127,7 +159,7 @@ func (s *Service) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*r RoomVersion: current.Version, // syncEvent 与 outbox 事件保持同一份快照,避免低延迟路径和补偿路径 payload 不一致。 EntryVehicle: imEntryVehicleFromCommand(cmd.EntryVehicle), - Attributes: roomUserJoinedIMAttributes(joinRole, cmd.ActorDisplayProfile), + Attributes: roomUserJoinedIMAttributes(joinRole, cmd.ActorDisplayProfile, joinVIP), } return result, []outbox.Record{joinedEvent}, nil }) @@ -137,9 +169,10 @@ func (s *Service) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*r s.projectRoomVisitBestEffort(ctx, cmd.ActorUserID(), cmd.RoomID()) return &roomv1.JoinRoomResponse{ - Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()), - User: result.user, - Room: joinRoomResponseSnapshot(result.snapshot, req.GetResponseMode()), + Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()), + User: result.user, + Room: joinRoomResponseSnapshot(result.snapshot, req.GetResponseMode()), + EffectiveVip: joinVIP.roomSnapshot(), }, nil } @@ -185,35 +218,43 @@ func userDisplayProfileFromProto(item *roomv1.RoomUserDisplayProfile) command.Us } } -func roomUserJoinedEventFromCommand(cmd command.JoinRoom, role string, visibleRegionID int64) *roomeventsv1.RoomUserJoined { +func roomUserJoinedEventFromCommand(cmd command.JoinRoom, role string, visibleRegionID int64, vip effectiveVIPAccess) *roomeventsv1.RoomUserJoined { profile := cmd.ActorDisplayProfile // 事件 body 是 direct IM 和 outbox 补偿的共同来源,展示字段必须和统计字段一起固定在命令提交时刻。 return &roomeventsv1.RoomUserJoined{ - UserId: cmd.ActorUserID(), - Role: role, - VisibleRegionId: visibleRegionID, - CountryId: cmd.ActorCountryID, - RegionId: firstNonZeroInt64(cmd.ActorRegionID, visibleRegionID), - IsRobot: cmd.ActorIsRobot, - EntryVehicle: eventEntryVehicleFromCommand(cmd.EntryVehicle), - Nickname: strings.TrimSpace(profile.Nickname), - Avatar: strings.TrimSpace(profile.Avatar), - DisplayUserId: strings.TrimSpace(profile.DisplayUserID), - PrettyDisplayUserId: strings.TrimSpace(profile.PrettyDisplayUserID), + UserId: cmd.ActorUserID(), + Role: role, + VisibleRegionId: visibleRegionID, + CountryId: cmd.ActorCountryID, + RegionId: firstNonZeroInt64(cmd.ActorRegionID, visibleRegionID), + IsRobot: cmd.ActorIsRobot, + EntryVehicle: eventEntryVehicleFromCommand(cmd.EntryVehicle), + Nickname: strings.TrimSpace(profile.Nickname), + Avatar: strings.TrimSpace(profile.Avatar), + DisplayUserId: strings.TrimSpace(profile.DisplayUserID), + PrettyDisplayUserId: strings.TrimSpace(profile.PrettyDisplayUserID), + VipProgramType: vip.programType, + EffectiveVipLevel: vip.level, + EffectiveVipName: vip.name, + RoomEntryNoticeEnabled: vip.roomEntryNoticeEnabled(), } } -func roomUserJoinedIMAttributes(role string, profile command.UserDisplayProfile) map[string]string { +func roomUserJoinedIMAttributes(role string, profile command.UserDisplayProfile, vip effectiveVIPAccess) map[string]string { values := map[string]string{ - "role": strings.TrimSpace(role), - "nickname": strings.TrimSpace(profile.Nickname), - "avatar": strings.TrimSpace(profile.Avatar), - "display_user_id": strings.TrimSpace(profile.DisplayUserID), - "pretty_display_user_id": strings.TrimSpace(profile.PrettyDisplayUserID), + "role": strings.TrimSpace(role), + "nickname": strings.TrimSpace(profile.Nickname), + "avatar": strings.TrimSpace(profile.Avatar), + "display_user_id": strings.TrimSpace(profile.DisplayUserID), + "pretty_display_user_id": strings.TrimSpace(profile.PrettyDisplayUserID), + "vip_program_type": vip.programType, + "effective_vip_level": int32String(vip.level), + "effective_vip_name": vip.name, + "room_entry_notice_enabled": boolString(vip.roomEntryNoticeEnabled()), } - // 空字段不进 IM payload,避免客户端把空字符串误判为后端明确清空资料。 + // 资料空字段不进 IM payload;VIP level/notice 即使为零值也必须保留,客户端据此明确关闭 VIP 入场表现。 for key, value := range values { - if value == "" { + if value == "" && key != "effective_vip_name" { delete(values, key) } } @@ -319,8 +360,31 @@ func (s *Service) RoomHeartbeat(ctx context.Context, req *roomv1.RoomHeartbeatRe existing, exists := current.OnlineUsers[cmd.ActorUserID()] if !exists { - // 心跳只刷新已存在 presence,不能把已离房用户重新加入房间。 - return mutationResult{}, nil, xerr.New(xerr.NotFound, "user not in room") + // presence 已丢失但客户端仍在按房间节奏心跳,说明发生过 stale 清理或 owner + // 迁移这类服务端单方面清理;不恢复的话该用户会陷入 MicUp 404、RTC token 403 + // 的粘性失败,只能退房重进。这里按"心跳即在房"原地恢复。 + // 安全边界与 JoinRoom 对齐:room active 已在上方校验,活跃 ban(踢人/封禁) + // 已在上方拦截;锁房进入需要密码而心跳不携带密码,非房主不得借心跳绕过锁房, + // 维持原 NotFound 契约让客户端走完整 JoinRoom 补交密码。 + if current.RoomPasswordHash != "" && current.OwnerUserID != cmd.ActorUserID() { + return mutationResult{}, nil, xerr.New(xerr.NotFound, "user not in room") + } + // 与 JoinRoom presence_recovery 相同的静默语义:只补 Room Cell 状态,不发 + // sync IM/direct IM/durable outbox,避免重复入房横幅与统计重复计数。 + // LeaveRoom 后仍在途的迟到心跳会短暂恢复 presence,由 stale worker 在下一个 + // 清理窗口自然移除,期间同样不产生任何对外事件。 + current.OnlineUsers[cmd.ActorUserID()] = &state.RoomUserState{ + UserID: cmd.ActorUserID(), + Role: recoveredPresenceRole(current, cmd.ActorUserID()), + JoinedAtMS: now.UnixMilli(), + LastSeenAtMS: now.UnixMilli(), + } + current.Version++ + snapshot := current.ToProto() + return mutationResult{ + snapshot: snapshot, + user: findProtoUser(snapshot, cmd.ActorUserID()), + }, nil, nil } existing.LastSeenAtMS = now.UnixMilli() @@ -343,6 +407,20 @@ func (s *Service) RoomHeartbeat(ctx context.Context, req *roomv1.RoomHeartbeatRe }, nil } +// recoveredPresenceRole 推导心跳恢复 presence 时的展示角色。房间权限判定始终以 +// OwnerUserID/AdminUsers 权威字段为准(见 actorRole),这里只保证在线列表的房主/ +// 管理员徽标不因恢复而丢失;无法从权威字段证明的身份一律回落 audience,不存在提权。 +func recoveredPresenceRole(current *state.RoomState, userID int64) string { + switch { + case current.OwnerUserID == userID: + return "owner" + case current.AdminUsers[userID]: + return "admin" + default: + return "audience" + } +} + // LeaveRoom 把用户从房间业务态移出。 func (s *Service) LeaveRoom(ctx context.Context, req *roomv1.LeaveRoomRequest) (*roomv1.LeaveRoomResponse, error) { ctx = contextFromMeta(ctx, req.GetMeta()) diff --git a/services/room-service/internal/room/service/presence_heartbeat_recovery_test.go b/services/room-service/internal/room/service/presence_heartbeat_recovery_test.go new file mode 100644 index 00000000..bee84e04 --- /dev/null +++ b/services/room-service/internal/room/service/presence_heartbeat_recovery_test.go @@ -0,0 +1,227 @@ +package service_test + +import ( + "context" + "testing" + "time" + + roomv1 "hyapp.local/api/proto/room/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/room-service/internal/integration" + roomservice "hyapp/services/room-service/internal/room/service" + "hyapp/services/room-service/internal/router" + "hyapp/services/room-service/internal/testutil/mysqltest" +) + +func newHeartbeatRecoveryService(t *testing.T, repository *mysqltest.Repository, nodeID string, clk *fixedRoomRocketClock) *roomservice.Service { + t.Helper() + cfg := roomservice.Config{ + NodeID: nodeID, + LeaseTTL: 10 * time.Second, + RankLimit: 20, + SnapshotEveryN: 1, + } + if clk != nil { + cfg.Clock = clk + } + return roomservice.New(cfg, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher()) +} + +func createHeartbeatRecoveryRoom(t *testing.T, svc *roomservice.Service, roomID string, ownerID int64, shortID string) { + t.Helper() + if _, err := svc.CreateRoom(context.Background(), &roomv1.CreateRoomRequest{ + Meta: roomservice.NewRequestMeta(roomID, ownerID), + SeatCount: 10, + Mode: "voice", + VisibleRegionId: 8101, + RoomName: "Heartbeat Recovery", + RoomAvatar: testRoomCoverURL, + RoomShortId: shortID, + }); err != nil { + t.Fatalf("create heartbeat recovery room fixture failed: %v", err) + } +} + +func TestRoomHeartbeatRecoversClearedPresence(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + svc := newHeartbeatRecoveryService(t, repository, "node-hb-recover-test", nil) + + roomID := "room-hb-recover" + ownerID := int64(7501) + viewerID := int64(7601) + createHeartbeatRecoveryRoom(t, svc, roomID, ownerID, "hb-recover") + if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{ + Meta: roomservice.NewRequestMeta(roomID, viewerID), + Role: "audience", + }); err != nil { + t.Fatalf("join viewer failed: %v", err) + } + // LeaveRoom 与 stale sweep 走同一条 presence 删除路径,等价模拟"服务端单方面清掉 presence"。 + if _, err := svc.LeaveRoom(ctx, &roomv1.LeaveRoomRequest{ + Meta: roomservice.NewRequestMeta(roomID, viewerID), + }); err != nil { + t.Fatalf("leave viewer failed: %v", err) + } + + outboxBefore := repository.CountRowsByRoom("room_outbox", roomID) + heartbeatResp, err := svc.RoomHeartbeat(ctx, &roomv1.RoomHeartbeatRequest{ + Meta: roomservice.NewRequestMeta(roomID, viewerID), + }) + if err != nil { + t.Fatalf("heartbeat must recover cleared presence: %v", err) + } + recovered := findRoomUser(heartbeatResp.GetRoom(), viewerID) + if recovered == nil || recovered.GetRole() != "audience" { + t.Fatalf("recovered presence must be back online as audience: %+v", heartbeatResp.GetRoom()) + } + if outboxAfter := repository.CountRowsByRoom("room_outbox", roomID); outboxAfter != outboxBefore { + // 恢复必须静默:不发 RoomUserJoined 等 durable outbox 事件,避免重复入房横幅与统计重复计数。 + t.Fatalf("silent recovery must not emit outbox events: before=%d after=%d", outboxBefore, outboxAfter) + } + + if _, err := svc.MicUp(ctx, &roomv1.MicUpRequest{ + Meta: roomservice.NewRequestMeta(roomID, viewerID), + SeatNo: 1, + }); err != nil { + t.Fatalf("mic up must succeed right after heartbeat recovery: %v", err) + } +} + +func TestRoomHeartbeatKeepsRejectingKickedUser(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + svc := newHeartbeatRecoveryService(t, repository, "node-hb-kick-guard-test", nil) + + roomID := "room-hb-kick-guard" + ownerID := int64(7701) + viewerID := int64(7801) + createHeartbeatRecoveryRoom(t, svc, roomID, ownerID, "hb-kick-guard") + if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{ + Meta: roomservice.NewRequestMeta(roomID, viewerID), + Role: "audience", + }); err != nil { + t.Fatalf("join viewer failed: %v", err) + } + if _, err := svc.KickUser(ctx, &roomv1.KickUserRequest{ + Meta: roomservice.NewRequestMeta(roomID, ownerID), + TargetUserId: viewerID, + }); err != nil { + t.Fatalf("kick viewer failed: %v", err) + } + + _, err := svc.RoomHeartbeat(ctx, &roomv1.RoomHeartbeatRequest{ + Meta: roomservice.NewRequestMeta(roomID, viewerID), + }) + if err == nil || xerr.CodeOf(err) != xerr.PermissionDenied { + t.Fatalf("kicked user heartbeat must stay PermissionDenied, got: %v", err) + } + + presenceResp, err := svc.VerifyRoomPresence(ctx, &roomv1.VerifyRoomPresenceRequest{ + AppCode: appcode.Default, + RoomId: roomID, + UserId: viewerID, + }) + if err != nil { + t.Fatalf("verify presence after rejected heartbeat failed: %v", err) + } + if presenceResp.GetPresent() || presenceResp.GetReason() != "user_banned" { + t.Fatalf("kicked user must not regain presence through heartbeat: %+v", presenceResp) + } +} + +func TestRoomHeartbeatRecoveryAfterKickExpires(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + clk := &fixedRoomRocketClock{now: time.Date(2026, 7, 13, 8, 0, 0, 0, time.UTC)} + svc := newHeartbeatRecoveryService(t, repository, "node-hb-kick-expire-test", clk) + + roomID := "room-hb-kick-expire" + ownerID := int64(7901) + viewerID := int64(8001) + createHeartbeatRecoveryRoom(t, svc, roomID, ownerID, "hb-kick-expire") + if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{ + Meta: roomservice.NewRequestMeta(roomID, viewerID), + Role: "audience", + }); err != nil { + t.Fatalf("join viewer failed: %v", err) + } + if _, err := svc.KickUser(ctx, &roomv1.KickUserRequest{ + Meta: roomservice.NewRequestMeta(roomID, ownerID), + TargetUserId: viewerID, + DurationMs: 1000, + }); err != nil { + t.Fatalf("duration kick failed: %v", err) + } + if _, err := svc.RoomHeartbeat(ctx, &roomv1.RoomHeartbeatRequest{ + Meta: roomservice.NewRequestMeta(roomID, viewerID), + }); err == nil || xerr.CodeOf(err) != xerr.PermissionDenied { + t.Fatalf("heartbeat inside ban window must stay PermissionDenied, got: %v", err) + } + + clk.now = clk.now.Add(2 * time.Second) + heartbeatResp, err := svc.RoomHeartbeat(ctx, &roomv1.RoomHeartbeatRequest{ + Meta: roomservice.NewRequestMeta(roomID, viewerID), + }) + if err != nil { + t.Fatalf("heartbeat after ban expiry must recover presence: %v", err) + } + if findRoomUser(heartbeatResp.GetRoom(), viewerID) == nil { + t.Fatalf("expired ban user must be back online: %+v", heartbeatResp.GetRoom()) + } +} + +func TestRoomHeartbeatDoesNotBypassRoomPassword(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + svc := newHeartbeatRecoveryService(t, repository, "node-hb-password-test", nil) + + roomID := "room-hb-password" + ownerID := int64(8101) + viewerID := int64(8201) + createHeartbeatRecoveryRoom(t, svc, roomID, ownerID, "hb-password") + if _, err := svc.SetRoomPassword(ctx, &roomv1.SetRoomPasswordRequest{ + Meta: roomservice.NewRequestMeta(roomID, ownerID), + Locked: true, + Password: "4321", + }); err != nil { + t.Fatalf("set room password failed: %v", err) + } + if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{ + Meta: roomservice.NewRequestMeta(roomID, viewerID), + Role: "audience", + Password: "4321", + }); err != nil { + t.Fatalf("join locked room with password failed: %v", err) + } + if _, err := svc.LeaveRoom(ctx, &roomv1.LeaveRoomRequest{ + Meta: roomservice.NewRequestMeta(roomID, viewerID), + }); err != nil { + t.Fatalf("leave viewer failed: %v", err) + } + + // 心跳不携带密码,非房主不得借心跳绕过锁房,必须维持原 NotFound 契约走完整 JoinRoom。 + if _, err := svc.RoomHeartbeat(ctx, &roomv1.RoomHeartbeatRequest{ + Meta: roomservice.NewRequestMeta(roomID, viewerID), + }); err == nil || xerr.CodeOf(err) != xerr.NotFound { + t.Fatalf("locked room heartbeat recovery must stay NotFound for non-owner, got: %v", err) + } + + // 房主不受锁房密码约束(与 JoinRoom 一致),恢复后角色收敛为 owner。 + if _, err := svc.LeaveRoom(ctx, &roomv1.LeaveRoomRequest{ + Meta: roomservice.NewRequestMeta(roomID, ownerID), + }); err != nil { + t.Fatalf("leave owner failed: %v", err) + } + heartbeatResp, err := svc.RoomHeartbeat(ctx, &roomv1.RoomHeartbeatRequest{ + Meta: roomservice.NewRequestMeta(roomID, ownerID), + }) + if err != nil { + t.Fatalf("owner heartbeat recovery in locked room failed: %v", err) + } + owner := findRoomUser(heartbeatResp.GetRoom(), ownerID) + if owner == nil || owner.GetRole() != "owner" { + t.Fatalf("owner recovery must keep owner role: %+v", heartbeatResp.GetRoom()) + } +} diff --git a/services/room-service/internal/room/service/presence_replay_internal_test.go b/services/room-service/internal/room/service/presence_replay_internal_test.go new file mode 100644 index 00000000..12219461 --- /dev/null +++ b/services/room-service/internal/room/service/presence_replay_internal_test.go @@ -0,0 +1,74 @@ +package service + +import ( + "testing" + + "hyapp/services/room-service/internal/room/command" + "hyapp/services/room-service/internal/room/state" +) + +func replayHeartbeat(t *testing.T, current *state.RoomState, userID int64, sentAtMS int64) { + t.Helper() + cmd := &command.RoomHeartbeat{Base: command.Base{ + AppCode: "lalu", + RequestID: "req-replay-heartbeat", + CommandID: "cmd-replay-heartbeat", + ActorID: userID, + Room: current.RoomID, + SentAtMS: sentAtMS, + }} + if err := replay(current, cmd, sentAtMS); err != nil { + t.Fatalf("replay heartbeat for user %d failed: %v", userID, err) + } +} + +// 线上心跳会静默恢复丢失的 presence 后才提交命令日志;回放必须重现恢复(含 Version++), +// 否则恢复出的 OnlineUsers/版本与快照谱系分叉。 +func TestReplayRoomHeartbeatRecreatesMissingPresence(t *testing.T) { + ownerID := int64(11) + adminID := int64(22) + viewerID := int64(33) + current := state.NewRoomState("room-replay-heartbeat", ownerID, 4, "voice") + current.AdminUsers[adminID] = true + baseVersion := current.Version + + replayHeartbeat(t, current, viewerID, 1_000) + viewer, exists := current.OnlineUsers[viewerID] + if !exists || viewer.Role != "audience" || viewer.JoinedAtMS != 1_000 || viewer.LastSeenAtMS != 1_000 { + t.Fatalf("replayed heal must recreate audience presence from SentAtMS: %+v", viewer) + } + if current.Version != baseVersion+1 { + t.Fatalf("replayed heal must advance version exactly once: base=%d now=%d", baseVersion, current.Version) + } + + // 角色从权威字段推导:owner/admin 恢复后徽标不丢失。 + replayHeartbeat(t, current, ownerID, 2_000) + if owner := current.OnlineUsers[ownerID]; owner == nil || owner.Role != "owner" { + t.Fatalf("replayed heal must restore owner role: %+v", owner) + } + replayHeartbeat(t, current, adminID, 3_000) + if admin := current.OnlineUsers[adminID]; admin == nil || admin.Role != "admin" { + t.Fatalf("replayed heal must restore admin role: %+v", admin) + } +} + +func TestReplayRoomHeartbeatRefreshesExistingPresence(t *testing.T) { + viewerID := int64(44) + current := state.NewRoomState("room-replay-heartbeat-refresh", 11, 4, "voice") + current.OnlineUsers[viewerID] = &state.RoomUserState{ + UserID: viewerID, + Role: "audience", + JoinedAtMS: 500, + LastSeenAtMS: 500, + } + baseVersion := current.Version + + replayHeartbeat(t, current, viewerID, 9_000) + viewer := current.OnlineUsers[viewerID] + if viewer.LastSeenAtMS != 9_000 || viewer.JoinedAtMS != 500 { + t.Fatalf("replayed refresh must only update last_seen: %+v", viewer) + } + if current.Version != baseVersion+1 { + t.Fatalf("replayed refresh must advance version exactly once: base=%d now=%d", baseVersion, current.Version) + } +} diff --git a/services/room-service/internal/room/service/recovery.go b/services/room-service/internal/room/service/recovery.go index b39dbddc..2911b773 100644 --- a/services/room-service/internal/room/service/recovery.go +++ b/services/room-service/internal/room/service/recovery.go @@ -3,6 +3,7 @@ package service import ( "context" "fmt" + "strings" "google.golang.org/protobuf/proto" roomv1 "hyapp.local/api/proto/room/v1" @@ -14,6 +15,21 @@ import ( "hyapp/services/room-service/internal/router" ) +// roomOwnedByOtherNodeMsgPrefix 是跨节点执行权冲突的稳定错误文案前缀; +// isRoomOwnedByOtherNodeError 依赖它把这类短暂冲突和业务终态 Conflict 区分开。 +const roomOwnedByOtherNodeMsgPrefix = "room is owned by " + +// roomOwnedByOtherNodeError 表示房间执行权由其他节点的有效 lease 持有。 +func roomOwnedByOtherNodeError(nodeID string) error { + return xerr.New(xerr.Conflict, roomOwnedByOtherNodeMsgPrefix+nodeID) +} + +// isRoomOwnedByOtherNodeError 判断错误是否为跨节点执行权冲突。 +// 它只在 lease TTL 窗口内成立,不代表业务不可重试;补偿判定不能把它当作终态。 +func isRoomOwnedByOtherNodeError(err error) bool { + return xerr.CodeOf(err) == xerr.Conflict && strings.HasPrefix(xerr.MessageOf(err), roomOwnedByOtherNodeMsgPrefix) +} + // ensureCell 确保当前节点拥有房间执行权,并在本地缺少 Cell 时从 MySQL 恢复。 func (s *Service) ensureCell(ctx context.Context, roomID string) (*cell.RoomCell, router.Lease, error) { // 每次写命令先刷新或接管 lease,确保同一房间同一时刻只有一个执行节点。 @@ -24,7 +40,7 @@ func (s *Service) ensureCell(ctx context.Context, roomID string) (*cell.RoomCell if lease.NodeID != s.nodeID { // 仍有其他有效 owner 时当前节点不能执行该房间命令。 - return nil, router.Lease{}, xerr.New(xerr.Conflict, fmt.Sprintf("room is owned by %s", lease.NodeID)) + return nil, router.Lease{}, roomOwnedByOtherNodeError(lease.NodeID) } if roomCell := s.loadCellForLease(ctx, roomID, lease); roomCell != nil { @@ -171,8 +187,18 @@ func replay(current *state.RoomState, cmd command.Command, committedAtMS int64) case *command.RoomHeartbeat: existing, exists := current.OnlineUsers[typed.ActorUserID()] if !exists { - // 心跳和离房可能在同一房间版本附近并发落库;如果离房先被重放,随后到达的旧心跳 - // 不能重新创建 presence,也不能让整个 Room Cell 恢复失败。 + // 线上心跳对丢失的 presence 会执行静默恢复后才提交命令日志;回放遇到 + // "用户不存在"的已提交心跳,就是当时恢复成功的事实,必须重现恢复 + // (含 Version++),否则恢复出的 OnlineUsers/版本会与快照谱系分叉。 + // 守卫(room active/ban/锁房)在线上提交前已把关,被拒的心跳不会落库, + // 回放无需也不能重复判断——回放看到的 BanUsers 时点可能晚于命令时点。 + current.OnlineUsers[typed.ActorUserID()] = &state.RoomUserState{ + UserID: typed.ActorUserID(), + Role: recoveredPresenceRole(current, typed.ActorUserID()), + JoinedAtMS: typed.SentAtMS, + LastSeenAtMS: typed.SentAtMS, + } + current.Version++ return nil } // 心跳只刷新 last_seen,不产生用户进房事件。 diff --git a/services/room-service/internal/room/service/repository.go b/services/room-service/internal/room/service/repository.go index 4770f424..de2423e5 100644 --- a/services/room-service/internal/room/service/repository.go +++ b/services/room-service/internal/room/service/repository.go @@ -3,6 +3,7 @@ package service import ( "context" + "errors" "hyapp/services/room-service/internal/room/outbox" ) @@ -43,6 +44,8 @@ type CommandRecord struct { CommandType string // Payload 是命令 JSON 序列化结果。 Payload []byte + // ResultPayload 是首次成功时固化的 typed 响应;恢复仍使用 Payload,幂等重试只能读取该结果,不能重新结算。 + ResultPayload []byte // Replayable 标记该命令是否参与恢复回放。 Replayable bool // OwnerNodeID 是提交命令时持有房间 lease 的 room-service 节点。 @@ -80,6 +83,62 @@ type MutationCommit struct { RoomUserContributionPeriodStats []RoomUserContributionPeriodStatIncrement // RoomWeeklyContribution 是后台房间贡献列的 UTC 周期读模型增量;它和命令日志同事务提交,避免送礼成功后榜单缺分。 RoomWeeklyContribution *RoomWeeklyContributionIncrement + // GiftOperation 非 nil 时必须在同一事务内把 saga 推进到 committed,并保存与 command log 完全一致的结果。 + GiftOperation *GiftOperationCommit +} + +var ( + // ErrCommandAlreadyCommitted 表示同一 app/room/command_id 已经提交;调用方必须重放旧结果,不能继续提交副作用。 + ErrCommandAlreadyCommitted = errors.New("room command already committed") +) + +const ( + GiftOperationStatusPending = "pending" + GiftOperationStatusDebited = "debited" + GiftOperationStatusDrawn = "drawn" + GiftOperationStatusCommitted = "committed" + GiftOperationStatusCompensated = "compensated" +) + +// GiftOperation 是 SendGift 跨钱包、抽奖和 Room Cell 提交的持久化 saga。 +// request_payload 用于服务端恢复,command_payload 用于同 command_id 载荷冲突判断;两者不能互换。 +type GiftOperation struct { + AppCode string + RoomID string + CommandID string + ActorUserID int64 + CommandPayload []byte + RequestPayload []byte + Status string + BillingPayload []byte + LuckyPayload []byte + ResultPayload []byte + LastError string + RetryCount int + NextRetryAtMS int64 + WorkerID string + LockUntilMS int64 + CreatedAtMS int64 + UpdatedAtMS int64 +} + +// GiftOperationCommit 是 Room Cell mutation 事务内的最终 saga 变更。 +type GiftOperationCommit struct { + CommandID string + ResultPayload []byte + UpdatedAtMS int64 +} + +// GiftOperationProgress 保存外部副作用后的稳定回执;同状态重写必须幂等。 +type GiftOperationProgress struct { + RoomID string + CommandID string + Status string + BillingPayload []byte + LuckyPayload []byte + LastError string + NextRetryAtMS int64 + UpdatedAtMS int64 } // RoomUserGiftStatIncrement 表达当前房间内某个送礼用户的礼物价值增量。 @@ -516,9 +575,11 @@ type RoomContributionRankStore interface { type RoomListQuery struct { // AppCode 是列表查询所属 App,通常来自 gateway RequestMeta。 AppCode string - // VisibleRegionID 是服务端解析出的用户区域,客户端不能伪造。 + // VisibleRegionID 是服务端解析出的用户区域,用于未显式筛选时的默认范围。 VisibleRegionID int64 - // AllVisibleRegions 只服务后台白名单用户;为 true 时列表读模型不再按 VisibleRegionID 收敛。 + // FilterRegionID 是 gateway 校验过的客户端区域筛选;非零时覆盖默认 VisibleRegionID。 + FilterRegionID int64 + // AllVisibleRegions 由 gateway 根据 App 策略或白名单注入;为 true 时列表读模型不再按 VisibleRegionID 收敛。 AllVisibleRegions bool // Tab 只允许 hot/new,决定公共发现列表排序字段。 Tab string @@ -528,9 +589,9 @@ type RoomListQuery struct { OwnerUserIDs []int64 // Query 非空时按 room_id 或 title 做包含匹配。 Query string - // CountryCode 非空时只返回房主国家命中的房间;gateway 必须先校验它属于当前用户区域。 + // CountryCode 非空时只返回房主国家命中的房间;gateway 必须先校验它属于 active 区域。 CountryCode string - // ViewerCountryCode 是当前用户国家;公共列表不按它过滤,只用它在区域内把本国家房间排到同状态房间前。 + // ViewerCountryCode 是当前用户国家;公共列表不按它过滤,只用它在 All/区域列表中把本国家房间排到同在线状态的其他房间前。 ViewerCountryCode string // Limit 是实际查询数量,service 层会限制最大值。 Limit int @@ -698,6 +759,25 @@ type Repository interface { RoomContributionRankStore AdminRoomStore RobotRoomStore + GiftOperationStore +} + +// GiftOperationStore 管理送礼 saga。它独立于 command log:command log 只代表房间提交完成,operation 还覆盖扣款后的恢复窗口。 +type GiftOperationStore interface { + EnsureGiftOperation(ctx context.Context, operation GiftOperation) (GiftOperation, bool, error) + // ReactivateCompensatedGiftOperation is a CAS executed only after the service has compared the + // stored and current idempotency payloads. EnsureGiftOperation itself must never make an unverified + // historical request claimable by the recovery worker. + ReactivateCompensatedGiftOperation(ctx context.Context, roomID string, commandID string, updatedAtMS int64) (GiftOperation, bool, error) + GetGiftOperation(ctx context.Context, roomID string, commandID string) (GiftOperation, bool, error) + SaveGiftOperationProgress(ctx context.Context, progress GiftOperationProgress) error + ClaimRecoverableGiftOperations(ctx context.Context, workerID string, limit int, nowMS int64, lockUntilMS int64) ([]GiftOperation, error) + // preserveLease is true only when a recovery attempt's total deadline fired after its Cell task was + // submitted. In that case Room Cell may still be using the detached post-debit commit budget, so the + // claim must remain until lock_until_ms instead of being exposed to a second worker immediately. + MarkGiftOperationRetry(ctx context.Context, roomID string, commandID string, lastError string, nextRetryAtMS int64, updatedAtMS int64, preserveLease bool) error + MarkGiftOperationCompensated(ctx context.Context, roomID string, commandID string, lastError string, updatedAtMS int64) error + HasRecoverableGiftOperations(ctx context.Context, roomID string) (bool, error) } // RoomOwnerProjectionStore 管理房主低频资料投影;只改当前读模型,不写 command log。 @@ -728,6 +808,9 @@ type RoomMetaStore interface { SaveRoomMeta(ctx context.Context, meta RoomMeta) error // GetRoomMeta 读取房间基础元数据,恢复无内存 Cell 的房间依赖它。 GetRoomMeta(ctx context.Context, roomID string) (RoomMeta, bool, error) + // GetRoomMetaByGlobalID 只供已验签的 RTC 等外部回调解析房间租户。 + // room_id 由全局 ID 生成器保证跨 App 唯一;普通业务查询仍必须使用 app_code + room_id。 + GetRoomMetaByGlobalID(ctx context.Context, roomID string) (RoomMeta, bool, error) // GetRoomMetaByShortID 用客户端展示短房号解析内部 room_id;命令链路必须先 canonicalize 后再取 lease 和写 command log。 GetRoomMetaByShortID(ctx context.Context, roomShortID string) (RoomMeta, bool, error) // GetRoomMetaByOwner 查询 owner 已创建的房间;产品规则要求一个用户只能创建一个房间。 @@ -774,7 +857,7 @@ type RoomConfigStore interface { // OutboxStore 管理 room_outbox 的补偿投递生命周期。 type OutboxStore interface { - // SaveOutbox 写入房间外事件,必须和成功命令保持同一提交语义。 + // SaveOutbox 写入房间外事件。Room Cell 事件必须和成功命令同事务提交;外部 owner 派生展示则以 source event_id 幂等单独提交。 SaveOutbox(ctx context.Context, records []outbox.Record) error // ListPendingOutbox 扫描待补偿投递事件。 ListPendingOutbox(ctx context.Context, limit int) ([]outbox.Record, error) diff --git a/services/room-service/internal/room/service/room_app_query.go b/services/room-service/internal/room/service/room_app_query.go new file mode 100644 index 00000000..fcae31b9 --- /dev/null +++ b/services/room-service/internal/room/service/room_app_query.go @@ -0,0 +1,27 @@ +package service + +import ( + "context" + "strings" + + roomv1 "hyapp.local/api/proto/room/v1" + "hyapp/pkg/roomid" + "hyapp/pkg/xerr" +) + +// ResolveRoomAppCode 让已验签的腾讯 RTC 回调从房间事实解析租户。 +// room-service 是房间归属 owner;gateway 不应信任 URL 中可伪造、也容易写死的 app_code。 +func (s *Service) ResolveRoomAppCode(ctx context.Context, req *roomv1.ResolveRoomAppCodeRequest) (*roomv1.ResolveRoomAppCodeResponse, error) { + roomID := strings.TrimSpace(req.GetRoomId()) + if !roomid.ValidStringID(roomID) { + return nil, xerr.New(xerr.InvalidArgument, "room_id is invalid") + } + meta, exists, err := s.repository.GetRoomMetaByGlobalID(ctx, roomID) + if err != nil { + return nil, err + } + if !exists || strings.TrimSpace(meta.AppCode) == "" { + return nil, xerr.New(xerr.NotFound, "room not found") + } + return &roomv1.ResolveRoomAppCodeResponse{AppCode: meta.AppCode}, nil +} diff --git a/services/room-service/internal/room/service/room_list_cache.go b/services/room-service/internal/room/service/room_list_cache.go index 6094afdb..70ef6121 100644 --- a/services/room-service/internal/room/service/room_list_cache.go +++ b/services/room-service/internal/room/service/room_list_cache.go @@ -13,8 +13,10 @@ import ( ) const ( - roomListCacheReadyValue = "1" + // v2 表示独立 namespace 已同时重建区域桶和全区桶;ready 之前一律回退 MySQL。 + roomListCacheReadyValue = "2" roomListCacheCountryAll = "_" + roomListCacheAllRegionsScope = "all" roomListCacheBucketOnline = "online" roomListCacheBucketEmpty = "empty" roomListCacheDefaultScanSize = 128 @@ -43,6 +45,8 @@ type roomListCachePinRow struct { type roomListCacheCandidate struct { Entry RoomListEntry SortValue int64 + // PinType 只用于同一房间命中多种置顶时复刻 MySQL CASE 的 global > region > country 选择优先级。 + PinType string } // NewRedisRoomListCacheStore 创建公共发现页 Redis 展示读模型。 @@ -65,10 +69,16 @@ func (s *RedisRoomListCacheStore) ListRoomListEntries(ctx context.Context, query if query.NowMS <= 0 { return nil, false, nil } - ready, err := s.client.Exists(ctx, roomListCacheReadyKey(query.AppCode)).Result() - if err != nil || ready == 0 { + ready, err := s.client.Get(ctx, roomListCacheReadyKey(query.AppCode)).Result() + if err == redis.Nil { + return nil, false, nil + } + if err != nil { return nil, false, err } + if ready != roomListCacheReadyValue { + return nil, false, nil + } scanSize := roomListCacheScanSize(query.Limit) candidates := make(map[string]roomListCacheCandidate, scanSize) @@ -100,7 +110,7 @@ func (s *RedisRoomListCacheStore) ListRoomListEntries(ctx context.Context, query card.PinListRank = rank card.PinWeight = row.Pin.Weight card.PinnedUntilMS = row.Pin.ExpiresAtMS - roomListCacheKeepBest(candidates, roomListCacheCandidate{Entry: card, SortValue: roomListCacheSortValue(query.Tab, card)}) + roomListCacheKeepBest(candidates, roomListCacheCandidate{Entry: card, SortValue: roomListCacheSortValue(query.Tab, card), PinType: row.Pin.PinType}) } normalIDs, normalTruncated, err := s.listRoomListCacheNormalRoomIDs(ctx, query, scanSize) @@ -241,16 +251,18 @@ func (s *RedisRoomListCacheStore) UpsertRoomPin(ctx context.Context, pin RoomLis if err != nil { return err } - ref := roomListCachePinRef(app, pin) - rawRefs, err := json.Marshal([]roomListCacheMemberRef{ref}) + refs := roomListCachePinRefs(app, pin) + rawRefs, err := json.Marshal(refs) if err != nil { return err } pipe := s.client.Pipeline() - pipe.SAdd(ctx, roomListCacheKnownKeysKey(app), ref.Key) pipe.HSet(ctx, roomListCachePinPayloadKey(app), strconv.FormatInt(pin.ID, 10), payload) pipe.HSet(ctx, roomListCachePinIndexKey(app), strconv.FormatInt(pin.ID, 10), rawRefs) - pipe.ZAdd(ctx, ref.Key, redis.Z{Score: -float64(pin.Weight), Member: ref.Member}) + for _, ref := range refs { + pipe.SAdd(ctx, roomListCacheKnownKeysKey(app), ref.Key) + pipe.ZAdd(ctx, ref.Key, redis.Z{Score: -float64(pin.Weight), Member: ref.Member}) + } _, err = pipe.Exec(ctx) return err } @@ -350,12 +362,18 @@ func (s *RedisRoomListCacheStore) removeRoomListPinIndexes(ctx context.Context, } func (s *RedisRoomListCacheStore) listRoomListCachePins(ctx context.Context, query RoomListQuery, scanSize int) ([]roomListCachePinRow, bool, error) { - keys := []string{ - roomListCachePinKey(query.AppCode, roomPinTypeGlobal, 0), - roomListCachePinKey(query.AppCode, roomPinTypeRegion, normalizeVisibleRegionID(query.VisibleRegionID)), - } - if roomListCacheSortCountry(query) != "" { - keys = append(keys, roomListCachePinKey(query.AppCode, roomPinTypeCountry, normalizeVisibleRegionID(query.VisibleRegionID))) + keys := []string{roomListCachePinKey(query.AppCode, roomPinTypeGlobal, 0)} + if query.AllVisibleRegions { + // All 列表不能复用 region=0;0 是真实 GLOBAL 桶,独立 all key 才能合并每个区域的置顶事实。 + keys = append(keys, roomListCacheAllRegionsPinKey(query.AppCode, roomPinTypeRegion)) + if roomListCacheSortCountry(query) != "" { + keys = append(keys, roomListCacheAllRegionsPinKey(query.AppCode, roomPinTypeCountry)) + } + } else { + keys = append(keys, roomListCachePinKey(query.AppCode, roomPinTypeRegion, normalizeVisibleRegionID(query.VisibleRegionID))) + if roomListCacheSortCountry(query) != "" { + keys = append(keys, roomListCachePinKey(query.AppCode, roomPinTypeCountry, normalizeVisibleRegionID(query.VisibleRegionID))) + } } pinIDs := make([]string, 0, len(keys)*scanSize) @@ -487,8 +505,36 @@ func uniqueRoomListCacheIDs(values []string) []string { func roomListCacheKeepBest(candidates map[string]roomListCacheCandidate, candidate roomListCacheCandidate) { current, exists := candidates[candidate.Entry.RoomID] - if !exists || roomListCacheCandidateBefore(candidate, current) { + if !exists { candidates[candidate.Entry.RoomID] = candidate + return + } + if candidate.Entry.IsPinned && current.Entry.IsPinned { + candidatePrecedence := roomListCachePinTypePrecedence(candidate.PinType) + currentPrecedence := roomListCachePinTypePrecedence(current.PinType) + if candidatePrecedence != currentPrecedence { + // MySQL 对同房间先选 global,其次 region,最后 country;不允许次级类型用更高 weight 反超。 + if candidatePrecedence < currentPrecedence { + candidates[candidate.Entry.RoomID] = candidate + } + return + } + } + if roomListCacheCandidateBefore(candidate, current) { + candidates[candidate.Entry.RoomID] = candidate + } +} + +func roomListCachePinTypePrecedence(pinType string) int { + switch roomListCacheNormalizePinType(pinType) { + case roomPinTypeGlobal: + return 0 + case roomPinTypeRegion: + return 1 + case roomPinTypeCountry: + return 2 + default: + return 3 } } @@ -535,7 +581,7 @@ func roomListCacheCardMatches(query RoomListQuery, entry RoomListEntry) bool { if appcode.Normalize(entry.AppCode) != appcode.Normalize(query.AppCode) || entry.Status != "active" { return false } - if normalizeVisibleRegionID(entry.VisibleRegionID) != normalizeVisibleRegionID(query.VisibleRegionID) { + if !query.AllVisibleRegions && normalizeVisibleRegionID(entry.VisibleRegionID) != normalizeVisibleRegionID(query.VisibleRegionID) { return false } if country := normalizeRoomCountryCode(query.CountryCode); country != "" && normalizeRoomCountryCode(entry.OwnerCountryCode) != country { @@ -549,9 +595,16 @@ func roomListCachePinApplies(query RoomListQuery, pin RoomListPinEntry, entry Ro case roomPinTypeGlobal: return pin.VisibleRegionID == 0 case roomPinTypeRegion: + if query.AllVisibleRegions { + return normalizeVisibleRegionID(pin.VisibleRegionID) == normalizeVisibleRegionID(entry.VisibleRegionID) + } return normalizeVisibleRegionID(pin.VisibleRegionID) == normalizeVisibleRegionID(query.VisibleRegionID) case roomPinTypeCountry: - return normalizeVisibleRegionID(pin.VisibleRegionID) == normalizeVisibleRegionID(query.VisibleRegionID) && normalizeRoomCountryCode(entry.OwnerCountryCode) == roomListCacheSortCountry(query) + pinRegionID := normalizeVisibleRegionID(query.VisibleRegionID) + if query.AllVisibleRegions { + pinRegionID = normalizeVisibleRegionID(entry.VisibleRegionID) + } + return normalizeVisibleRegionID(pin.VisibleRegionID) == pinRegionID && normalizeRoomCountryCode(entry.OwnerCountryCode) == roomListCacheSortCountry(query) default: return false } @@ -611,16 +664,22 @@ func roomListCacheEntryRefs(app string, entry RoomListEntry) []roomListCacheMemb if entry.OnlineCount > 0 { bucket = roomListCacheBucketOnline } - refs := make([]roomListCacheMemberRef, 0, 4) + refs := make([]roomListCacheMemberRef, 0, 8) for _, tab := range []string{roomListTabHot, roomListTabNew} { refs = append(refs, roomListCacheMemberRef{ Key: roomListCacheBaseKey(app, tab, entry.VisibleRegionID, roomListCacheCountryAll, bucket), Member: entry.RoomID, + }, roomListCacheMemberRef{ + Key: roomListCacheAllRegionsBaseKey(app, tab, roomListCacheCountryAll, bucket), + Member: entry.RoomID, }) if country := normalizeRoomCountryCode(entry.OwnerCountryCode); country != "" { refs = append(refs, roomListCacheMemberRef{ Key: roomListCacheBaseKey(app, tab, entry.VisibleRegionID, country, bucket), Member: entry.RoomID, + }, roomListCacheMemberRef{ + Key: roomListCacheAllRegionsBaseKey(app, tab, country, bucket), + Member: entry.RoomID, }) } } @@ -638,31 +697,44 @@ func roomListCacheNormalKeys(query RoomListQuery) []string { app := appcode.Normalize(query.AppCode) regionID := normalizeVisibleRegionID(query.VisibleRegionID) sortCountry := roomListCacheSortCountry(query) + key := func(country string, bucket string) string { + if query.AllVisibleRegions { + return roomListCacheAllRegionsBaseKey(app, query.Tab, country, bucket) + } + return roomListCacheBaseKey(app, query.Tab, regionID, country, bucket) + } if filterCountry := normalizeRoomCountryCode(query.CountryCode); filterCountry != "" { return []string{ - roomListCacheBaseKey(app, query.Tab, regionID, filterCountry, roomListCacheBucketOnline), - roomListCacheBaseKey(app, query.Tab, regionID, filterCountry, roomListCacheBucketEmpty), + key(filterCountry, roomListCacheBucketOnline), + key(filterCountry, roomListCacheBucketEmpty), } } if sortCountry != "" { return []string{ - roomListCacheBaseKey(app, query.Tab, regionID, sortCountry, roomListCacheBucketOnline), - roomListCacheBaseKey(app, query.Tab, regionID, roomListCacheCountryAll, roomListCacheBucketOnline), - roomListCacheBaseKey(app, query.Tab, regionID, sortCountry, roomListCacheBucketEmpty), - roomListCacheBaseKey(app, query.Tab, regionID, roomListCacheCountryAll, roomListCacheBucketEmpty), + key(sortCountry, roomListCacheBucketOnline), + key(roomListCacheCountryAll, roomListCacheBucketOnline), + key(sortCountry, roomListCacheBucketEmpty), + key(roomListCacheCountryAll, roomListCacheBucketEmpty), } } return []string{ - roomListCacheBaseKey(app, query.Tab, regionID, roomListCacheCountryAll, roomListCacheBucketOnline), - roomListCacheBaseKey(app, query.Tab, regionID, roomListCacheCountryAll, roomListCacheBucketEmpty), + key(roomListCacheCountryAll, roomListCacheBucketOnline), + key(roomListCacheCountryAll, roomListCacheBucketEmpty), } } -func roomListCachePinRef(app string, pin RoomListPinEntry) roomListCacheMemberRef { - return roomListCacheMemberRef{ +func roomListCachePinRefs(app string, pin RoomListPinEntry) []roomListCacheMemberRef { + refs := []roomListCacheMemberRef{{ Key: roomListCachePinKey(app, pin.PinType, pin.VisibleRegionID), Member: roomListCachePinMember(pin), + }} + if pin.PinType == roomPinTypeRegion || pin.PinType == roomPinTypeCountry { + refs = append(refs, roomListCacheMemberRef{ + Key: roomListCacheAllRegionsPinKey(app, pin.PinType), + Member: roomListCachePinMember(pin), + }) } + return refs } func roomListCachePinMember(pin RoomListPinEntry) string { @@ -697,7 +769,8 @@ func roomListCacheNormalizePinType(pinType string) string { } func roomListCachePrefix(app string) string { - return "room:list_cache:" + appcode.Normalize(app) + // v2 使用独立 namespace:滚动发布期间旧进程仍会按 v1 的 4-ref 索引删改,绝不能触碰新进程的 8-ref All 索引。 + return "room:list_cache:v2:" + appcode.Normalize(app) } func roomListCacheReadyKey(app string) string { @@ -732,9 +805,22 @@ func roomListCacheBaseKey(app string, tab string, regionID int64, country string return fmt.Sprintf("%s:base:%s:%d:%s:%s", roomListCachePrefix(app), tab, normalizeVisibleRegionID(regionID), country, bucket) } +func roomListCacheAllRegionsBaseKey(app string, tab string, country string, bucket string) string { + country = normalizeRoomCountryCode(country) + if country == "" { + country = roomListCacheCountryAll + } + // all 使用文本 scope,不和业务上的 visible_region_id=0 GLOBAL 桶共用任何 key。 + return fmt.Sprintf("%s:base:%s:%s:%s:%s", roomListCachePrefix(app), roomListCacheAllRegionsScope, tab, country, bucket) +} + func roomListCachePinKey(app string, pinType string, regionID int64) string { if pinType == roomPinTypeGlobal { regionID = 0 } return fmt.Sprintf("%s:pin:%s:%d", roomListCachePrefix(app), pinType, normalizeVisibleRegionID(regionID)) } + +func roomListCacheAllRegionsPinKey(app string, pinType string) string { + return fmt.Sprintf("%s:pin:%s:%s", roomListCachePrefix(app), roomListCacheAllRegionsScope, pinType) +} diff --git a/services/room-service/internal/room/service/room_list_cache_test.go b/services/room-service/internal/room/service/room_list_cache_test.go index 3c062549..0d533a75 100644 --- a/services/room-service/internal/room/service/room_list_cache_test.go +++ b/services/room-service/internal/room/service/room_list_cache_test.go @@ -2,6 +2,7 @@ package service import ( "sort" + "strings" "testing" ) @@ -71,6 +72,91 @@ func TestRoomListCacheCountryPinOnlyAppliesToSelectedCountry(t *testing.T) { } } +func TestRoomListCacheAllRegionsUsesDedicatedIndexesWithoutReusingGlobalRegion(t *testing.T) { + query := RoomListQuery{ + AppCode: "huwaa", + VisibleRegionID: 9001, + AllVisibleRegions: true, + ViewerCountryCode: "US", + Tab: roomListTabHot, + } + if !roomListCacheEligible(query) { + t.Fatal("all-region hot/new homepage queries must be cache eligible") + } + legacyPrefix := "room:list_cache:huwaa" + if got := roomListCachePrefix("huwaa"); got == legacyPrefix || !strings.HasPrefix(got, "room:list_cache:v2:") { + t.Fatalf("v2 cache must use a namespace isolated from rolling v1 writers: got=%q legacy=%q", got, legacyPrefix) + } + if got := roomListCacheReadyKey("huwaa"); strings.HasPrefix(got, legacyPrefix+":") { + t.Fatalf("v1 ready=1 must not make the v2 cache readable: %q", got) + } + keys := roomListCacheNormalKeys(query) + if len(keys) != 4 { + t.Fatalf("all-region viewer-country query keys mismatch: %v", keys) + } + for _, key := range keys { + if !strings.Contains(key, ":base:all:hot:") { + t.Fatalf("all-region query must use dedicated all scope, got %q", key) + } + if key == roomListCacheBaseKey("huwaa", roomListTabHot, 0, roomListCacheCountryAll, roomListCacheBucketOnline) { + t.Fatalf("all-region key must not alias visible_region_id=0 GLOBAL bucket: %q", key) + } + } + + entry := roomListCacheTestEntry("other-region", "AE", 1, 100, 0) + entry.AppCode = "huwaa" + entry.VisibleRegionID = 9002 + if !roomListCacheCardMatches(query, entry) { + t.Fatal("all-region query must accept a card from another region") + } + refs := roomListCacheEntryRefs("huwaa", entry) + hasRegionalIndex := false + hasAllIndex := false + for _, ref := range refs { + hasRegionalIndex = hasRegionalIndex || strings.Contains(ref.Key, ":base:hot:9002:") + hasAllIndex = hasAllIndex || strings.Contains(ref.Key, ":base:all:hot:") + } + if !hasRegionalIndex || !hasAllIndex { + t.Fatalf("each cache card must update regional and all-region indexes: %+v", refs) + } + + regionPin := RoomListPinEntry{ID: 1, PinType: roomPinTypeRegion, VisibleRegionID: 9002, RoomID: entry.RoomID} + if !roomListCachePinApplies(query, regionPin, entry) { + t.Fatal("all-region list must retain the room's own regional pin") + } + pinRefs := roomListCachePinRefs("huwaa", regionPin) + if len(pinRefs) != 2 || pinRefs[0].Key == pinRefs[1].Key || !strings.Contains(pinRefs[1].Key, ":pin:all:region") { + t.Fatalf("regional pin must update regional and all-region indexes: %+v", pinRefs) + } +} + +func TestRoomListCachePinTypePrecedenceMatchesMySQLCaseOrder(t *testing.T) { + candidates := make(map[string]roomListCacheCandidate) + roomARegion := roomListCachePinnedTestEntry("room-a", 0, 100, 10000, 1) + roomAGlobal := roomListCachePinnedTestEntry("room-a", 0, 10, 10000, 1) + roomBRegion := roomListCachePinnedTestEntry("room-b", 0, 50, 10000, 1) + + // 先放入高权重 region,再放入低权重 global,确认同房间先选 pin type 而不是 weight。 + roomListCacheKeepBest(candidates, roomListCacheCandidate{Entry: roomARegion, SortValue: roomARegion.SortScore, PinType: roomPinTypeRegion}) + roomListCacheKeepBest(candidates, roomListCacheCandidate{Entry: roomAGlobal, SortValue: roomAGlobal.SortScore, PinType: roomPinTypeGlobal}) + roomListCacheKeepBest(candidates, roomListCacheCandidate{Entry: roomBRegion, SortValue: roomBRegion.SortScore, PinType: roomPinTypeRegion}) + + selectedA := candidates["room-a"] + if selectedA.PinType != roomPinTypeGlobal || selectedA.Entry.PinWeight != 10 { + t.Fatalf("same-room global pin must suppress higher-weight region pin exactly like MySQL CASE: %+v", selectedA) + } + ordered := make([]roomListCacheCandidate, 0, len(candidates)) + for _, candidate := range candidates { + ordered = append(ordered, candidate) + } + sort.SliceStable(ordered, func(i, j int) bool { + return roomListCacheCandidateBefore(ordered[i], ordered[j]) + }) + if len(ordered) != 2 || ordered[0].Entry.RoomID != "room-b" || ordered[1].Entry.RoomID != "room-a" { + t.Fatalf("cache order must match MySQL fallback B(region=50), A(global=10): %+v", ordered) + } +} + func roomListCacheTestEntry(roomID string, country string, online int32, sortScore int64, createdAtMS int64) RoomListEntry { return RoomListEntry{ AppCode: "lalu", diff --git a/services/room-service/internal/room/service/room_rocket_test.go b/services/room-service/internal/room/service/room_rocket_test.go index 105c5865..a4522994 100644 --- a/services/room-service/internal/room/service/room_rocket_test.go +++ b/services/room-service/internal/room/service/room_rocket_test.go @@ -40,6 +40,9 @@ type rocketTestWallet struct { lastBatch *walletv1.BatchDebitGiftRequest balanceResponses []*walletv1.GetBalancesResponse balanceRequests []*walletv1.GetBalancesRequest + vipResponses map[int64]*walletv1.GetMyVipResponse + vipRequests []*walletv1.GetMyVipRequest + vipError error } type recordingRoomDirectIMPublisher struct { @@ -153,6 +156,58 @@ func TestRepeatJoinRoomPublishesEntryBroadcastDirectIMOnly(t *testing.T) { } } +func TestPresenceRecoveryRestoresOnlineStateWithoutJoinEvents(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + now := &fixedRoomRocketClock{now: time.Date(2026, 7, 12, 6, 0, 0, 0, time.UTC)} + directIM := newRecordingRoomDirectIMPublisher() + svc := newRocketTestServiceWithDirectIM(t, repository, &rocketTestWallet{}, now, nil, directIM) + + roomID := "room-presence-recovery-no-entry" + userID := int64(1002) + createRocketRoom(t, ctx, svc, roomID, 1001, 9001) + joinRocketRoom(t, ctx, svc, roomID, userID) + waitForRoomDirectIMCounts(t, directIM, map[string]int{"RoomUserJoined": 1}) + if _, err := svc.LeaveRoom(ctx, &roomv1.LeaveRoomRequest{ + Meta: roomservice.NewRequestMeta(roomID, userID), + }); err != nil { + t.Fatalf("remove stale presence fixture failed: %v", err) + } + + joinedBefore := outboxEventCounts(t, ctx, repository)["RoomUserJoined"] + directJoinedBefore := directIM.counts()["RoomUserJoined"] + resp, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{ + Meta: roomservice.NewRequestMeta(roomID, userID), + Role: "audience", + PresenceRecovery: true, + }) + if err != nil { + t.Fatalf("presence recovery failed: %v", err) + } + if resp.GetUser().GetUserId() != userID { + t.Fatalf("presence recovery must restore current user: %+v", resp.GetUser()) + } + if got := outboxEventCounts(t, ctx, repository)["RoomUserJoined"]; got != joinedBefore { + t.Fatalf("presence recovery must not publish durable RoomUserJoined: before=%d after=%d", joinedBefore, got) + } + if got := directIM.counts()["RoomUserJoined"]; got != directJoinedBefore { + t.Fatalf("presence recovery must not publish entry direct IM: before=%d after=%d", directJoinedBefore, got) + } + if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{ + Meta: roomservice.NewRequestMeta(roomID, userID), + Role: "audience", + PresenceRecovery: true, + }); err != nil { + t.Fatalf("idempotent presence recovery failed: %v", err) + } + if got := outboxEventCounts(t, ctx, repository)["RoomUserJoined"]; got != joinedBefore { + t.Fatalf("recovery racing with restored presence must not publish durable join: before=%d after=%d", joinedBefore, got) + } + if got := directIM.counts()["RoomUserJoined"]; got != directJoinedBefore { + t.Fatalf("recovery racing with restored presence must not publish entry direct IM: before=%d after=%d", directJoinedBefore, got) + } +} + func TestJoinRoomLiteTrimsResponseSnapshotOnly(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) @@ -989,6 +1044,17 @@ func (w *rocketTestWallet) GrantResourceGroup(_ context.Context, req *walletv1.G }, nil } +func (w *rocketTestWallet) GetMyVip(_ context.Context, req *walletv1.GetMyVipRequest) (*walletv1.GetMyVipResponse, error) { + w.vipRequests = append(w.vipRequests, proto.Clone(req).(*walletv1.GetMyVipRequest)) + if w.vipError != nil { + return nil, w.vipError + } + if response := w.vipResponses[req.GetUserId()]; response != nil { + return proto.Clone(response).(*walletv1.GetMyVipResponse), nil + } + return legacyVIPResponse(req.GetUserId()), nil +} + func (w *rocketTestWallet) GetBalances(_ context.Context, req *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error) { w.balanceRequests = append(w.balanceRequests, req) if len(w.balanceResponses) == 0 { diff --git a/services/room-service/internal/room/service/service.go b/services/room-service/internal/room/service/service.go index f6b3f7f2..c33d81b1 100644 --- a/services/room-service/internal/room/service/service.go +++ b/services/room-service/internal/room/service/service.go @@ -37,7 +37,7 @@ type Config struct { RoomRocketLaunchScheduler integration.RoomRocketLaunchScheduler // RobotDisplayPublisher 专门 best-effort 投递机器人房间展示事件;失败只打日志,不进入 MySQL outbox。 RobotDisplayPublisher integration.OutboxPublisher - // RoomDisplayPublisher 在 Room Cell 提交后直接投递腾讯 IM 展示事件;失败不回滚已提交房间命令。 + // RoomDisplayPublisher 在 Room Cell 提交后直投低延迟展示,也由幸运礼物派生 outbox 补偿投递房间 IM。 RoomDisplayPublisher integration.OutboxPublisher // HumanRobotPoolProvider 自动读取全站机器人账号和用户国家,用于真人房间机器人运行时按国家分组。 HumanRobotPoolProvider integration.HumanRoomRobotPoolProvider @@ -83,10 +83,12 @@ type Service struct { outboxPublisher integration.OutboxPublisher // robotDisplayPublisher 只服务机器人房间展示事件,和主 room_outbox 持久事实隔离。 robotDisplayPublisher integration.OutboxPublisher - // roomDisplayPublisher 只承载客户端可由快照修复的直接 IM 展示事件,不作为业务事实源。 + // roomDisplayPublisher 只承载腾讯 IM 展示,不作为业务事实源;真人幸运飘屏由本地 room_outbox 兜住失败重试。 roomDisplayPublisher integration.OutboxPublisher // outboxWake 提交成功后唤醒主 outbox worker;它只是低延迟提示,事实源仍然是 MySQL room_outbox。 outboxWake chan struct{} + // outboxClaimCursor 让跨 App claim 轮转起点,避免默认 lalu 长期有积压时饿死 fami/huwaa 等租户。 + outboxClaimCursor atomic.Uint64 // humanRobotPoolProvider 运行时自动获取全站机器人池,避免后台手工维护国家机器人池。 humanRobotPoolProvider integration.HumanRoomRobotPoolProvider // roomRocketLaunchScheduler 在 ignited outbox 投递成功后安排 launch_at_ms 唤醒。 @@ -175,6 +177,8 @@ type mutationResult struct { luckyGifts []*roomv1.LuckyGiftDrawResult // batchDisplay 是 batch-send 的发送方本地展示快照;它不替代逐目标 RoomGiftSent 事实。 batchDisplay *roomv1.SendGiftBatchDisplay + // giftV2 是首次提交后持久化并在相同 command_id 重试时原样重放的精简结算结果。 + giftV2 *roomv1.SendGiftResultV2 // suppressDirectIMEventTypes 只影响提交后的低延迟展示 lane,不能改变 durable outbox 事实。 suppressDirectIMEventTypes map[string]bool // commandPayload 允许少数命令把外部依赖的结算快照写入 command log,用于恢复。 diff --git a/services/room-service/internal/room/service/user_region_sync_test.go b/services/room-service/internal/room/service/user_region_sync_test.go index c7ae990c..662c8b3b 100644 --- a/services/room-service/internal/room/service/user_region_sync_test.go +++ b/services/room-service/internal/room/service/user_region_sync_test.go @@ -97,4 +97,41 @@ func TestHandleUserRegionChangedUpdatesOwnerRoomRegionIdempotently(t *testing.T) if len(oldRegionEntries) != 0 { t.Fatalf("old region list must not contain migrated room: %+v", oldRegionEntries) } + + // 同一区域内再次换国家时 visible_region_id 不变,但国家 tab 仍必须从旧国家迁到新国家。 + sameRegionPayload, err := json.Marshal(map[string]any{ + "user_id": ownerID, + "old_country": "BD", + "new_country": "PK", + "old_region_id": 2, + "new_region_id": 2, + "source": "app", + "request_id": "req-user-country-sync-same-region", + "changed_at_ms": int64(1_700_000_790_000), + }) + if err != nil { + t.Fatalf("marshal same-region payload failed: %v", err) + } + if err := svc.HandleUserOutboxMessage(ctx, usermq.UserOutboxMessage{ + AppCode: appcode.Default, + EventID: "UserRegionChanged:app:163212:BD:PK:2:2:1700000790000", + EventType: "UserRegionChanged", + AggregateType: "user", + AggregateID: ownerID, + PayloadJSON: string(sameRegionPayload), + OccurredAtMS: 1_700_000_790_000, + }); err != nil { + t.Fatalf("same-region HandleUserOutboxMessage failed: %v", err) + } + oldCountryEntries, err := repository.ListRoomListEntries(ctx, roomservice.RoomListQuery{VisibleRegionID: 2, CountryCode: "BD", Tab: "new", Limit: 10}) + if err != nil { + t.Fatalf("list old country entries failed: %v", err) + } + newCountryEntries, err := repository.ListRoomListEntries(ctx, roomservice.RoomListQuery{VisibleRegionID: 2, CountryCode: "PK", Tab: "new", Limit: 10}) + if err != nil { + t.Fatalf("list same-region new country entries failed: %v", err) + } + if len(oldCountryEntries) != 0 || len(newCountryEntries) != 1 || newCountryEntries[0].RoomID != roomID || newCountryEntries[0].OwnerCountryCode != "PK" { + t.Fatalf("same-region country projection mismatch: old=%+v new=%+v", oldCountryEntries, newCountryEntries) + } } diff --git a/services/room-service/internal/room/service/vip.go b/services/room-service/internal/room/service/vip.go new file mode 100644 index 00000000..1c37eff5 --- /dev/null +++ b/services/room-service/internal/room/service/vip.go @@ -0,0 +1,141 @@ +package service + +import ( + "context" + "fmt" + "strings" + "time" + + roomv1 "hyapp.local/api/proto/room/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" +) + +const ( + // tieredPrivilegeVIPProgram 是 P1 权益制 VIP 的稳定策略名。App 选择哪套体系由 wallet 配置决定, + // room-service 禁止再按 fami/lalu 写死分支,否则后续 App 无法复用同一房间权限实现。 + tieredPrivilegeVIPProgram = "tiered_privilege_v1" + + vipBenefitCustomRoomBackground = "custom_room_background" + vipBenefitAntiKick = "anti_kick" + vipBenefitAntiMute = "anti_mute" + vipBenefitRoomEntryNotice = "room_entry_notice" + + // VIP 查询会进入进房展示或低频房管动作,必须给同步依赖设置短上限,避免 wallet 抖动长期占住房间请求。 + roomVIPLookupTimeout = time.Second +) + +// effectiveVIPAccess 是 room-service 从 wallet effective state 裁剪出的房间权限快照。 +// paid/trial 合并、等级继承和体验卡过滤都已经由 wallet 完成,这里只做精确 benefit_code membership 判断。 +type effectiveVIPAccess struct { + programType string + level int32 + name string + benefits map[string]struct{} + roomEntryNoticeSettingEnabled bool +} + +func (access effectiveVIPAccess) hasBenefit(code string) bool { + _, exists := access.benefits[normalizeVIPCode(code)] + return exists +} + +func (access effectiveVIPAccess) roomEntryNoticeEnabled() bool { + return access.roomEntryNoticeSettingEnabled && access.hasBenefit(vipBenefitRoomEntryNotice) +} + +func (access effectiveVIPAccess) roomSnapshot() *roomv1.RoomEffectiveVipSnapshot { + return &roomv1.RoomEffectiveVipSnapshot{ + ProgramType: access.programType, + Level: access.level, + Name: access.name, + RoomEntryNoticeEnabled: access.roomEntryNoticeEnabled(), + } +} + +// loadEffectiveVIPAccess 调 wallet GetMyVip 一次并只接受完整 effective state。 +// 空 VIP 是合法结果,但 program_config 不能为空;缺配置时无法判断当前 App 是否应执行 P1 权限,必须按上游失败处理。 +func (s *Service) loadEffectiveVIPAccess(ctx context.Context, requestID string, userID int64) (effectiveVIPAccess, error) { + if s == nil || s.wallet == nil { + return effectiveVIPAccess{}, xerr.New(xerr.Unavailable, "wallet vip client is unavailable") + } + if userID <= 0 { + return effectiveVIPAccess{}, xerr.New(xerr.InvalidArgument, "vip user_id is required") + } + + lookupCtx, cancel := context.WithTimeout(ctx, roomVIPLookupTimeout) + defer cancel() + response, err := s.wallet.GetMyVip(lookupCtx, &walletv1.GetMyVipRequest{ + RequestId: strings.TrimSpace(requestID), + AppCode: appcode.FromContext(ctx), + UserId: userID, + }) + if err != nil { + return effectiveVIPAccess{}, xerr.New(xerr.Unavailable, fmt.Sprintf("get effective vip failed: %v", err)) + } + if response == nil || response.GetState() == nil || response.GetState().GetProgramConfig() == nil { + return effectiveVIPAccess{}, xerr.New(xerr.Unavailable, "wallet vip state is incomplete") + } + + state := response.GetState() + access := effectiveVIPAccess{ + programType: normalizeVIPCode(state.GetProgramConfig().GetProgramType()), + benefits: make(map[string]struct{}, len(state.GetEffectiveBenefits())), + roomEntryNoticeSettingEnabled: true, + } + // 旧 wallet 在滚动升级期间没有 user_settings tag;nil 必须兼容为产品默认开启。 + if settings := state.GetUserSettings(); settings != nil { + access.roomEntryNoticeSettingEnabled = settings.GetRoomEntryNoticeEnabled() + } + if access.programType == "" { + return effectiveVIPAccess{}, xerr.New(xerr.Unavailable, "wallet vip program is missing") + } + if effective := state.GetEffectiveVip(); effective != nil && effective.GetActive() { + access.level = effective.GetLevel() + access.name = strings.TrimSpace(effective.GetName()) + } + for _, benefit := range state.GetEffectiveBenefits() { + if benefit == nil { + continue + } + if code := normalizeVIPCode(benefit.GetBenefitCode()); code != "" { + access.benefits[code] = struct{}{} + } + } + return access, nil +} + +// requireCustomRoomBackgroundBenefit 只对配置为 P1 权益制的 App 执行自定义背景门禁。 +// legacy_timed 保持 Lalu 原行为;新 App 只需在 wallet 切换 program_type,无需再修改 room-service。 +func (s *Service) requireCustomRoomBackgroundBenefit(ctx context.Context, requestID string, ownerUserID int64) error { + access, err := s.loadEffectiveVIPAccess(ctx, requestID, ownerUserID) + if err != nil { + // 权限事实不可用时必须失败关闭;放行会让无权益用户在 wallet 故障窗口获得可持久化背景权限。 + return err + } + if access.programType != tieredPrivilegeVIPProgram { + return nil + } + if !access.hasBenefit(vipBenefitCustomRoomBackground) { + return xerr.New(xerr.PermissionDenied, "custom room background vip benefit is required") + } + return nil +} + +// requireTargetWithoutVIPBenefit 保护普通房主/管理员治理入口;平台治理必须继续走 SystemEvictUser,不能复用该守卫。 +func (s *Service) requireTargetWithoutVIPBenefit(ctx context.Context, requestID string, targetUserID int64, benefitCode string) error { + access, err := s.loadEffectiveVIPAccess(ctx, requestID, targetUserID) + if err != nil { + // anti_kick/anti_mute 属于付费保护,wallet 不可判定时宁可拒绝普通房管动作,也不能错误剥夺已生效权益。 + return err + } + if access.hasBenefit(benefitCode) { + return xerr.New(xerr.PermissionDenied, "target vip benefit prevents this room moderation action") + } + return nil +} + +func normalizeVIPCode(value string) string { + return strings.ToLower(strings.TrimSpace(value)) +} diff --git a/services/room-service/internal/room/service/vip_test.go b/services/room-service/internal/room/service/vip_test.go new file mode 100644 index 00000000..b280e93b --- /dev/null +++ b/services/room-service/internal/room/service/vip_test.go @@ -0,0 +1,266 @@ +package service_test + +import ( + "context" + "fmt" + "testing" + "time" + + "google.golang.org/protobuf/proto" + roomeventsv1 "hyapp.local/api/proto/events/room/v1" + roomv1 "hyapp.local/api/proto/room/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/xerr" + roomservice "hyapp/services/room-service/internal/room/service" + "hyapp/services/room-service/internal/testutil/mysqltest" +) + +const vipTestAppCode = "fami" + +func TestJoinRoomCarriesEffectiveVIPAndQueriesWalletOnce(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + wallet := &rocketTestWallet{vipResponses: map[int64]*walletv1.GetMyVipResponse{ + 8202: tieredVIPResponse(8202, 4, "VIP4", "room_entry_notice"), + }} + directIM := newRecordingRoomDirectIMPublisher() + now := &fixedRoomRocketClock{now: time.Date(2026, 7, 10, 8, 0, 0, 0, time.UTC)} + svc := newRocketTestServiceWithDirectIM(t, repository, wallet, now, nil, directIM) + + roomID := "fami-vip-join-room" + createVIPRoom(t, ctx, svc, roomID, 8201) + response, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{ + Meta: vipRoomMeta(roomID, 8202, "join"), + Role: "audience", + }) + if err != nil { + t.Fatalf("join room failed: %v", err) + } + if response.GetEffectiveVip().GetProgramType() != "tiered_privilege_v1" || response.GetEffectiveVip().GetLevel() != 4 || response.GetEffectiveVip().GetName() != "VIP4" || !response.GetEffectiveVip().GetRoomEntryNoticeEnabled() { + t.Fatalf("join effective vip mismatch: %+v", response.GetEffectiveVip()) + } + if len(wallet.vipRequests) != 1 || wallet.vipRequests[0].GetAppCode() != vipTestAppCode || wallet.vipRequests[0].GetUserId() != 8202 { + t.Fatalf("join must query exactly one scoped effective vip state: %+v", wallet.vipRequests) + } + + waitForRoomDirectIMCounts(t, directIM, map[string]int{"RoomUserJoined": 1}) + records := directIM.recordsByType("RoomUserJoined") + if len(records) != 1 { + t.Fatalf("joined direct IM record mismatch: %+v", records) + } + var joined roomeventsv1.RoomUserJoined + if err := proto.Unmarshal(records[0].GetBody(), &joined); err != nil { + t.Fatalf("decode joined event failed: %v", err) + } + if joined.GetVipProgramType() != "tiered_privilege_v1" || joined.GetEffectiveVipLevel() != 4 || joined.GetEffectiveVipName() != "VIP4" || !joined.GetRoomEntryNoticeEnabled() { + t.Fatalf("joined event vip mismatch: %+v", &joined) + } +} + +func TestRoomEntryNoticeRequiresBenefitAndUserSetting(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + wallet := &rocketTestWallet{vipResponses: map[int64]*walletv1.GetMyVipResponse{ + 8502: tieredVIPResponseWithRoomNoticeSetting(8502, 8, "VIP8", true, true), + 8503: tieredVIPResponseWithRoomNoticeSetting(8503, 8, "VIP8", true, false), + 8504: tieredVIPResponseWithRoomNoticeSetting(8504, 8, "VIP8", false, true), + // nil user_settings 模拟滚动升级期间旧 wallet,必须按默认开启兼容。 + 8505: tieredVIPResponse(8505, 8, "VIP8", "room_entry_notice"), + }} + directIM := newRecordingRoomDirectIMPublisher() + now := &fixedRoomRocketClock{now: time.Date(2026, 7, 10, 8, 30, 0, 0, time.UTC)} + svc := newRocketTestServiceWithDirectIM(t, repository, wallet, now, nil, directIM) + roomID := "fami-vip-setting-room" + createVIPRoom(t, ctx, svc, roomID, 8501) + + wants := map[int64]bool{8502: true, 8503: false, 8504: false, 8505: true} + for userID, want := range wants { + response, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{ + Meta: vipRoomMeta(roomID, userID, fmt.Sprintf("settings-%d", userID)), Role: "audience", + }) + if err != nil { + t.Fatalf("join user %d failed: %v", userID, err) + } + if got := response.GetEffectiveVip().GetRoomEntryNoticeEnabled(); got != want { + t.Fatalf("room snapshot setting mismatch user=%d got=%v want=%v snapshot=%+v", userID, got, want, response.GetEffectiveVip()) + } + } + + waitForRoomDirectIMCounts(t, directIM, map[string]int{"RoomUserJoined": len(wants)}) + seen := make(map[int64]bool, len(wants)) + for _, record := range directIM.recordsByType("RoomUserJoined") { + var joined roomeventsv1.RoomUserJoined + if err := proto.Unmarshal(record.GetBody(), &joined); err != nil { + t.Fatalf("decode joined event failed: %v", err) + } + seen[joined.GetUserId()] = joined.GetRoomEntryNoticeEnabled() + } + for userID, want := range wants { + if got, exists := seen[userID]; !exists || got != want { + t.Fatalf("joined event/IM setting mismatch user=%d exists=%v got=%v want=%v", userID, exists, got, want) + } + } +} + +func TestTieredVIPCustomRoomBackgroundRequiresEffectiveBenefit(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + ownerID := int64(8301) + wallet := &rocketTestWallet{vipResponses: map[int64]*walletv1.GetMyVipResponse{ + ownerID: tieredVIPResponse(ownerID, 3, "VIP3"), + }} + now := &fixedRoomRocketClock{now: time.Date(2026, 7, 10, 9, 0, 0, 0, time.UTC)} + svc := newRocketTestServiceWithDirectIM(t, repository, wallet, now, nil, newRecordingRoomDirectIMPublisher()) + roomID := "fami-vip-background-room" + createVIPRoom(t, ctx, svc, roomID, ownerID) + + if _, err := svc.SaveRoomBackground(ctx, &roomv1.SaveRoomBackgroundRequest{ + Meta: vipRoomMeta(roomID, ownerID, "save-denied"), + RoomId: roomID, + ImageUrl: "https://cdn.example.com/fami-vip-denied.png", + }); !xerr.IsCode(err, xerr.PermissionDenied) { + t.Fatalf("tiered app without custom_room_background must be denied: %v", err) + } + + wallet.vipResponses[ownerID] = tieredVIPResponse(ownerID, 3, "VIP3", "custom_room_background") + first := saveVIPRoomBackground(t, ctx, svc, roomID, ownerID, "first", "https://cdn.example.com/fami-vip-first.png") + second := saveVIPRoomBackground(t, ctx, svc, roomID, ownerID, "second", "https://cdn.example.com/fami-vip-second.png") + if _, err := svc.SetRoomBackground(ctx, &roomv1.SetRoomBackgroundRequest{ + Meta: vipRoomMeta(roomID, ownerID, "set-allowed"), + BackgroundId: first.GetBackgroundId(), + }); err != nil { + t.Fatalf("effective custom_room_background benefit must allow set: %v", err) + } + + wallet.vipResponses[ownerID] = tieredVIPResponse(ownerID, 3, "VIP3") + if _, err := svc.SetRoomBackground(ctx, &roomv1.SetRoomBackgroundRequest{ + Meta: vipRoomMeta(roomID, ownerID, "set-denied"), + BackgroundId: second.GetBackgroundId(), + }); !xerr.IsCode(err, xerr.PermissionDenied) { + t.Fatalf("expired custom_room_background benefit must block later set: %v", err) + } +} + +func TestVIPModerationProtectionBlocksRoomManagersButNotSystemEvict(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + targetID := int64(8402) + wallet := &rocketTestWallet{vipResponses: map[int64]*walletv1.GetMyVipResponse{ + targetID: tieredVIPResponse(targetID, 9, "VIP9", "anti_kick", "anti_mute"), + }} + now := &fixedRoomRocketClock{now: time.Date(2026, 7, 10, 10, 0, 0, 0, time.UTC)} + svc := newRocketTestServiceWithDirectIM(t, repository, wallet, now, nil, newRecordingRoomDirectIMPublisher()) + roomID := "fami-vip-moderation-room" + ownerID := int64(8401) + createVIPRoom(t, ctx, svc, roomID, ownerID) + if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{Meta: vipRoomMeta(roomID, targetID, "target-join"), Role: "audience"}); err != nil { + t.Fatalf("join protected target failed: %v", err) + } + + if _, err := svc.MuteUser(ctx, &roomv1.MuteUserRequest{ + Meta: vipRoomMeta(roomID, ownerID, "mute"), + TargetUserId: targetID, + Muted: true, + }); !xerr.IsCode(err, xerr.PermissionDenied) { + t.Fatalf("anti_mute must block ordinary room owner: %v", err) + } + if _, err := svc.KickUser(ctx, &roomv1.KickUserRequest{ + Meta: vipRoomMeta(roomID, ownerID, "kick"), + TargetUserId: targetID, + }); !xerr.IsCode(err, xerr.PermissionDenied) { + t.Fatalf("anti_kick must block ordinary room owner: %v", err) + } + + vipCallsBeforeSystemEvict := len(wallet.vipRequests) + evict, err := svc.SystemEvictUser(ctx, &roomv1.SystemEvictUserRequest{ + Meta: vipRoomMeta(roomID, 9901, "system-evict"), + TargetUserId: targetID, + OperatorUserId: 9901, + Reason: "platform_moderation", + BanFromRoom: true, + }) + if err != nil { + t.Fatalf("system evict must bypass anti_kick: %v", err) + } + if !evict.GetResult().GetApplied() || findRoomUser(evict.GetRoom(), targetID) != nil || !containsInt64(evict.GetRoom().GetBanUserIds(), targetID) { + t.Fatalf("system evict must remove and ban protected target: %+v", evict) + } + if len(wallet.vipRequests) != vipCallsBeforeSystemEvict { + t.Fatalf("SystemEvictUser must not query or enforce ordinary VIP moderation benefits: before=%d after=%d", vipCallsBeforeSystemEvict, len(wallet.vipRequests)) + } +} + +func tieredVIPResponse(userID int64, level int32, name string, benefitCodes ...string) *walletv1.GetMyVipResponse { + benefits := make([]*walletv1.VipBenefit, 0, len(benefitCodes)) + for index, code := range benefitCodes { + benefits = append(benefits, &walletv1.VipBenefit{ + BenefitCode: code, + Name: code, + Status: "active", + SortOrder: int32(index + 1), + }) + } + return &walletv1.GetMyVipResponse{State: &walletv1.VipState{ + ProgramConfig: &walletv1.VipProgramConfig{AppCode: vipTestAppCode, ProgramType: "tiered_privilege_v1", Status: "active", LevelCount: 9}, + EffectiveVip: &walletv1.UserVip{ + UserId: userID, + Level: level, + Name: name, + Active: level > 0, + ProgramType: "tiered_privilege_v1", + }, + EffectiveBenefits: benefits, + }} +} + +func tieredVIPResponseWithRoomNoticeSetting(userID int64, level int32, name string, hasBenefit bool, enabled bool) *walletv1.GetMyVipResponse { + codes := []string{} + if hasBenefit { + codes = append(codes, "room_entry_notice") + } + response := tieredVIPResponse(userID, level, name, codes...) + response.GetState().UserSettings = &walletv1.VipUserSettings{ + AppCode: vipTestAppCode, UserId: userID, + RoomEntryNoticeEnabled: enabled, OnlineGlobalNoticeEnabled: true, + } + return response +} + +func createVIPRoom(t *testing.T, ctx context.Context, svc *roomservice.Service, roomID string, ownerID int64) { + t.Helper() + if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{ + Meta: vipRoomMeta(roomID, ownerID, "create"), + SeatCount: 10, + Mode: "voice", + VisibleRegionId: 1001, + RoomName: roomID, + RoomAvatar: testRoomCoverURL, + RoomShortId: roomID, + }); err != nil { + t.Fatalf("create vip room failed: %v", err) + } +} + +func saveVIPRoomBackground(t *testing.T, ctx context.Context, svc *roomservice.Service, roomID string, ownerID int64, suffix string, imageURL string) *roomv1.RoomBackgroundImage { + t.Helper() + response, err := svc.SaveRoomBackground(ctx, &roomv1.SaveRoomBackgroundRequest{ + Meta: vipRoomMeta(roomID, ownerID, "save-"+suffix), + RoomId: roomID, + ImageUrl: imageURL, + }) + if err != nil { + t.Fatalf("save vip room background failed: %v", err) + } + return response.GetBackground() +} + +func vipRoomMeta(roomID string, actorUserID int64, suffix string) *roomv1.RequestMeta { + return &roomv1.RequestMeta{ + RequestId: fmt.Sprintf("req-vip-%s", suffix), + CommandId: fmt.Sprintf("cmd-vip-%s", suffix), + ActorUserId: actorUserID, + RoomId: roomID, + AppCode: vipTestAppCode, + SentAtMs: time.Date(2026, 7, 10, 8, 0, 0, 0, time.UTC).UnixMilli(), + } +} diff --git a/services/room-service/internal/storage/mysql/command_log.go b/services/room-service/internal/storage/mysql/command_log.go index 3d4f6b82..cdd26875 100644 --- a/services/room-service/internal/storage/mysql/command_log.go +++ b/services/room-service/internal/storage/mysql/command_log.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + mysqlerr "github.com/go-sql-driver/mysql" "google.golang.org/protobuf/proto" "hyapp/pkg/appcode" "hyapp/services/room-service/internal/room/command" @@ -19,7 +20,7 @@ import ( func (r *Repository) GetCommand(ctx context.Context, roomID string, commandID string) (roomservice.CommandRecord, bool, error) { // 幂等检查只看 command log,只有成功提交过的命令才算 seen。 row := r.db.QueryRowContext(ctx, - `SELECT app_code, room_id, room_version, command_id, command_type, owner_node_id, lease_token, payload, replayable, created_at_ms + `SELECT app_code, room_id, room_version, command_id, command_type, owner_node_id, lease_token, payload, result_payload, replayable, created_at_ms FROM room_command_log WHERE app_code = ? AND room_id = ? AND command_id = ? LIMIT 1`, @@ -29,7 +30,7 @@ func (r *Repository) GetCommand(ctx context.Context, roomID string, commandID st ) var record roomservice.CommandRecord - if err := row.Scan(&record.AppCode, &record.RoomID, &record.RoomVersion, &record.CommandID, &record.CommandType, &record.OwnerNodeID, &record.LeaseToken, &record.Payload, &record.Replayable, &record.CreatedAtMS); err != nil { + if err := row.Scan(&record.AppCode, &record.RoomID, &record.RoomVersion, &record.CommandID, &record.CommandType, &record.OwnerNodeID, &record.LeaseToken, &record.Payload, &record.ResultPayload, &record.Replayable, &record.CreatedAtMS); err != nil { if errors.Is(err, sql.ErrNoRows) { // 没有命令日志表示本命令尚未成功提交。 return roomservice.CommandRecord{}, false, nil @@ -46,8 +47,8 @@ func (r *Repository) SaveCommand(ctx context.Context, record roomservice.Command // ON DUPLICATE KEY 保证重复命令写入不会破坏已提交记录。 appCode := normalizedRecordAppCode(ctx, record.AppCode) _, err := r.db.ExecContext(ctx, - `INSERT INTO room_command_log (app_code, room_id, room_version, command_id, command_type, owner_node_id, lease_token, payload, replayable, created_at_ms) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `INSERT INTO room_command_log (app_code, room_id, room_version, command_id, command_type, owner_node_id, lease_token, payload, result_payload, replayable, created_at_ms) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE command_id = command_id`, appCode, record.RoomID, @@ -57,6 +58,7 @@ func (r *Repository) SaveCommand(ctx context.Context, record roomservice.Command record.OwnerNodeID, record.LeaseToken, record.Payload, + nullableBytes(record.ResultPayload), record.Replayable, record.CreatedAtMS, ) @@ -102,9 +104,8 @@ func (r *Repository) SaveMutation(ctx context.Context, commit roomservice.Mutati } if _, err := tx.ExecContext(ctx, - `INSERT INTO room_command_log (app_code, room_id, room_version, command_id, command_type, owner_node_id, lease_token, payload, replayable, created_at_ms) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE command_id = command_id`, + `INSERT INTO room_command_log (app_code, room_id, room_version, command_id, command_type, owner_node_id, lease_token, payload, result_payload, replayable, created_at_ms) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, appCode, commit.Command.RoomID, commit.Command.RoomVersion, @@ -113,10 +114,16 @@ func (r *Repository) SaveMutation(ctx context.Context, commit roomservice.Mutati commit.Command.OwnerNodeID, commit.Command.LeaseToken, commit.Command.Payload, + nullableBytes(commit.Command.ResultPayload), commit.Command.Replayable, commit.Command.CreatedAtMS, ); err != nil { _ = tx.Rollback() + var duplicate *mysqlerr.MySQLError + if errors.As(err, &duplicate) && duplicate.Number == 1062 && strings.Contains(duplicate.Message, "uk_room_command") { + // 绝不能用 ON DUPLICATE KEY 吞掉重复命令后继续写 outbox/统计增量;调用方会读取首次结果并原样重放。 + return roomservice.ErrCommandAlreadyCommitted + } return err } @@ -345,6 +352,35 @@ func (r *Repository) SaveMutation(ctx context.Context, commit roomservice.Mutati } } + if commit.GiftOperation != nil { + operation := commit.GiftOperation + result, err := tx.ExecContext(ctx, + `UPDATE room_gift_operations + SET status = ?, result_payload = ?, last_error = NULL, worker_id = '', lock_until_ms = NULL, + next_retry_at_ms = 0, updated_at_ms = ? + WHERE app_code = ? AND room_id = ? AND command_id = ? AND status <> ?`, + roomservice.GiftOperationStatusCommitted, + operation.ResultPayload, + operation.UpdatedAtMS, + appCode, + commit.Command.RoomID, + operation.CommandID, + roomservice.GiftOperationStatusCompensated, + ) + if err != nil { + _ = tx.Rollback() + return err + } + affected, err := result.RowsAffected() + if err != nil || affected != 1 { + _ = tx.Rollback() + if err != nil { + return err + } + return fmt.Errorf("gift operation cannot commit: room_id=%s command_id=%s", commit.Command.RoomID, operation.CommandID) + } + } + return tx.Commit() } @@ -394,6 +430,7 @@ func (r *Repository) deleteRoomRows(ctx context.Context, tx *sql.Tx, appCode str `DELETE FROM room_user_contribution_period_stats WHERE app_code = ? AND room_id = ?`, `DELETE FROM room_background_images WHERE app_code = ? AND room_id = ?`, `DELETE FROM room_snapshots WHERE app_code = ? AND room_id = ?`, + `DELETE FROM room_gift_operations WHERE app_code = ? AND room_id = ?`, `DELETE FROM room_command_log WHERE app_code = ? AND room_id = ?`, `DELETE FROM room_list_entries WHERE app_code = ? AND room_id = ?`, `DELETE FROM rooms WHERE app_code = ? AND room_id = ?`, @@ -447,7 +484,7 @@ func (r *Repository) insertRoomOutboxRecords(ctx context.Context, tx *sql.Tx, re func (r *Repository) ListCommandsAfter(ctx context.Context, roomID string, roomVersion int64) ([]roomservice.CommandRecord, error) { // 恢复必须按 room_version 再按自增 id 排序,确保同版本异常数据也有稳定顺序。 rows, err := r.db.QueryContext(ctx, - `SELECT app_code, room_id, room_version, command_id, command_type, owner_node_id, lease_token, payload, replayable, created_at_ms + `SELECT app_code, room_id, room_version, command_id, command_type, owner_node_id, lease_token, payload, result_payload, replayable, created_at_ms FROM room_command_log WHERE app_code = ? AND room_id = ? AND room_version > ? ORDER BY room_version ASC, id ASC`, @@ -464,7 +501,7 @@ func (r *Repository) ListCommandsAfter(ctx context.Context, roomID string, roomV for rows.Next() { // payload 保持原始字节,领域层根据 command_type 反序列化。 var record roomservice.CommandRecord - if err := rows.Scan(&record.AppCode, &record.RoomID, &record.RoomVersion, &record.CommandID, &record.CommandType, &record.OwnerNodeID, &record.LeaseToken, &record.Payload, &record.Replayable, &record.CreatedAtMS); err != nil { + if err := rows.Scan(&record.AppCode, &record.RoomID, &record.RoomVersion, &record.CommandID, &record.CommandType, &record.OwnerNodeID, &record.LeaseToken, &record.Payload, &record.ResultPayload, &record.Replayable, &record.CreatedAtMS); err != nil { return nil, err } diff --git a/services/room-service/internal/storage/mysql/command_log_test.go b/services/room-service/internal/storage/mysql/command_log_test.go index 0607f13c..9c25935e 100644 --- a/services/room-service/internal/storage/mysql/command_log_test.go +++ b/services/room-service/internal/storage/mysql/command_log_test.go @@ -3,6 +3,7 @@ package mysql import ( "bytes" "context" + "errors" "sync" "testing" @@ -11,6 +12,34 @@ import ( roomservice "hyapp/services/room-service/internal/room/service" ) +func TestSaveMutationRejectsSameCommandInsteadOfContinuingSideEffects(t *testing.T) { + ctx := appcode.WithContext(context.Background(), appcode.Default) + repo := newCommandLogTestRepository(t, ctx) + roomID := "room-command-id-race" + insertCommandLogTestRoom(t, ctx, repo, roomID) + first := roomservice.MutationCommit{Command: roomservice.CommandRecord{ + AppCode: appcode.Default, RoomID: roomID, RoomVersion: 1, CommandID: "cmd-stable", + CommandType: "send_gift", Payload: []byte(`{"command_id":"cmd-stable"}`), ResultPayload: []byte("first-result"), + Replayable: true, OwnerNodeID: "node-a", LeaseToken: "lease-a", CreatedAtMS: 1000, + }} + if err := repo.SaveMutation(ctx, first); err != nil { + t.Fatalf("save first command: %v", err) + } + second := first + second.Command.RoomVersion = 2 + second.Command.ResultPayload = []byte("must-not-overwrite") + if err := repo.SaveMutation(ctx, second); !errors.Is(err, roomservice.ErrCommandAlreadyCommitted) { + t.Fatalf("same command_id should return replay signal, got %v", err) + } + record, exists, err := repo.GetCommand(ctx, roomID, "cmd-stable") + if err != nil || !exists { + t.Fatalf("read first command: exists=%v err=%v", exists, err) + } + if record.RoomVersion != 1 || !bytes.Equal(record.ResultPayload, []byte("first-result")) { + t.Fatalf("duplicate command changed original result: %+v", record) + } +} + func TestSaveSnapshotDoesNotOverwriteSameVersion(t *testing.T) { ctx := appcode.WithContext(context.Background(), appcode.Default) repo := newCommandLogTestRepository(t, ctx) diff --git a/services/room-service/internal/storage/mysql/gift_operation.go b/services/room-service/internal/storage/mysql/gift_operation.go new file mode 100644 index 00000000..2b2e0c5b --- /dev/null +++ b/services/room-service/internal/storage/mysql/gift_operation.go @@ -0,0 +1,269 @@ +package mysql + +import ( + "context" + "database/sql" + "errors" + "fmt" + "strings" + + mysqlerr "github.com/go-sql-driver/mysql" + "hyapp/pkg/appcode" + roomservice "hyapp/services/room-service/internal/room/service" +) + +// EnsureGiftOperation 在第一次可能调用钱包前固化恢复输入;重复 command_id 只返回原记录,不能覆盖首次载荷。 +func (r *Repository) EnsureGiftOperation(ctx context.Context, operation roomservice.GiftOperation) (roomservice.GiftOperation, bool, error) { + operation.AppCode = normalizedRecordAppCode(ctx, operation.AppCode) + if operation.Status == "" { + operation.Status = roomservice.GiftOperationStatusPending + } + _, err := r.db.ExecContext(ctx, ` + INSERT INTO room_gift_operations ( + app_code, room_id, command_id, actor_user_id, command_payload, request_payload, status, + billing_payload, lucky_payload, result_payload, last_error, retry_count, next_retry_at_ms, + worker_id, lock_until_ms, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, '', NULL, ?, ?) + `, operation.AppCode, operation.RoomID, operation.CommandID, operation.ActorUserID, + operation.CommandPayload, operation.RequestPayload, operation.Status, + nullableBytes(operation.BillingPayload), nullableBytes(operation.LuckyPayload), nullableBytes(operation.ResultPayload), + nullableString(operation.LastError), operation.NextRetryAtMS, operation.CreatedAtMS, operation.UpdatedAtMS) + if err == nil { + return operation, true, nil + } + var duplicate *mysqlerr.MySQLError + if !errors.As(err, &duplicate) || duplicate.Number != 1062 { + return roomservice.GiftOperation{}, false, err + } + + existing, exists, err := r.GetGiftOperation(ctx, operation.RoomID, operation.CommandID) + if err != nil || !exists { + return roomservice.GiftOperation{}, false, err + } + // A duplicate command may carry a different gift/recipient/count. Do not reactivate compensated + // work here: the service must first compare normalized idempotency payloads, otherwise a rejected + // conflicting request could make the old stored request claimable and debit it in the background. + return existing, false, nil +} + +// ReactivateCompensatedGiftOperation reopens a confirmed-identical request. The status predicate is +// the concurrency boundary: if another caller already moved it forward, return the authoritative row +// and let the shared command_id idempotency path converge instead of overwriting its progress. +func (r *Repository) ReactivateCompensatedGiftOperation(ctx context.Context, roomID string, commandID string, updatedAtMS int64) (roomservice.GiftOperation, bool, error) { + result, err := r.db.ExecContext(ctx, ` + UPDATE room_gift_operations + SET status = ?, last_error = NULL, retry_count = 0, next_retry_at_ms = 0, + worker_id = '', lock_until_ms = NULL, updated_at_ms = ? + WHERE app_code = ? AND room_id = ? AND command_id = ? AND status = ? + `, roomservice.GiftOperationStatusPending, updatedAtMS, appcode.FromContext(ctx), roomID, commandID, + roomservice.GiftOperationStatusCompensated) + if err != nil { + return roomservice.GiftOperation{}, false, err + } + affected, err := result.RowsAffected() + if err != nil { + return roomservice.GiftOperation{}, false, err + } + operation, exists, err := r.GetGiftOperation(ctx, roomID, commandID) + if err != nil { + return roomservice.GiftOperation{}, false, err + } + if !exists { + return roomservice.GiftOperation{}, false, fmt.Errorf("gift operation disappeared during reactivation: room_id=%s command_id=%s", roomID, commandID) + } + return operation, affected == 1, nil +} + +func (r *Repository) GetGiftOperation(ctx context.Context, roomID string, commandID string) (roomservice.GiftOperation, bool, error) { + row := r.db.QueryRowContext(ctx, ` + SELECT app_code, room_id, command_id, actor_user_id, command_payload, request_payload, status, + billing_payload, lucky_payload, result_payload, COALESCE(last_error, ''), retry_count, + next_retry_at_ms, worker_id, lock_until_ms, created_at_ms, updated_at_ms + FROM room_gift_operations + WHERE app_code = ? AND room_id = ? AND command_id = ? + LIMIT 1 + `, appcode.FromContext(ctx), strings.TrimSpace(roomID), strings.TrimSpace(commandID)) + operation, err := scanGiftOperation(row) + if errors.Is(err, sql.ErrNoRows) { + return roomservice.GiftOperation{}, false, nil + } + if err != nil { + return roomservice.GiftOperation{}, false, err + } + return operation, true, nil +} + +// SaveGiftOperationProgress 只允许 saga 向前推进;恢复重放 wallet 时写 debited 不能把已 drawn 的操作降级。 +func (r *Repository) SaveGiftOperationProgress(ctx context.Context, progress roomservice.GiftOperationProgress) error { + statusRank, ok := giftOperationStatusRank(progress.Status) + if !ok || statusRank < giftOperationStatusRankMust(roomservice.GiftOperationStatusDebited) || statusRank > giftOperationStatusRankMust(roomservice.GiftOperationStatusDrawn) { + return fmt.Errorf("invalid gift operation progress status %q", progress.Status) + } + _, err := r.db.ExecContext(ctx, ` + UPDATE room_gift_operations + SET status = CASE + WHEN status = ? THEN status + WHEN status = ? THEN status + WHEN status = ? AND ? = ? THEN status + ELSE ? + END, + billing_payload = COALESCE(?, billing_payload), + lucky_payload = COALESCE(?, lucky_payload), + last_error = NULL, next_retry_at_ms = ?, updated_at_ms = ? + WHERE app_code = ? AND room_id = ? AND command_id = ? + AND status IN (?, ?, ?) + `, roomservice.GiftOperationStatusCommitted, roomservice.GiftOperationStatusCompensated, + roomservice.GiftOperationStatusDrawn, progress.Status, roomservice.GiftOperationStatusDebited, progress.Status, + nullableBytes(progress.BillingPayload), nullableBytes(progress.LuckyPayload), progress.NextRetryAtMS, progress.UpdatedAtMS, + appcode.FromContext(ctx), progress.RoomID, progress.CommandID, + roomservice.GiftOperationStatusPending, roomservice.GiftOperationStatusDebited, roomservice.GiftOperationStatusDrawn) + if err != nil { + return err + } + return nil +} + +// ClaimRecoverableGiftOperations 用短租约分摊恢复任务;command_id 幂等是最终防线,租约只避免多实例重复施压。 +func (r *Repository) ClaimRecoverableGiftOperations(ctx context.Context, workerID string, limit int, nowMS int64, lockUntilMS int64) ([]roomservice.GiftOperation, error) { + if limit <= 0 { + return nil, nil + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + rows, err := tx.QueryContext(ctx, ` + SELECT app_code, room_id, command_id, actor_user_id, command_payload, request_payload, status, + billing_payload, lucky_payload, result_payload, COALESCE(last_error, ''), retry_count, + next_retry_at_ms, worker_id, lock_until_ms, created_at_ms, updated_at_ms + FROM room_gift_operations + WHERE status IN (?, ?, ?) + AND next_retry_at_ms <= ? + AND (lock_until_ms IS NULL OR lock_until_ms <= ?) + ORDER BY updated_at_ms ASC, command_id ASC + LIMIT ? + FOR UPDATE SKIP LOCKED + `, roomservice.GiftOperationStatusPending, roomservice.GiftOperationStatusDebited, roomservice.GiftOperationStatusDrawn, nowMS, nowMS, limit) + if err != nil { + _ = tx.Rollback() + return nil, err + } + operations := make([]roomservice.GiftOperation, 0, limit) + for rows.Next() { + operation, err := scanGiftOperation(rows) + if err != nil { + _ = rows.Close() + _ = tx.Rollback() + return nil, err + } + operations = append(operations, operation) + } + if err := rows.Close(); err != nil { + _ = tx.Rollback() + return nil, err + } + for index := range operations { + operation := &operations[index] + if _, err := tx.ExecContext(ctx, ` + UPDATE room_gift_operations + SET worker_id = ?, lock_until_ms = ?, updated_at_ms = ? + WHERE app_code = ? AND room_id = ? AND command_id = ? + `, workerID, lockUntilMS, nowMS, operation.AppCode, operation.RoomID, operation.CommandID); err != nil { + _ = tx.Rollback() + return nil, err + } + operation.WorkerID = workerID + operation.LockUntilMS = lockUntilMS + } + if err := tx.Commit(); err != nil { + return nil, err + } + return operations, nil +} + +func (r *Repository) MarkGiftOperationRetry(ctx context.Context, roomID string, commandID string, lastError string, nextRetryAtMS int64, updatedAtMS int64, preserveLease bool) error { + _, err := r.db.ExecContext(ctx, ` + UPDATE room_gift_operations + SET retry_count = retry_count + 1, last_error = ?, next_retry_at_ms = ?, + worker_id = CASE WHEN ? THEN worker_id ELSE '' END, + lock_until_ms = CASE WHEN ? THEN lock_until_ms ELSE NULL END, + updated_at_ms = ? + WHERE app_code = ? AND room_id = ? AND command_id = ? AND status IN (?, ?, ?) + `, nullableString(strings.TrimSpace(lastError)), nextRetryAtMS, preserveLease, preserveLease, updatedAtMS, appcode.FromContext(ctx), roomID, commandID, + roomservice.GiftOperationStatusPending, roomservice.GiftOperationStatusDebited, roomservice.GiftOperationStatusDrawn) + return err +} + +func (r *Repository) MarkGiftOperationCompensated(ctx context.Context, roomID string, commandID string, lastError string, updatedAtMS int64) error { + _, err := r.db.ExecContext(ctx, ` + UPDATE room_gift_operations + SET status = ?, last_error = ?, next_retry_at_ms = 0, worker_id = '', lock_until_ms = NULL, updated_at_ms = ? + WHERE app_code = ? AND room_id = ? AND command_id = ? AND status = ? + `, roomservice.GiftOperationStatusCompensated, nullableString(strings.TrimSpace(lastError)), updatedAtMS, + appcode.FromContext(ctx), roomID, commandID, roomservice.GiftOperationStatusPending) + return err +} + +func (r *Repository) HasRecoverableGiftOperations(ctx context.Context, roomID string) (bool, error) { + var exists int + err := r.db.QueryRowContext(ctx, ` + SELECT 1 + FROM room_gift_operations + WHERE app_code = ? AND room_id = ? AND status IN (?, ?, ?) + LIMIT 1 + `, appcode.FromContext(ctx), strings.TrimSpace(roomID), roomservice.GiftOperationStatusPending, + roomservice.GiftOperationStatusDebited, roomservice.GiftOperationStatusDrawn).Scan(&exists) + if errors.Is(err, sql.ErrNoRows) { + return false, nil + } + if err != nil { + return false, err + } + return true, nil +} + +type giftOperationScanner interface { + Scan(dest ...any) error +} + +func scanGiftOperation(scanner giftOperationScanner) (roomservice.GiftOperation, error) { + var operation roomservice.GiftOperation + var billingPayload, luckyPayload, resultPayload []byte + var lockUntil sql.Null[int64] + err := scanner.Scan(&operation.AppCode, &operation.RoomID, &operation.CommandID, &operation.ActorUserID, + &operation.CommandPayload, &operation.RequestPayload, &operation.Status, + &billingPayload, &luckyPayload, &resultPayload, &operation.LastError, &operation.RetryCount, + &operation.NextRetryAtMS, &operation.WorkerID, &lockUntil, &operation.CreatedAtMS, &operation.UpdatedAtMS) + if err != nil { + return roomservice.GiftOperation{}, err + } + operation.BillingPayload = billingPayload + operation.LuckyPayload = luckyPayload + operation.ResultPayload = resultPayload + if lockUntil.Valid { + operation.LockUntilMS = lockUntil.V + } + return operation, nil +} + +func giftOperationStatusRank(status string) (int, bool) { + switch status { + case roomservice.GiftOperationStatusPending: + return 1, true + case roomservice.GiftOperationStatusDebited: + return 2, true + case roomservice.GiftOperationStatusDrawn: + return 3, true + case roomservice.GiftOperationStatusCommitted: + return 4, true + case roomservice.GiftOperationStatusCompensated: + return 5, true + default: + return 0, false + } +} + +func giftOperationStatusRankMust(status string) int { + rank, _ := giftOperationStatusRank(status) + return rank +} diff --git a/services/room-service/internal/storage/mysql/gift_operation_test.go b/services/room-service/internal/storage/mysql/gift_operation_test.go new file mode 100644 index 00000000..12fa2aab --- /dev/null +++ b/services/room-service/internal/storage/mysql/gift_operation_test.go @@ -0,0 +1,54 @@ +package mysql + +import ( + "context" + "testing" + + "hyapp/internal/testutil/mysqlschema" + "hyapp/pkg/appcode" + roomservice "hyapp/services/room-service/internal/room/service" +) + +func TestEnsureGiftOperationDoesNotReactivateBeforeServicePayloadCheck(t *testing.T) { + schema := mysqlschema.New(t, mysqlschema.Config{ + EnvVar: "ROOM_SERVICE_MYSQL_TEST_DSN", + InitDBPath: mysqlschema.InitDBPath(t, mysqlschema.CallerFile(t, 1), "..", "..", "..", "deploy", "mysql", "initdb", "001_room_service.sql"), + DatabasePrefix: "hyapp_room_gift_reactivate", + }) + ctx := appcode.WithContext(context.Background(), appcode.Default) + repository, err := Open(ctx, schema.DSN, 4, 4) + if err != nil { + t.Fatalf("open repository: %v", err) + } + defer repository.Close() + + stored := roomservice.GiftOperation{ + AppCode: appcode.Default, RoomID: "room-reactivate", CommandID: "cmd-reactivate", ActorUserID: 101, + CommandPayload: []byte("stored-command"), RequestPayload: []byte("stored-request"), + Status: roomservice.GiftOperationStatusCompensated, LastError: "insufficient balance", + CreatedAtMS: 1_000, UpdatedAtMS: 1_000, + } + if _, created, err := repository.EnsureGiftOperation(ctx, stored); err != nil || !created { + t.Fatalf("insert compensated operation: created=%v err=%v", created, err) + } + conflicting := stored + conflicting.CommandPayload = []byte("different-command") + conflicting.RequestPayload = []byte("different-request") + conflicting.UpdatedAtMS = 2_000 + got, created, err := repository.EnsureGiftOperation(ctx, conflicting) + if err != nil || created { + t.Fatalf("read duplicate operation: created=%v err=%v", created, err) + } + if got.Status != roomservice.GiftOperationStatusCompensated || string(got.CommandPayload) != "stored-command" { + t.Fatalf("Ensure mutated an unverified historical operation: %+v", got) + } + + got, reactivated, err := repository.ReactivateCompensatedGiftOperation(ctx, stored.RoomID, stored.CommandID, 3_000) + if err != nil || !reactivated || got.Status != roomservice.GiftOperationStatusPending { + t.Fatalf("payload-checked CAS did not reactivate: reactivated=%v operation=%+v err=%v", reactivated, got, err) + } + got, reactivated, err = repository.ReactivateCompensatedGiftOperation(ctx, stored.RoomID, stored.CommandID, 4_000) + if err != nil || reactivated || got.Status != roomservice.GiftOperationStatusPending { + t.Fatalf("reactivation must be idempotent after status advances: reactivated=%v operation=%+v err=%v", reactivated, got, err) + } +} diff --git a/services/room-service/internal/storage/mysql/outbox.go b/services/room-service/internal/storage/mysql/outbox.go index 5337173f..8ea3d788 100644 --- a/services/room-service/internal/storage/mysql/outbox.go +++ b/services/room-service/internal/storage/mysql/outbox.go @@ -13,7 +13,7 @@ import ( "hyapp/services/room-service/internal/room/outbox" ) -// SaveOutbox 追加待投递 room_outbox 事件;机器人展示事件已经改为提交后 best-effort 直发 IM,不再进入 MySQL。 +// SaveOutbox 追加待投递 room_outbox 事件;同一 app_code/event_id 重投保持 no-op,既覆盖 Room Cell 事实,也覆盖外部 owner 派生展示。 func (r *Repository) SaveOutbox(ctx context.Context, records []outbox.Record) error { if len(records) == 0 { // 没有事件时保持无副作用,方便领域层统一调用。 @@ -64,6 +64,25 @@ func (r *Repository) SaveOutbox(ctx context.Context, records []outbox.Record) er return tx.Commit() } +// ListRoomOutboxAppCodes 枚举本表实际存在的租户键,供常驻 worker 逐 App 使用现有前缀索引 claim。 +// app_code 是主键首列,DISTINCT 可走有序索引;相比移除 app_code 条件全表 claim,不会让热 outbox 扫描退化。 +func (r *Repository) ListRoomOutboxAppCodes(ctx context.Context) ([]string, error) { + rows, err := r.db.QueryContext(ctx, `SELECT DISTINCT app_code FROM room_outbox ORDER BY app_code`) + if err != nil { + return nil, err + } + defer rows.Close() + appCodes := make([]string, 0, 4) + for rows.Next() { + var appCode string + if err := rows.Scan(&appCode); err != nil { + return nil, err + } + appCodes = append(appCodes, appCode) + } + return appCodes, rows.Err() +} + // ListPendingOutbox 返回待投递事件。 func (r *Repository) ListPendingOutbox(ctx context.Context, limit int) ([]outbox.Record, error) { if limit <= 0 { diff --git a/services/room-service/internal/storage/mysql/repository.go b/services/room-service/internal/storage/mysql/repository.go index 5a49dab8..3128ac51 100644 --- a/services/room-service/internal/storage/mysql/repository.go +++ b/services/room-service/internal/storage/mysql/repository.go @@ -92,6 +92,14 @@ func nullableString(value string) sql.Null[string] { } } +func nullableBytes(value []byte) any { + // nil 结果和空 protobuf 必须都落 NULL;旧命令没有 V2 结果时幂等重放会保持历史 V1 snapshot 语义。 + if len(value) == 0 { + return nil + } + return value +} + func normalizedRecordAppCode(ctx context.Context, value string) string { value = strings.TrimSpace(value) if value == "" { diff --git a/services/room-service/internal/storage/mysql/room_list.go b/services/room-service/internal/storage/mysql/room_list.go index e57ee680..77358b80 100644 --- a/services/room-service/internal/storage/mysql/room_list.go +++ b/services/room-service/internal/storage/mysql/room_list.go @@ -583,6 +583,10 @@ func buildRoomListQuerySQL(query roomservice.RoomListQuery) (string, []any) { return buildRoomListByOwnersQuerySQL(query) } regionID := query.VisibleRegionID + if query.FilterRegionID > 0 { + // 直接 repository 调用也必须尊重已校验的区域筛选,不依赖 service 预先覆盖默认区域。 + regionID = query.FilterRegionID + } if regionID < 0 { regionID = 0 } diff --git a/services/room-service/internal/storage/mysql/room_meta.go b/services/room-service/internal/storage/mysql/room_meta.go index f8f2c543..13862e6e 100644 --- a/services/room-service/internal/storage/mysql/room_meta.go +++ b/services/room-service/internal/storage/mysql/room_meta.go @@ -60,6 +60,27 @@ func (r *Repository) GetRoomMeta(ctx context.Context, roomID string) (roomservic return meta, true, nil } +// GetRoomMetaByGlobalID 从全局 room_id 解析房间所属 App。 +// 该查询只允许可信内部回调使用;uk_rooms_room_id 同时保证查询命中索引并阻止跨 App 歧义。 +func (r *Repository) GetRoomMetaByGlobalID(ctx context.Context, roomID string) (roomservice.RoomMeta, bool, error) { + row := r.db.QueryRowContext(ctx, + `SELECT app_code, room_id, room_short_id, owner_user_id, seat_count, mode, status, room_password_hash, visible_region_id + FROM rooms + WHERE room_id = ? + LIMIT 1`, + strings.TrimSpace(roomID), + ) + + var meta roomservice.RoomMeta + if err := row.Scan(&meta.AppCode, &meta.RoomID, &meta.RoomShortID, &meta.OwnerUserID, &meta.SeatCount, &meta.Mode, &meta.Status, &meta.RoomPasswordHash, &meta.VisibleRegionID); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return roomservice.RoomMeta{}, false, nil + } + return roomservice.RoomMeta{}, false, err + } + return meta, true, nil +} + // GetRoomMetaByShortID 通过展示短房号解析内部房间 ID。 func (r *Repository) GetRoomMetaByShortID(ctx context.Context, roomShortID string) (roomservice.RoomMeta, bool, error) { // rooms.uk_rooms_short_id 保证同 app 下短房号唯一;这里只做解析,不改变 GetRoomMeta 的精确 room_id 语义。 diff --git a/services/room-service/internal/storage/mysql/room_meta_test.go b/services/room-service/internal/storage/mysql/room_meta_test.go index ead304d4..04d45ebe 100644 --- a/services/room-service/internal/storage/mysql/room_meta_test.go +++ b/services/room-service/internal/storage/mysql/room_meta_test.go @@ -23,3 +23,23 @@ func TestGetRoomMetaByShortIDReturnsCanonicalRoomID(t *testing.T) { t.Fatalf("short id must return canonical room meta, got %+v", meta) } } + +func TestGetRoomMetaByGlobalIDResolvesOwningApp(t *testing.T) { + ctx := context.Background() + repo := newCommandLogTestRepository(t, ctx) + if _, err := repo.db.ExecContext(ctx, ` + INSERT INTO rooms ( + app_code, room_id, room_short_id, owner_user_id, seat_count, mode, status, + visible_region_id, created_at_ms, updated_at_ms + ) VALUES ('fami', 'room-fami-rtc', 'fami-rtc', 9001, 8, 'voice', 'active', 1, 1000, 1000)`); err != nil { + t.Fatalf("insert fami room: %v", err) + } + + meta, exists, err := repo.GetRoomMetaByGlobalID(appcode.WithContext(ctx, "lalu"), "room-fami-rtc") + if err != nil || !exists { + t.Fatalf("resolve global room app: exists=%t err=%v", exists, err) + } + if meta.AppCode != "fami" || meta.RoomID != "room-fami-rtc" { + t.Fatalf("global room lookup must ignore caller default app and return owner: %+v", meta) + } +} diff --git a/services/room-service/internal/storage/mysql/schema.go b/services/room-service/internal/storage/mysql/schema.go index 186985f3..c7061058 100644 --- a/services/room-service/internal/storage/mysql/schema.go +++ b/services/room-service/internal/storage/mysql/schema.go @@ -135,11 +135,33 @@ func (r *Repository) Migrate(ctx context.Context) error { owner_node_id VARCHAR(128) NOT NULL DEFAULT '', lease_token VARCHAR(128) NOT NULL DEFAULT '', payload LONGBLOB NOT NULL, + result_payload LONGBLOB NULL, replayable BOOLEAN NOT NULL, created_at_ms BIGINT NOT NULL, UNIQUE KEY uk_room_command (app_code, room_id, command_id), KEY idx_room_command_replay (app_code, room_id, room_version) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS room_gift_operations ( + app_code VARCHAR(32) NOT NULL, + room_id VARCHAR(64) NOT NULL, + command_id VARCHAR(128) NOT NULL, + actor_user_id BIGINT NOT NULL, + command_payload LONGBLOB NOT NULL, + request_payload LONGBLOB NOT NULL, + status VARCHAR(32) NOT NULL, + billing_payload LONGBLOB NULL, + lucky_payload LONGBLOB NULL, + result_payload LONGBLOB NULL, + last_error TEXT NULL, + retry_count INT NOT NULL DEFAULT 0, + next_retry_at_ms BIGINT NOT NULL DEFAULT 0, + worker_id VARCHAR(128) NOT NULL DEFAULT '', + lock_until_ms BIGINT NULL, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, room_id, command_id), + KEY idx_room_gift_operation_recovery (status, next_retry_at_ms, lock_until_ms, updated_at_ms) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, `CREATE TABLE IF NOT EXISTS room_outbox ( app_code VARCHAR(32) NOT NULL, event_id VARCHAR(128) NOT NULL, @@ -335,6 +357,7 @@ func (r *Repository) Migrate(ctx context.Context) error { `ALTER TABLE room_user_presence MODIFY COLUMN app_code VARCHAR(32) NOT NULL`, `ALTER TABLE room_snapshots MODIFY COLUMN app_code VARCHAR(32) NOT NULL`, `ALTER TABLE room_command_log MODIFY COLUMN app_code VARCHAR(32) NOT NULL`, + `ALTER TABLE room_gift_operations MODIFY COLUMN app_code VARCHAR(32) NOT NULL`, `ALTER TABLE room_outbox MODIFY COLUMN app_code VARCHAR(32) NOT NULL`, `ALTER TABLE room_user_feed_entries MODIFY COLUMN app_code VARCHAR(32) NOT NULL`, `ALTER TABLE room_follows MODIFY COLUMN app_code VARCHAR(32) NOT NULL`, @@ -378,10 +401,28 @@ func (r *Repository) Migrate(ctx context.Context) error { if err := r.ensureHumanRobotConfigBehaviorSchema(ctx); err != nil { return err } + if err := r.ensureGiftOperationSchema(ctx); err != nil { + return err + } return r.ensureOutboxRetrySchema(ctx) } +func (r *Repository) ensureGiftOperationSchema(ctx context.Context) error { + // CREATE TABLE IF NOT EXISTS 不会给历史 room_command_log 补列;auto_migrate 必须显式探测, + // 否则新二进制启动后第一笔 GetCommand 会因 result_payload 不存在而直接 SQL 失败。 + hasResultPayload, err := r.columnExists(ctx, "room_command_log", "result_payload") + if err != nil { + return err + } + if !hasResultPayload { + if _, err := r.db.ExecContext(ctx, `ALTER TABLE room_command_log ADD COLUMN result_payload LONGBLOB NULL AFTER payload`); err != nil { + return err + } + } + return nil +} + func (r *Repository) ensureHumanRobotConfigBehaviorSchema(ctx context.Context) error { columns := []struct { name string diff --git a/services/room-service/internal/storage/mysql/schema_outbox_index_test.go b/services/room-service/internal/storage/mysql/schema_outbox_index_test.go index d85f4b1b..a1e91a00 100644 --- a/services/room-service/internal/storage/mysql/schema_outbox_index_test.go +++ b/services/room-service/internal/storage/mysql/schema_outbox_index_test.go @@ -75,3 +75,40 @@ func TestMigrateDropsDuplicateRoomOutboxRetryIndex(t *testing.T) { t.Fatal("idx_room_outbox_pending must survive the duplicate index cleanup") } } + +func TestMigrateBackfillsGiftV2SchemaOnExistingDatabase(t *testing.T) { + schema := mysqlschema.New(t, mysqlschema.Config{ + EnvVar: "ROOM_SERVICE_MYSQL_TEST_DSN", + InitDBPath: mysqlschema.InitDBPath(t, mysqlschema.CallerFile(t, 1), "..", "..", "..", "deploy", "mysql", "initdb", "001_room_service.sql"), + DatabasePrefix: "hyapp_room_gift_v2_migrate", + }) + ctx := context.Background() + if _, err := schema.DB.ExecContext(ctx, `DROP TABLE room_gift_operations`); err != nil { + t.Fatalf("drop gift operation table: %v", err) + } + if _, err := schema.DB.ExecContext(ctx, `ALTER TABLE room_command_log DROP COLUMN result_payload`); err != nil { + t.Fatalf("drop result_payload: %v", err) + } + repo, err := Open(ctx, schema.DSN, 4, 4) + if err != nil { + t.Fatalf("open repository: %v", err) + } + defer repo.Close() + if err := repo.Migrate(ctx); err != nil { + t.Fatalf("migrate legacy gift schema: %v", err) + } + hasResult, err := repo.columnExists(ctx, "room_command_log", "result_payload") + if err != nil || !hasResult { + t.Fatalf("result_payload was not backfilled: exists=%v err=%v", hasResult, err) + } + var operationTableCount int + if err := repo.db.QueryRowContext(ctx, ` + SELECT COUNT(*) FROM information_schema.TABLES + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'room_gift_operations' + `).Scan(&operationTableCount); err != nil || operationTableCount != 1 { + t.Fatalf("room_gift_operations was not created: count=%d err=%v", operationTableCount, err) + } + if _, _, err := repo.GetCommand(ctx, "missing-room", "missing-command"); err != nil { + t.Fatalf("GetCommand must be usable immediately after auto migration: %v", err) + } +} diff --git a/services/room-service/internal/transport/grpc/server.go b/services/room-service/internal/transport/grpc/server.go index efcdff0a..29f1a09f 100644 --- a/services/room-service/internal/transport/grpc/server.go +++ b/services/room-service/internal/transport/grpc/server.go @@ -292,6 +292,11 @@ func (s *Server) ApplyRTCEvent(ctx context.Context, req *roomv1.ApplyRTCEventReq return mapServiceResult(s.svc.ApplyRTCEvent(ctx, req)) } +// ResolveRoomAppCode 仅解析已验签回调的房间租户,不读取或修改 Room Cell 状态。 +func (s *Server) ResolveRoomAppCode(ctx context.Context, req *roomv1.ResolveRoomAppCodeRequest) (*roomv1.ResolveRoomAppCodeResponse, error) { + return mapServiceResult(s.svc.ResolveRoomAppCode(ctx, req)) +} + // SetMicSeatLock 代理到领域服务。 func (s *Server) SetMicSeatLock(ctx context.Context, req *roomv1.SetMicSeatLockRequest) (*roomv1.SetMicSeatLockResponse, error) { // 锁麦权限和目标麦位状态由 Room Cell 判定。 diff --git a/services/room-service/internal/transport/grpc/server_test.go b/services/room-service/internal/transport/grpc/server_test.go index b99dcbd9..ecc47486 100644 --- a/services/room-service/internal/transport/grpc/server_test.go +++ b/services/room-service/internal/transport/grpc/server_test.go @@ -45,6 +45,14 @@ func (fakeWallet) GrantResourceGroup(context.Context, *walletv1.GrantResourceGro return &walletv1.ResourceGrantResponse{Grant: &walletv1.ResourceGrant{GrantId: "grant-test"}}, nil } +// GetMyVip 返回显式 legacy program,避免 transport 测试把 VIP 配置缺失误判成房间协议错误。 +func (fakeWallet) GetMyVip(_ context.Context, req *walletv1.GetMyVipRequest) (*walletv1.GetMyVipResponse, error) { + return &walletv1.GetMyVipResponse{State: &walletv1.VipState{ + ProgramConfig: &walletv1.VipProgramConfig{AppCode: appcode.Default, ProgramType: "legacy_timed", Status: "active"}, + EffectiveVip: &walletv1.UserVip{UserId: req.GetUserId()}, + }}, nil +} + // newTransportTestServer 构造完整领域服务,确保测试覆盖真实 service -> transport 错误边界。 func newTransportTestServer(t *testing.T) *Server { t.Helper() diff --git a/services/statistics-service/cmd/backfill-last7/main.go b/services/statistics-service/cmd/backfill-last7/main.go index 3dbad996..2f28583e 100644 --- a/services/statistics-service/cmd/backfill-last7/main.go +++ b/services/statistics-service/cmd/backfill-last7/main.go @@ -352,12 +352,15 @@ func collectReportMetricUserIDs(ctx context.Context, walletDB *sql.DB, userDB *s }{ { db: walletDB, + // 历史持有人集合从 wallet_accounts 取:账户行在首笔流水时创建且从不删除, + // 与「wallet_entries 中出现过该资产」等价(提前开户的账户是无害超集,只影响维度预取)。 + // 千万级 wallet_entries 上按资产全量 DISTINCT 扫描曾是线上 MySQL 每 10 分钟的 CPU 尖峰主因。 query: ` - SELECT DISTINCT e.user_id - FROM wallet_entries e - WHERE e.app_code = ? AND e.asset_type IN ('COIN', 'COIN_SELLER_COIN', 'HOST_SALARY_USD', 'AGENCY_SALARY_USD') AND e.created_at_ms < ? + SELECT user_id + FROM wallet_accounts + WHERE app_code = ? AND asset_type IN ('COIN', 'COIN_SELLER_COIN', 'HOST_SALARY_USD', 'AGENCY_SALARY_USD') AND created_at_ms < ? UNION - SELECT DISTINCT e.counterparty_user_id + SELECT e.counterparty_user_id FROM wallet_entries e JOIN wallet_transactions wt ON wt.app_code = e.app_code AND wt.transaction_id = e.transaction_id WHERE e.app_code = ? AND wt.biz_type = 'salary_transfer_to_coin_seller' diff --git a/services/statistics-service/deploy/mysql/initdb/001_statistics_service.sql b/services/statistics-service/deploy/mysql/initdb/001_statistics_service.sql index 591b8f1d..23a95e0a 100644 --- a/services/statistics-service/deploy/mysql/initdb/001_statistics_service.sql +++ b/services/statistics-service/deploy/mysql/initdb/001_statistics_service.sql @@ -48,6 +48,7 @@ CREATE TABLE IF NOT EXISTS app_tracking_events ( KEY idx_app_tracking_user (app_code, user_id, occurred_at_ms), KEY idx_app_tracking_device (app_code, device_id, occurred_at_ms), KEY idx_app_tracking_event_window (app_code, event_name, occurred_at_ms, identity_key, device_id), + KEY idx_app_tracking_time_window (app_code, occurred_at_ms, event_name, identity_key, device_id), KEY idx_app_tracking_retention_identity (app_code, stat_day, identity_key) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='App 埋点原始明细表'; @@ -174,7 +175,8 @@ CREATE TABLE IF NOT EXISTS stat_social_user_day ( gift_panel_open TINYINT(1) NOT NULL DEFAULT 0 COMMENT '礼物面板打开去重标记', probability_game_panel_open TINYINT(1) NOT NULL DEFAULT 0 COMMENT '概率游戏面板打开去重标记', app_stay_ms BIGINT NOT NULL DEFAULT 0 COMMENT 'App 停留时长毫秒', - room_stay_ms BIGINT NOT NULL DEFAULT 0 COMMENT '房间停留时长毫秒', + room_stay_ms BIGINT NOT NULL DEFAULT 0 COMMENT '房间停留时长毫秒,客户端 room_stay 埋点口径', + room_online_ms BIGINT NOT NULL DEFAULT 0 COMMENT '房间在线时长毫秒,服务端进退房事实口径', mic_stay_ms BIGINT NOT NULL DEFAULT 0 COMMENT '麦上停留时长毫秒', recharge_count BIGINT NOT NULL DEFAULT 0 COMMENT '充值次数', recharge_usd_minor BIGINT NOT NULL DEFAULT 0 COMMENT '总充值 USD 最小单位', diff --git a/services/statistics-service/internal/app/app.go b/services/statistics-service/internal/app/app.go index 058ed454..b1b831f3 100644 --- a/services/statistics-service/internal/app/app.go +++ b/services/statistics-service/internal/app/app.go @@ -145,13 +145,26 @@ func newConsumers(cfg config.Config, repo *mysqlstorage.Repository) ([]*rocketmq }); err != nil { return nil, err } - if err := add(cfg.RocketMQ.WalletOutbox, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + // 统计服务需要多个钱包事实,但不应为未建模的 gift debit、红包等事件支付 + // 消费 TPS。兼容表达式继续接住存量 legacy 消息,新消息由 Broker 按下列 + // 精确事件集合过滤。 + walletTagExpression, err := walletmq.LegacyCompatibleTagExpression(statisticsWalletEventTypes()...) + if err != nil { + shutdownConsumers(consumers) + return nil, err + } + if err := add(cfg.RocketMQ.WalletOutbox, walletTagExpression, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { return ConsumeWalletOutboxBody(ctx, repo, message.Body) }); err != nil { shutdownConsumers(consumers) return nil, err } - if err := add(cfg.RocketMQ.RoomOutbox, roommq.TagRoomOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + roomTagExpression, err := roommq.LegacyCompatibleTagExpression(statisticsRoomEventTypes()...) + if err != nil { + shutdownConsumers(consumers) + return nil, err + } + if err := add(cfg.RocketMQ.RoomOutbox, roomTagExpression, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { return ConsumeRoomOutboxBody(ctx, repo, message.Body) }); err != nil { shutdownConsumers(consumers) @@ -166,6 +179,30 @@ func newConsumers(cfg config.Config, repo *mysqlstorage.Repository) ([]*rocketmq return consumers, nil } +func statisticsWalletEventTypes() []string { + return []string{ + walletmq.EventTypeWalletRechargeRecorded, + walletmq.EventTypeWalletCoinSellerStockPurchased, + walletmq.EventTypeWalletLuckyGiftRewardCredited, + walletmq.EventTypeWalletTaskRewardCredited, + walletmq.EventTypeWalletWheelRewardCredited, + walletmq.EventTypeWalletRoomTurnoverRewardCredited, + walletmq.EventTypeWalletInviteActivityRewardCredited, + walletmq.EventTypeWalletAgencyOpeningRewardCredited, + walletmq.EventTypeWalletGiftIncomeCredited, + walletmq.EventTypeWalletBalanceChanged, + } +} + +func statisticsRoomEventTypes() []string { + // 统计服务只把送礼和真人进房投影到本地聚合表。legacy 分支负责排空迁移前 + // 消息;producer 切 typed 后,其余高频房间状态不再占用统计消费 TPS。 + return []string{ + roommq.EventTypeRoomGiftSent, + roommq.EventTypeRoomUserJoined, + } +} + // ConsumeUserOutboxBody 是 user outbox 的唯一统计投影入口;实时 MQ 和离线回灌共用它,避免同一 payload 出现两套解析口径。 func ConsumeUserOutboxBody(ctx context.Context, repo *mysqlstorage.Repository, body []byte) error { if event, ok, err := userRegisteredEvent(body); err != nil || ok { @@ -180,11 +217,17 @@ func ConsumeUserOutboxBody(ctx context.Context, repo *mysqlstorage.Repository, b } return repo.ConsumeUserRegionChanged(appcode.WithContext(ctx, event.AppCode), event) } - event, ok, err := userMicDailyStatsUpdatedEvent(body) + if event, ok, err := userMicDailyStatsUpdatedEvent(body); err != nil || ok { + if err != nil || !ok { + return err + } + return repo.ConsumeUserMicDailyStatsUpdated(appcode.WithContext(ctx, event.AppCode), event) + } + event, ok, err := userRoomDailyStatsUpdatedEvent(body) if err != nil || !ok { return err } - return repo.ConsumeUserMicDailyStatsUpdated(appcode.WithContext(ctx, event.AppCode), event) + return repo.ConsumeUserRoomDailyStatsUpdated(appcode.WithContext(ctx, event.AppCode), event) } // ConsumeWalletOutboxBody 按钱包事实类型分派充值、币商补货、奖励和余额变动;未知事件只提交消费位点,不写统计读模型。 @@ -329,12 +372,31 @@ func userMicDailyStatsUpdatedEvent(body []byte) (mysqlstorage.UserMicDailyStatsU }, true, nil } +func userRoomDailyStatsUpdatedEvent(body []byte) (mysqlstorage.UserRoomDailyStatsUpdatedEvent, bool, error) { + message, err := usermq.DecodeUserOutboxMessage(body) + if err != nil { + return mysqlstorage.UserRoomDailyStatsUpdatedEvent{}, false, err + } + if message.EventType != "UserRoomDailyStatsUpdated" { + return mysqlstorage.UserRoomDailyStatsUpdatedEvent{}, false, nil + } + payload := mysqlstorage.DecodeJSON(message.PayloadJSON) + return mysqlstorage.UserRoomDailyStatsUpdatedEvent{ + AppCode: message.AppCode, + EventID: message.EventID, + UserID: firstNonZeroInt64(mysqlstorage.Int64(payload, "user_id"), message.AggregateID), + StatDate: mysqlstorage.String(payload, "stat_date"), + RoomOnlineDeltaMS: mysqlstorage.Int64(payload, "room_online_delta_ms"), + OccurredAtMS: firstNonZeroInt64(mysqlstorage.Int64(payload, "updated_at_ms"), message.OccurredAtMS), + }, true, nil +} + func rechargeEvent(body []byte) (mysqlstorage.RechargeEvent, bool, error) { message, err := walletmq.DecodeWalletOutboxMessage(body) if err != nil { return mysqlstorage.RechargeEvent{}, false, err } - if message.EventType != "WalletRechargeRecorded" { + if message.EventType != walletmq.EventTypeWalletRechargeRecorded { return mysqlstorage.RechargeEvent{}, false, nil } payload := mysqlstorage.DecodeJSON(message.PayloadJSON) @@ -366,7 +428,7 @@ func coinSellerStockEvent(body []byte) (mysqlstorage.CoinSellerStockEvent, bool, if err != nil { return mysqlstorage.CoinSellerStockEvent{}, false, err } - if message.EventType != "WalletCoinSellerStockPurchased" { + if message.EventType != walletmq.EventTypeWalletCoinSellerStockPurchased { return mysqlstorage.CoinSellerStockEvent{}, false, nil } payload := mysqlstorage.DecodeJSON(message.PayloadJSON) @@ -434,7 +496,7 @@ func luckyGiftRewardEvent(body []byte) (mysqlstorage.LuckyGiftRewardEvent, bool, if err != nil { return mysqlstorage.LuckyGiftRewardEvent{}, false, err } - if message.EventType != "WalletLuckyGiftRewardCredited" { + if message.EventType != walletmq.EventTypeWalletLuckyGiftRewardCredited { return mysqlstorage.LuckyGiftRewardEvent{}, false, nil } payload := mysqlstorage.DecodeJSON(message.PayloadJSON) @@ -456,7 +518,7 @@ func platformGrantCoinEvent(body []byte) (mysqlstorage.PlatformGrantCoinEvent, b if err != nil { return mysqlstorage.PlatformGrantCoinEvent{}, false, err } - if !isPlatformGrantCoinEventType(message.EventType) || message.EventType == "WalletLuckyGiftRewardCredited" { + if !isPlatformGrantCoinEventType(message.EventType) || message.EventType == walletmq.EventTypeWalletLuckyGiftRewardCredited { return mysqlstorage.PlatformGrantCoinEvent{}, false, nil } payload := mysqlstorage.DecodeJSON(message.PayloadJSON) @@ -477,7 +539,7 @@ func giftIncomeOutputEvent(body []byte) (mysqlstorage.CoinOutputEvent, bool, err if err != nil { return mysqlstorage.CoinOutputEvent{}, false, err } - if message.EventType != "WalletGiftIncomeCredited" { + if message.EventType != walletmq.EventTypeWalletGiftIncomeCredited { return mysqlstorage.CoinOutputEvent{}, false, nil } payload := mysqlstorage.DecodeJSON(message.PayloadJSON) @@ -498,7 +560,7 @@ func walletBalanceChangedEvent(body []byte) (mysqlstorage.WalletBalanceChangedEv if err != nil { return mysqlstorage.WalletBalanceChangedEvent{}, false, err } - if message.EventType != "WalletBalanceChanged" { + if message.EventType != walletmq.EventTypeWalletBalanceChanged { return mysqlstorage.WalletBalanceChangedEvent{}, false, nil } payload := mysqlstorage.DecodeJSON(message.PayloadJSON) @@ -528,11 +590,11 @@ func walletBalanceChangedEvent(body []byte) (mysqlstorage.WalletBalanceChangedEv func isPlatformGrantCoinEventType(value string) bool { switch strings.TrimSpace(value) { - case "WalletTaskRewardCredited", - "WalletWheelRewardCredited", - "WalletRoomTurnoverRewardCredited", - "WalletInviteActivityRewardCredited", - "WalletAgencyOpeningRewardCredited": + case walletmq.EventTypeWalletTaskRewardCredited, + walletmq.EventTypeWalletWheelRewardCredited, + walletmq.EventTypeWalletRoomTurnoverRewardCredited, + walletmq.EventTypeWalletInviteActivityRewardCredited, + walletmq.EventTypeWalletAgencyOpeningRewardCredited: return true default: return false diff --git a/services/statistics-service/internal/app/local_flow_test.go b/services/statistics-service/internal/app/local_flow_test.go index 8a99c381..437258b0 100644 --- a/services/statistics-service/internal/app/local_flow_test.go +++ b/services/statistics-service/internal/app/local_flow_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "runtime" + "strings" "testing" "time" @@ -17,6 +18,59 @@ import ( mysqlstorage "hyapp/services/statistics-service/internal/storage/mysql" ) +func TestStatisticsWalletTagExpressionMatchesModeledEvents(t *testing.T) { + eventTypes := statisticsWalletEventTypes() + want := map[string]struct{}{ + walletmq.EventTypeWalletRechargeRecorded: {}, + walletmq.EventTypeWalletCoinSellerStockPurchased: {}, + walletmq.EventTypeWalletLuckyGiftRewardCredited: {}, + walletmq.EventTypeWalletTaskRewardCredited: {}, + walletmq.EventTypeWalletWheelRewardCredited: {}, + walletmq.EventTypeWalletRoomTurnoverRewardCredited: {}, + walletmq.EventTypeWalletInviteActivityRewardCredited: {}, + walletmq.EventTypeWalletAgencyOpeningRewardCredited: {}, + walletmq.EventTypeWalletGiftIncomeCredited: {}, + walletmq.EventTypeWalletBalanceChanged: {}, + } + if len(eventTypes) != len(want) { + t.Fatalf("statistics wallet event count = %d, want %d: %v", len(eventTypes), len(want), eventTypes) + } + for _, eventType := range eventTypes { + if _, ok := want[eventType]; !ok { + t.Fatalf("statistics selector contains unmodeled event %q", eventType) + } + delete(want, eventType) + } + if len(want) != 0 { + t.Fatalf("statistics selector misses modeled events: %v", want) + } + expression, err := walletmq.LegacyCompatibleTagExpression(eventTypes...) + if err != nil { + t.Fatalf("LegacyCompatibleTagExpression failed: %v", err) + } + if !strings.Contains(expression, walletmq.TagWalletOutboxEvent) { + t.Fatalf("compatibility expression misses legacy tag: %q", expression) + } + if strings.Contains(expression, walletmq.EventTypeWalletGiftDebited) { + t.Fatalf("statistics selector must not receive high-volume gift debit facts: %q", expression) + } +} + +func TestStatisticsRoomTagExpressionOnlyIncludesModeledEvents(t *testing.T) { + expression, err := roommq.LegacyCompatibleTagExpression(statisticsRoomEventTypes()...) + if err != nil { + t.Fatalf("LegacyCompatibleTagExpression failed: %v", err) + } + for _, required := range []string{roommq.TagRoomOutboxEvent, roommq.EventTypeRoomGiftSent, roommq.EventTypeRoomUserJoined} { + if !strings.Contains(expression, required) { + t.Fatalf("statistics room selector %q misses %q", expression, required) + } + } + if strings.Contains(expression, "RoomHeatChanged") { + t.Fatalf("statistics room selector must not receive heat events: %q", expression) + } +} + // 这个用例只覆盖 statistics-service 对 owner outbox payload 的解析和聚合边界; // 本地真实业务链路用 scripts/databi-real-flow-check.sh 从各 owner service 的 outbox 重放验证。 func TestConsumeOutboxMessagesProjectsDataScreenMetrics(t *testing.T) { @@ -391,6 +445,132 @@ func TestConsumeWalletOutboxBodyProjectsBalanceChangedReportMetrics(t *testing.T } } +func TestGiftConsumptionUsesRoomFactAsCanonicalOwner(t *testing.T) { + ctx := context.Background() + _, file, _, _ := runtime.Caller(0) + schema := mysqlschema.New(t, mysqlschema.Config{ + InitDBPath: mysqlschema.InitDBPath(t, file, "../../deploy/mysql/initdb/001_statistics_service.sql"), + DatabasePrefix: "hy_stats_gift_canonical_flow", + }) + repo, err := mysqlstorage.Open(ctx, schema.DSN) + if err != nil { + t.Fatalf("open statistics repository failed: %v", err) + } + t.Cleanup(func() { _ = repo.Close() }) + + occurredAt := time.Date(2026, 7, 11, 2, 0, 0, 0, time.UTC).UnixMilli() + walletBody := walletMessage(t, walletmq.WalletOutboxMessage{ + AppCode: "lalu", + EventID: "WalletBalanceChanged:gift:canonical-1", + EventType: walletmq.EventTypeWalletBalanceChanged, + TransactionID: "wallet_tx_gift_canonical_1", + CommandID: "gift-canonical-1", + UserID: 88001, + AssetType: "COIN", + AvailableDelta: -120, + PayloadJSON: mustJSON(t, map[string]any{ + "user_id": int64(88001), + "asset_type": "COIN", + "biz_type": "gift_debit", + "available_delta": int64(-120), + "country_id": int64(15), + "region_id": int64(10), + "created_at_ms": occurredAt, + }), + OccurredAtMS: occurredAt, + }) + if err := ConsumeWalletOutboxBody(ctx, repo, walletBody); err != nil { + t.Fatalf("consume wallet gift balance fact failed: %v", err) + } + if err := ConsumeRoomOutboxBody(ctx, repo, roomGiftMessage(t, "lalu", "RoomGiftSent:canonical-1", "room-1", occurredAt, &roomeventsv1.RoomGiftSent{ + SenderUserId: 88001, + TargetUserId: 88002, + GiftId: "gift-1", + GiftCount: 12, + GiftValue: 120, + CoinSpent: 120, + CountryId: 15, + VisibleRegionId: 10, + })); err != nil { + t.Fatalf("consume canonical room gift fact failed: %v", err) + } + // 多目标/微批钱包路径使用 gift_debit;它和 direct_gift_debit 一样只能更新 + // coin_total,消费额仍由对应 target 的 RoomGiftSent 事实负责。 + batchWalletBody := walletMessage(t, walletmq.WalletOutboxMessage{ + AppCode: "lalu", + EventID: "WalletBalanceChanged:gift:canonical-2", + EventType: walletmq.EventTypeWalletBalanceChanged, + TransactionID: "wallet_tx_gift_canonical_2", + CommandID: "gift-canonical-2", + UserID: 88001, + AssetType: "COIN", + AvailableDelta: -80, + PayloadJSON: mustJSON(t, map[string]any{ + "user_id": int64(88001), + "asset_type": "COIN", + "biz_type": "gift_debit", + "available_delta": int64(-80), + "country_id": int64(15), + "region_id": int64(10), + "created_at_ms": occurredAt, + }), + OccurredAtMS: occurredAt, + }) + if err := ConsumeWalletOutboxBody(ctx, repo, batchWalletBody); err != nil { + t.Fatalf("consume batch wallet gift balance fact failed: %v", err) + } + if err := ConsumeRoomOutboxBody(ctx, repo, roomGiftMessage(t, "lalu", "RoomGiftSent:canonical-2", "room-1", occurredAt, &roomeventsv1.RoomGiftSent{ + SenderUserId: 88001, + TargetUserId: 88003, + GiftId: "gift-2", + GiftCount: 8, + GiftValue: 80, + CoinSpent: 80, + CountryId: 15, + VisibleRegionId: 10, + })); err != nil { + t.Fatalf("consume canonical batch room gift fact failed: %v", err) + } + // 私聊礼物没有 RoomGiftSent;direct_gift_debit 必须由钱包事件补计,否则消费总额永久漏失。 + directWalletBody := walletMessage(t, walletmq.WalletOutboxMessage{ + AppCode: "lalu", + EventID: "WalletBalanceChanged:direct-gift:canonical-3", + EventType: walletmq.EventTypeWalletBalanceChanged, + TransactionID: "wallet_tx_direct_gift_canonical_3", + CommandID: "direct-gift-canonical-3", + UserID: 88001, + AssetType: "COIN", + AvailableDelta: -55, + PayloadJSON: mustJSON(t, map[string]any{ + "user_id": int64(88001), + "asset_type": "COIN", + "biz_type": "direct_gift_debit", + "available_delta": int64(-55), + "country_id": int64(15), + "region_id": int64(10), + "created_at_ms": occurredAt, + }), + OccurredAtMS: occurredAt, + }) + if err := ConsumeWalletOutboxBody(ctx, repo, directWalletBody); err != nil { + t.Fatalf("consume direct gift wallet fact failed: %v", err) + } + + dayStart := time.Date(2026, 7, 11, 0, 0, 0, 0, time.UTC).UnixMilli() + overview, err := repo.QueryOverview(ctx, mysqlstorage.OverviewQuery{ + AppCode: "lalu", + StartMS: dayStart, + EndMS: dayStart + int64(24*time.Hour/time.Millisecond), + CountryID: 15, + }) + if err != nil { + t.Fatalf("query canonical gift overview failed: %v", err) + } + if overview.CoinTotal != -255 || overview.GiftCoinSpent != 200 || overview.ConsumedCoin != 255 { + t.Fatalf("room gifts must use RoomGiftSent once while direct gifts use wallet once: %+v", overview) + } +} + func TestRoomJoinActiveEventSkipsRobotJoin(t *testing.T) { occurredAt := time.Date(2026, 6, 25, 8, 0, 0, 0, time.UTC).UnixMilli() diff --git a/services/statistics-service/internal/app/query_http.go b/services/statistics-service/internal/app/query_http.go index e731b8e2..79d36a84 100644 --- a/services/statistics-service/internal/app/query_http.go +++ b/services/statistics-service/internal/app/query_http.go @@ -11,6 +11,7 @@ import ( "time" "hyapp/pkg/appcode" + "hyapp/pkg/apptracking" mysqlstorage "hyapp/services/statistics-service/internal/storage/mysql" ) @@ -185,16 +186,17 @@ func (s *queryHTTPServer) socialRequirements(w http.ResponseWriter, r *http.Requ endMS = startMS + 24*time.Hour.Milliseconds() } requirements, err := s.repo.QuerySocialRequirements(r.Context(), mysqlstorage.SocialRequirementsQuery{ - AppCode: query.Get("app_code"), - StatTZ: query.Get("stat_tz"), - StartMS: startMS, - EndMS: endMS, - CountryID: parseInt64(query.Get("country_id")), - RegionID: parseInt64(query.Get("region_id")), - RegionIDs: parseInt64CSV(query.Get("region_ids")), - Section: query.Get("section"), - UserRole: query.Get("user_role"), - PayerType: query.Get("payer_type"), + AppCode: query.Get("app_code"), + StatTZ: query.Get("stat_tz"), + StartMS: startMS, + EndMS: endMS, + CountryID: parseInt64(query.Get("country_id")), + RegionID: parseInt64(query.Get("region_id")), + RegionIDs: parseInt64CSV(query.Get("region_ids")), + Section: query.Get("section"), + UserRole: query.Get("user_role"), + PayerType: query.Get("payer_type"), + NewUserType: query.Get("new_user_type"), }) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]any{"error": err.Error()}) @@ -325,7 +327,7 @@ func (s *queryHTTPServer) appEvents(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid json"}) return } - if len(body.Events) == 0 || len(body.Events) > 50 { + if !apptracking.ValidBatchSize(len(body.Events)) { writeJSON(w, http.StatusBadRequest, map[string]any{"error": "events batch is invalid"}) return } diff --git a/services/statistics-service/internal/storage/mysql/query.go b/services/statistics-service/internal/storage/mysql/query.go index f91432ee..2cf79deb 100644 --- a/services/statistics-service/internal/storage/mysql/query.go +++ b/services/statistics-service/internal/storage/mysql/query.go @@ -752,17 +752,13 @@ func (r *Repository) QueryAppTrackingFunnel(ctx context.Context, query AppTracki } out.Steps = buildAppTrackingFunnelSteps(steps) out.Totals = appTrackingTotals(out.Steps) - d1Base, d1Users, err := r.queryAppTrackingD1Total(ctx, app, startMS, endMS, query.CountryID, query.RegionID, out.RegionIDs) + d1Base, d1Users, cohorts, err := r.queryAppTrackingD1Cohorts(ctx, app, startMS, endMS, query.CountryID, query.RegionID, out.RegionIDs) if err != nil { return AppTrackingFunnel{}, err } out.Totals.D1RetentionBaseUsers = d1Base out.Totals.D1RetentionUsers = d1Users out.Totals.D1RetentionRate = ratio(d1Users, d1Base) - cohorts, err := r.queryAppTrackingD1Cohorts(ctx, app, startMS, endMS, query.CountryID, query.RegionID, out.RegionIDs) - if err != nil { - return AppTrackingFunnel{}, err - } out.Cohorts = cohorts return out, nil } @@ -2097,7 +2093,7 @@ func (r *Repository) queryAppTrackingFunnelSteps(ctx context.Context, app string } rows, err := r.db.QueryContext(ctx, ` SELECT event_name, COUNT(*), COUNT(DISTINCT `+appTrackingIdentitySQL("")+`), COUNT(DISTINCT NULLIF(device_id, '')) - FROM app_tracking_events + FROM app_tracking_events FORCE INDEX (idx_app_tracking_time_window) WHERE `+filter+` AND event_name IN (`+placeholders+`) GROUP BY event_name`, args...) if err != nil { @@ -2172,97 +2168,119 @@ func appTrackingTotals(steps []AppTrackingFunnelStep) AppTrackingFunnelTotals { } } -func (r *Repository) queryAppTrackingD1Total(ctx context.Context, app string, startMS, endMS int64, countryID int64, regionID int64, regionIDs []int64) (int64, int64, error) { - filter, args := appTrackingFilter(app, startMS, endMS, countryID, regionID, regionIDs) +func (r *Repository) queryAppTrackingD1Cohorts(ctx context.Context, app string, startMS, endMS int64, countryID int64, regionID int64, regionIDs []int64) (int64, int64, []AppTrackingFunnelCohort, error) { + filter, filterArgs := appTrackingFilter(app, startMS, endMS, countryID, regionID, regionIDs) + totalArgs := append([]any{}, filterArgs...) + dimensionSQL, dimensionArgs := appTrackingCohortUnionSQL(appTrackingD1CohortSourceSQL, filter, filterArgs, "source.stat_day, source.identity, source.value") + args := append(totalArgs, dimensionArgs...) args = append(args, app) - row := r.db.QueryRowContext(ctx, ` - WITH base AS ( + // retention_base 对每个 stat_day+identity 只判断一次次日活跃,再与五个维度关联;旧实现会为每个维度重复执行同一 EXISTS。 + rows, err := r.db.QueryContext(ctx, ` + WITH total_base AS ( SELECT DISTINCT stat_day, `+appTrackingIdentitySQL("")+` AS identity FROM app_tracking_events WHERE `+filter+` AND event_name = 'login_success' + ), + dimension_base AS (`+dimensionSQL+`), + retention_base AS ( + SELECT total_base.stat_day, total_base.identity, + CASE WHEN EXISTS ( + SELECT 1 FROM app_tracking_events n + WHERE n.app_code = ? AND n.stat_day = DATE_ADD(total_base.stat_day, INTERVAL 1 DAY) + AND `+appTrackingIdentitySQL("n")+` = total_base.identity + LIMIT 1 + ) THEN 1 ELSE 0 END AS retained + FROM total_base + ), + dimension_aggregated AS ( + SELECT dimension_base.dimension, dimension_base.value, COUNT(*) AS base_users, + COALESCE(SUM(retention_base.retained), 0) AS retained_users + FROM dimension_base + JOIN retention_base ON retention_base.stat_day = dimension_base.stat_day + AND retention_base.identity = dimension_base.identity + GROUP BY dimension_base.dimension, dimension_base.value + ), + dimension_ranked AS ( + SELECT dimension, value, base_users, retained_users, + ROW_NUMBER() OVER (PARTITION BY dimension ORDER BY base_users DESC, value ASC) AS dimension_rank + FROM dimension_aggregated ) - SELECT COUNT(*), - COALESCE(SUM(CASE WHEN EXISTS ( - SELECT 1 FROM app_tracking_events n - WHERE n.app_code = ? AND n.stat_day = DATE_ADD(base.stat_day, INTERVAL 1 DAY) - AND `+appTrackingIdentitySQL("n")+` = base.identity - LIMIT 1 - ) THEN 1 ELSE 0 END),0) - FROM base`, args...) - var baseUsers, retainedUsers int64 - if err := row.Scan(&baseUsers, &retainedUsers); err != nil { - return 0, 0, err - } - return baseUsers, retainedUsers, nil -} - -func (r *Repository) queryAppTrackingD1Cohorts(ctx context.Context, app string, startMS, endMS int64, countryID int64, regionID int64, regionIDs []int64) ([]AppTrackingFunnelCohort, error) { - out := []AppTrackingFunnelCohort{} - for _, def := range appTrackingCohortDefs { - rows, err := r.queryAppTrackingD1CohortDimension(ctx, app, startMS, endMS, countryID, regionID, regionIDs, def) - if err != nil { - return nil, err - } - stepCounts, err := r.queryAppTrackingCohortSteps(ctx, app, startMS, endMS, countryID, regionID, regionIDs, def) - if err != nil { - return nil, err - } - for index := range rows { - rows[index].Steps = buildAppTrackingCohortSteps(stepCounts[rows[index].Value]) - } - out = append(out, rows...) - } - return out, nil -} - -func (r *Repository) queryAppTrackingD1CohortDimension(ctx context.Context, app string, startMS, endMS int64, countryID int64, regionID int64, regionIDs []int64, def appTrackingCohortDef) ([]AppTrackingFunnelCohort, error) { - filter, filterArgs := appTrackingFilter(app, startMS, endMS, countryID, regionID, regionIDs) - sourceSQL := appTrackingD1CohortSourceSQL(def.Key, filter) - args := appTrackingCohortSourceArgs(def.Key, filterArgs) - args = append(args, app) - rows, err := r.db.QueryContext(ctx, ` - WITH base AS (`+sourceSQL+`) - SELECT value, COUNT(*), - COALESCE(SUM(CASE WHEN EXISTS ( - SELECT 1 FROM app_tracking_events n - WHERE n.app_code = ? AND n.stat_day = DATE_ADD(base.stat_day, INTERVAL 1 DAY) - AND `+appTrackingIdentitySQL("n")+` = base.identity - LIMIT 1 - ) THEN 1 ELSE 0 END),0) - FROM base - GROUP BY value - ORDER BY COUNT(*) DESC, value ASC - LIMIT 50`, args...) + SELECT '' AS dimension, '' AS value, COUNT(*) AS base_users, + COALESCE(SUM(retained), 0) AS retained_users, 0 AS dimension_rank + FROM retention_base + UNION ALL + SELECT dimension, value, base_users, retained_users, dimension_rank + FROM dimension_ranked + WHERE dimension_rank <= 50 + ORDER BY dimension ASC, dimension_rank ASC`, args...) if err != nil { - return nil, err + return 0, 0, nil, err } defer rows.Close() - out := []AppTrackingFunnelCohort{} + var baseUsers int64 + var retainedUsers int64 + byDimension := map[string][]AppTrackingFunnelCohort{} + labels := map[string]string{} + for _, def := range appTrackingCohortDefs { + labels[def.Key] = def.Label + } for rows.Next() { + var dimension string var value sql.NullString - var baseUsers, retainedUsers int64 - if err := rows.Scan(&value, &baseUsers, &retainedUsers); err != nil { - return nil, err + var rowBaseUsers, rowRetainedUsers, dimensionRank int64 + if err := rows.Scan(&dimension, &value, &rowBaseUsers, &rowRetainedUsers, &dimensionRank); err != nil { + return 0, 0, nil, err + } + if dimension == "" { + baseUsers = rowBaseUsers + retainedUsers = rowRetainedUsers + continue } cohortValue := normalizeCohortValue(value.String) - out = append(out, AppTrackingFunnelCohort{ - Dimension: def.Key, - DimensionLabel: def.Label, + byDimension[dimension] = append(byDimension[dimension], AppTrackingFunnelCohort{ + Dimension: dimension, + DimensionLabel: labels[dimension], Value: cohortValue, Label: cohortValue, - BaseUsers: baseUsers, - D1RetentionUsers: retainedUsers, - D1RetentionRate: ratio(retainedUsers, baseUsers), + BaseUsers: rowBaseUsers, + D1RetentionUsers: rowRetainedUsers, + D1RetentionRate: ratio(rowRetainedUsers, rowBaseUsers), Steps: []AppTrackingCohortStepCount{}, }) } - return out, rows.Err() + if err := rows.Err(); err != nil { + return 0, 0, nil, err + } + stepCounts, err := r.queryAppTrackingCohortSteps(ctx, app, startMS, endMS, countryID, regionID, regionIDs) + if err != nil { + return 0, 0, nil, err + } + out := []AppTrackingFunnelCohort{} + for _, def := range appTrackingCohortDefs { + for _, cohort := range byDimension[def.Key] { + cohort.Steps = buildAppTrackingCohortSteps(stepCounts[def.Key][cohort.Value]) + out = append(out, cohort) + } + } + return baseUsers, retainedUsers, out, nil } -func (r *Repository) queryAppTrackingCohortSteps(ctx context.Context, app string, startMS, endMS int64, countryID int64, regionID int64, regionIDs []int64, def appTrackingCohortDef) (map[string]map[string]int64, error) { +type appTrackingCohortSourceBuilder func(dimension string, filter string) string + +func appTrackingCohortUnionSQL(builder appTrackingCohortSourceBuilder, filter string, filterArgs []any, columns string) (string, []any) { + parts := make([]string, 0, len(appTrackingCohortDefs)) + args := []any{} + for _, def := range appTrackingCohortDefs { + parts = append(parts, "SELECT ? AS dimension, "+columns+" FROM ("+builder(def.Key, filter)+") source") + args = append(args, def.Key) + args = append(args, appTrackingCohortSourceArgs(def.Key, filterArgs)...) + } + return strings.Join(parts, "\nUNION ALL\n"), args +} + +func (r *Repository) queryAppTrackingCohortSteps(ctx context.Context, app string, startMS, endMS int64, countryID int64, regionID int64, regionIDs []int64) (map[string]map[string]map[string]int64, error) { filter, filterArgs := appTrackingFilter(app, startMS, endMS, countryID, regionID, regionIDs) - cohortSQL := appTrackingStepCohortSourceSQL(def.Key, filter) - args := appTrackingCohortSourceArgs(def.Key, filterArgs) + cohortSQL, args := appTrackingCohortUnionSQL(appTrackingStepCohortSourceSQL, filter, filterArgs, "source.identity, source.value") eventFilter, eventArgs := appTrackingFilter(app, startMS, endMS, countryID, regionID, regionIDs) args = append(args, eventArgs...) names := appTrackingEventNames() @@ -2274,30 +2292,35 @@ func (r *Repository) queryAppTrackingCohortSteps(ctx context.Context, app string WITH cohort AS (`+cohortSQL+`), events AS ( SELECT DISTINCT `+appTrackingIdentitySQL("")+` AS identity, event_name - FROM app_tracking_events + -- 同一窗口会覆盖几乎全部漏斗事件;强制时间优先覆盖索引,避免优化器只按 app_code 扫描主键后再回表去重。 + FROM app_tracking_events FORCE INDEX (idx_app_tracking_time_window) WHERE `+eventFilter+` AND event_name IN (`+placeholders+`) ) - SELECT cohort.value, events.event_name, COUNT(DISTINCT cohort.identity) + SELECT cohort.dimension, cohort.value, events.event_name, COUNT(DISTINCT cohort.identity) FROM cohort JOIN events ON events.identity = cohort.identity - GROUP BY cohort.value, events.event_name`, args...) + GROUP BY cohort.dimension, cohort.value, events.event_name`, args...) if err != nil { return nil, err } defer rows.Close() - out := map[string]map[string]int64{} + out := map[string]map[string]map[string]int64{} for rows.Next() { + var dimension string var value sql.NullString var eventName string var userCount int64 - if err := rows.Scan(&value, &eventName, &userCount); err != nil { + if err := rows.Scan(&dimension, &value, &eventName, &userCount); err != nil { return nil, err } cohortValue := normalizeCohortValue(value.String) - if out[cohortValue] == nil { - out[cohortValue] = map[string]int64{} + if out[dimension] == nil { + out[dimension] = map[string]map[string]int64{} } - out[cohortValue][eventName] = userCount + if out[dimension][cohortValue] == nil { + out[dimension][cohortValue] = map[string]int64{} + } + out[dimension][cohortValue][eventName] = userCount } return out, rows.Err() } diff --git a/services/statistics-service/internal/storage/mysql/query_test.go b/services/statistics-service/internal/storage/mysql/query_test.go index 433f9878..8f9d6c69 100644 --- a/services/statistics-service/internal/storage/mysql/query_test.go +++ b/services/statistics-service/internal/storage/mysql/query_test.go @@ -13,8 +13,20 @@ import ( "github.com/DATA-DOG/go-sqlmock" "hyapp/internal/testutil/mysqlschema" "hyapp/pkg/appcode" + "hyapp/pkg/apptracking" ) +func TestGiftDebitConsumptionOwnersDoNotOverlap(t *testing.T) { + if !isConsumedCoinBizType("direct_gift_debit") || isRoomGiftDebitBizType("direct_gift_debit") { + t.Fatal("direct gift has no RoomGiftSent and must be consumed from wallet only") + } + for _, roomBizType := range []string{"gift_debit", "robot_gift_debit"} { + if isConsumedCoinBizType(roomBizType) || !isRoomGiftDebitBizType(roomBizType) { + t.Fatalf("room gift %s must be excluded from wallet consumption and owned by room facts", roomBizType) + } + } +} + func TestQueryOverviewUsesActivityLuckyGiftDayStats(t *testing.T) { ctx := context.Background() _, file, _, _ := runtime.Caller(0) @@ -84,6 +96,31 @@ func TestConsumeAppTrackingEventsStoresRawEventsAndDeduplicates(t *testing.T) { } t.Cleanup(func() { _ = repository.Close() }) + // 漏斗读取按 App+毫秒窗口裁剪,索引顺序属于性能契约;event_name 放在时间前会让全事件漏斗退化为按 App 扫描。 + indexRows, err := repository.db.QueryContext(ctx, ` + SELECT COLUMN_NAME + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'app_tracking_events' AND INDEX_NAME = 'idx_app_tracking_time_window' + ORDER BY SEQ_IN_INDEX`) + if err != nil { + t.Fatalf("query app tracking time window index: %v", err) + } + var indexColumns []string + for indexRows.Next() { + var column string + if err := indexRows.Scan(&column); err != nil { + _ = indexRows.Close() + t.Fatalf("scan app tracking time window index: %v", err) + } + indexColumns = append(indexColumns, column) + } + if err := indexRows.Close(); err != nil { + t.Fatalf("close app tracking time window index rows: %v", err) + } + if got, want := strings.Join(indexColumns, ","), "app_code,occurred_at_ms,event_name,identity_key,device_id"; got != want { + t.Fatalf("app tracking time window index mismatch: got=%s want=%s", got, want) + } + occurredAt := time.Date(2026, 7, 2, 23, 59, 59, 0, time.UTC).UnixMilli() result, err := repository.ConsumeAppTrackingEvents(appcode.WithContext(ctx, "lalu"), []AppTrackingEvent{ { @@ -183,10 +220,17 @@ func TestQueryAppTrackingFunnelAggregatesStepsAndD1Cohorts(t *testing.T) { } } user1Props := `{"country":"US","channel":"ads","login_method":"google"}` + user1AffiliateProps := `{"country":"US","channel":"affiliate","login_method":"google"}` user2Props := `{"country":"BR","channel":"organic","login_method":"phone"}` + affiliateLogin := event(1, "dev-1", "login_success", 1500*time.Millisecond, user1AffiliateProps) + affiliateLogin.EventID += "-affiliate" + retainedOutsideFilter := event(1, "dev-1", "send_message", 24*time.Hour+time.Second, user1Props) + // D1 表示次日任意活跃,不能继承首日查询的区域过滤,否则用户次日跨区会被错误判定流失。 + retainedOutsideFilter.RegionID = 999 events := []AppTrackingEvent{ event(1, "dev-1", "login_start", 0, user1Props), event(1, "dev-1", "login_success", time.Second, user1Props), + affiliateLogin, event(1, "dev-1", "profile_start", 2*time.Second, user1Props), event(1, "dev-1", "profile_complete", 3*time.Second, user1Props), event(1, "dev-1", "home_room_impression", 4*time.Second, user1Props), @@ -197,7 +241,7 @@ func TestQueryAppTrackingFunnelAggregatesStepsAndD1Cohorts(t *testing.T) { event(1, "dev-1", "send_message", 4*time.Minute, user1Props), event(1, "dev-1", "follow_host", 5*time.Minute, user1Props), event(1, "dev-1", "push_permission", 6*time.Minute, user1Props), - event(1, "dev-1", "send_message", 24*time.Hour+time.Second, user1Props), + retainedOutsideFilter, event(2, "dev-2", "login_start", 0, user2Props), event(2, "dev-2", "login_success", time.Second, user2Props), event(2, "dev-2", "home_room_impression", 2*time.Second, user2Props), @@ -252,6 +296,25 @@ func TestQueryAppTrackingFunnelAggregatesStepsAndD1Cohorts(t *testing.T) { if us.BaseUsers != 1 || us.D1RetentionUsers != 1 || math.Abs(us.D1RetentionRate-1) > 0.0001 { t.Fatalf("unexpected US cohort: %+v", us) } + cohortSteps := func(item AppTrackingFunnelCohort) map[string]int64 { + steps := map[string]int64{} + for _, step := range item.Steps { + steps[step.EventName] = step.UserCount + } + return steps + } + usSteps := cohortSteps(us) + if usSteps["login_success"] != 1 || usSteps["room_join_success"] != 1 || usSteps["send_message"] != 1 { + t.Fatalf("unexpected US cohort steps: %+v", usSteps) + } + brSteps := cohortSteps(cohort("country", "BR")) + if brSteps["login_success"] != 1 || brSteps["room_join_success"] != 0 || brSteps["room_join_fail"] != 1 { + t.Fatalf("unexpected BR cohort steps: %+v", brSteps) + } + // 同一 identity+day 的相同国家只计一次;同日出现两个渠道值时,各渠道仍按既有口径分别计入。 + if cohort("country", "US").BaseUsers != 1 || cohort("channel", "ads").BaseUsers != 1 || cohort("channel", "affiliate").BaseUsers != 1 { + t.Fatalf("multi-value cohort deduplication mismatch: %+v", funnel.Cohorts) + } stay := cohort("first_room_stay", "3m-10m") if stay.BaseUsers != 1 || stay.D1RetentionUsers != 1 { t.Fatalf("unexpected stay cohort: %+v", stay) @@ -349,6 +412,14 @@ func TestQuerySocialRequirementsAggregatesP0ToP5(t *testing.T) { }); err != nil { t.Fatalf("consume self game: %v", err) } + // day2 再押一次 dice:用于验证按游戏的同游戏次留(day1 cohort 1 人,day2 继续押注 → 100%)。 + if err := repository.ConsumeSelfGameMatch(ctx, SelfGameMatchEvent{ + AppCode: "lalu", EventID: "social:self-game:settled-day2", EventType: "SelfGameMatchSettled", GameID: "dice", CountryID: 86, RegionID: 210, + StakeCoin: 100, UserParticipants: 1, UserStakeCoin: 100, Participants: []SelfGameMatchParticipantEvent{{UserID: user1, ParticipantType: "user", StakeCoin: 100, Result: "lose"}}, + OccurredAtMS: start.Add(24*time.Hour + 30*time.Minute).UnixMilli(), + }); err != nil { + t.Fatalf("consume self game day2: %v", err) + } query := SocialRequirementsQuery{ AppCode: "lalu", @@ -366,14 +437,20 @@ func TestQuerySocialRequirementsAggregatesP0ToP5(t *testing.T) { socialMetricInt(t, newUsers.Total, "new_host_users") != 1 || socialMetricInt(t, newUsers.Total, "new_normal_users") != 1 || math.Abs(socialMetricFloat(t, newUsers.Total, "room_join_success_rate")-0.5) > 0.0001 || - math.Abs(socialMetricFloat(t, newUsers.Total, "new_host_d1_retention_rate")-1) > 0.0001 { + math.Abs(socialMetricFloat(t, newUsers.Total, "new_host_d1_retention_rate")-1) > 0.0001 || + // 新用户平均进房/上麦时长只统计当天注册用户:room_stay 300s/1人=5min,mic 120s/1人=2min。 + math.Abs(socialMetricFloat(t, newUsers.Total, "avg_room_stay_min")-5) > 0.0001 || + math.Abs(socialMetricFloat(t, newUsers.Total, "avg_mic_stay_min")-2) > 0.0001 || + // 新用户充值金额=全渠道:user1 普通充值 1200 + user2 币商入账 900。 + socialMetricInt(t, newUsers.Total, "new_user_recharge_usd_minor") != 2100 { t.Fatalf("P0 metrics mismatch: %+v", newUsers.Total.Metrics) } revenue := socialTestSection(t, result, SocialSectionRevenue) + // 文档口径:总充值 2100 = Google 500 + 三方(MiFaPay) 700 + 币商(finance verified) 900。 if socialMetricInt(t, revenue.Total, "recharge_usd_minor") != 2100 || socialMetricInt(t, revenue.Total, "google_recharge_usd_minor") != 500 || - socialMetricInt(t, revenue.Total, "third_party_recharge_usd_minor") != 900 || + socialMetricInt(t, revenue.Total, "third_party_recharge_usd_minor") != 700 || socialMetricInt(t, revenue.Total, "coin_seller_recharge_usd_minor") != 900 || socialMetricInt(t, revenue.Total, "recharge_count") != 3 || socialMetricInt(t, revenue.Total, "recharge_users") != 1 || @@ -386,9 +463,10 @@ func TestQuerySocialRequirementsAggregatesP0ToP5(t *testing.T) { t.Fatalf("query revenue requirements: %v", err) } paidRevenueSection := socialTestSection(t, paidRevenue, SocialSectionRevenue) + // paid 过滤只剩 user1:Google 500 + MiFaPay(三方) 700;user2 的币商订单不置付费标记被排除。 if socialMetricInt(t, paidRevenueSection.Total, "recharge_usd_minor") != 1200 || socialMetricInt(t, paidRevenueSection.Total, "google_recharge_usd_minor") != 500 || - socialMetricInt(t, paidRevenueSection.Total, "third_party_recharge_usd_minor") != 0 || + socialMetricInt(t, paidRevenueSection.Total, "third_party_recharge_usd_minor") != 700 || socialMetricInt(t, paidRevenueSection.Total, "recharge_count") != 2 || socialMetricInt(t, paidRevenueSection.Total, "first_recharge_users") != 1 || socialMetricInt(t, paidRevenueSection.Total, "repeat_recharge_users") != 1 || @@ -397,7 +475,10 @@ func TestQuerySocialRequirementsAggregatesP0ToP5(t *testing.T) { } retention := socialTestSection(t, result, SocialSectionRetention) - if math.Abs(socialMetricFloat(t, retention.Total, "registered_d1_active_rate")-0.5) > 0.0001 || + // 用户次留按全体活跃用户 cohort:day1 活跃 user1+user2,day2 仅 user1 回访 → 0.5。 + if socialMetricInt(t, retention.Total, "active_d1_active_base_users") != 2 || + socialMetricInt(t, retention.Total, "active_d1_active_users") != 1 || + math.Abs(socialMetricFloat(t, retention.Total, "active_d1_active_rate")-0.5) > 0.0001 || math.Abs(socialMetricFloat(t, retention.Total, "after_room_d1_room_rate")-1) > 0.0001 { t.Fatalf("P2 retention metrics mismatch: %+v", retention.Total.Metrics) } @@ -424,6 +505,60 @@ func TestQuerySocialRequirementsAggregatesP0ToP5(t *testing.T) { math.Abs(socialMetricFloat(t, game.Total, "probability_game_bet_arppu_coin")-300) > 0.0001 { t.Fatalf("P5 metrics mismatch: %+v", game.Total.Metrics) } + + // 按游戏明细:dice 自研(day1 押 400,day2 继续押 → 同游戏次留 100%),slot 厂商(300,无次留)。 + if len(game.Games) != 2 { + t.Fatalf("expected 2 game breakdown rows, got %+v", game.Games) + } + gameByName := map[string]SocialRequirementGame{} + for _, item := range game.Games { + gameByName[item.GameName] = item + } + dice, ok := gameByName["dice"] + if !ok || + socialMetricInt(t, dice.Total, "game_bet_users") != 1 || + socialMetricInt(t, dice.Total, "game_bet_coin") != 400 || + math.Abs(socialMetricFloat(t, dice.Total, "game_arppu_coin")-400) > 0.0001 || + math.Abs(socialMetricFloat(t, dice.Total, "game_d1_retention_rate")-1) > 0.0001 { + t.Fatalf("dice breakdown mismatch: %+v", dice.Total.Metrics) + } + slot, ok := gameByName["slot (vendor)"] + if !ok || + socialMetricInt(t, slot.Total, "game_bet_users") != 1 || + socialMetricInt(t, slot.Total, "game_bet_coin") != 300 || + math.Abs(socialMetricFloat(t, slot.Total, "game_d1_retention_rate")) > 0.0001 { + t.Fatalf("slot breakdown mismatch: %+v", slot.Total.Metrics) + } + if len(game.GameColumns) == 0 { + t.Fatalf("expected game breakdown columns") + } + + // 是否新用户筛选:day1 两人都是当天注册 → new=2、old=0。 + newUserActive, err := repository.QuerySocialRequirements(ctx, SocialRequirementsQuery{AppCode: "lalu", StartMS: query.StartMS, EndMS: query.EndMS, RegionIDs: []int64{210}, Section: SocialSectionActive, NewUserType: "new"}) + if err != nil { + t.Fatalf("query new-user active requirements: %v", err) + } + if socialMetricInt(t, socialTestSection(t, newUserActive, SocialSectionActive).Total, "active_users") != 2 { + t.Fatalf("new-user filtered DAU mismatch: %+v", socialTestSection(t, newUserActive, SocialSectionActive).Total.Metrics) + } + oldUserActive, err := repository.QuerySocialRequirements(ctx, SocialRequirementsQuery{AppCode: "lalu", StartMS: query.StartMS, EndMS: query.EndMS, RegionIDs: []int64{210}, Section: SocialSectionActive, NewUserType: "old"}) + if err != nil { + t.Fatalf("query old-user active requirements: %v", err) + } + if socialMetricInt(t, socialTestSection(t, oldUserActive, SocialSectionActive).Total, "active_users") != 0 { + t.Fatalf("old-user filtered DAU mismatch: %+v", socialTestSection(t, oldUserActive, SocialSectionActive).Total.Metrics) + } + + // 平均值行:两天窗口 day1 活跃 2 人、day2 活跃 1 人(user1 home_view)→ 汇总 3 人天、日均 1.5。 + twoDay, err := repository.QuerySocialRequirements(ctx, SocialRequirementsQuery{AppCode: "lalu", StartMS: query.StartMS, EndMS: start.Add(48 * time.Hour).UnixMilli(), RegionIDs: []int64{210}, Section: SocialSectionActive}) + if err != nil { + t.Fatalf("query two-day active requirements: %v", err) + } + twoDayActive := socialTestSection(t, twoDay, SocialSectionActive) + if socialMetricInt(t, twoDayActive.Total, "active_users") != 3 || + math.Abs(socialMetricFloat(t, twoDayActive.Average, "active_users")-1.5) > 0.0001 { + t.Fatalf("average row mismatch: total=%+v average=%+v", twoDayActive.Total.Metrics, twoDayActive.Average.Metrics) + } } func socialTestSection(t *testing.T, result SocialRequirements, key string) SocialRequirementSection { @@ -493,7 +628,7 @@ func TestConsumeAppTrackingEventsRejectsInvalidProperties(t *testing.T) { properties json.RawMessage }{ {name: "invalid json", properties: json.RawMessage(`{"bad"`)}, - {name: "too large json", properties: json.RawMessage(`"` + strings.Repeat("a", appTrackingMaxPropertiesBytes+1) + `"`)}, + {name: "too large json", properties: json.RawMessage(`"` + strings.Repeat("a", apptracking.MaxPropertiesBytes+1) + `"`)}, } { t.Run(test.name, func(t *testing.T) { _, err := repository.ConsumeAppTrackingEvents(appcode.WithContext(ctx, "lalu"), []AppTrackingEvent{ @@ -506,6 +641,29 @@ func TestConsumeAppTrackingEventsRejectsInvalidProperties(t *testing.T) { } } +func TestNormalizeAppTrackingEventRejects185ByteErrorCodeWithoutTruncation(t *testing.T) { + base := AppTrackingEvent{ + AppCode: "lalu", + EventID: "evt-error-code-limit", + EventName: "pay_failed", + DeviceID: "dev-1", + OccurredAtMS: time.Date(2026, 7, 13, 0, 0, 0, 0, time.UTC).UnixMilli(), + } + base.ErrorCode = strings.Repeat("e", apptracking.MaxErrorCodeBytes) + normalized, _, err := normalizeAppTrackingEvent(context.Background(), base, base.OccurredAtMS) + if err != nil { + t.Fatalf("error_code at shared byte limit must remain valid: %v", err) + } + if normalized.ErrorCode != base.ErrorCode { + t.Fatalf("valid error_code must be stored intact: got=%q want=%q", normalized.ErrorCode, base.ErrorCode) + } + + base.ErrorCode = strings.Repeat("e", 185) + if _, _, err := normalizeAppTrackingEvent(context.Background(), base, base.OccurredAtMS); err == nil { + t.Fatal("185-byte error_code must be rejected instead of truncated or deferred to MySQL") + } +} + func TestConsumeUserRegisteredCountsOnlyNewRegistration(t *testing.T) { ctx := context.Background() _, file, _, _ := runtime.Caller(0) diff --git a/services/statistics-service/internal/storage/mysql/repository.go b/services/statistics-service/internal/storage/mysql/repository.go index bd8e1ce3..560ad93c 100644 --- a/services/statistics-service/internal/storage/mysql/repository.go +++ b/services/statistics-service/internal/storage/mysql/repository.go @@ -1,7 +1,6 @@ package mysql import ( - "bytes" "context" "database/sql" "encoding/json" @@ -11,6 +10,7 @@ import ( mysqlerr "github.com/go-sql-driver/mysql" "hyapp/pkg/appcode" + "hyapp/pkg/apptracking" "hyapp/pkg/xerr" ) @@ -23,11 +23,6 @@ const ( SourceFinance = "finance" ) -const ( - appTrackingMaxBatchSize = 50 - appTrackingMaxPropertiesBytes = 4096 -) - // Repository owns the statistics read model. Online queries must use aggregate // tables; the optional activity connection only reads lucky-gift day snapshots, // never the high-cardinality draw fact table. @@ -139,6 +134,7 @@ func (r *Repository) Migrate(ctx context.Context) error { KEY idx_app_tracking_user (app_code, user_id, occurred_at_ms), KEY idx_app_tracking_device (app_code, device_id, occurred_at_ms), KEY idx_app_tracking_event_window (app_code, event_name, occurred_at_ms, identity_key, device_id), + KEY idx_app_tracking_time_window (app_code, occurred_at_ms, event_name, identity_key, device_id), KEY idx_app_tracking_retention_identity (app_code, stat_day, identity_key) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, `CREATE TABLE IF NOT EXISTS stat_app_day_country ( @@ -204,7 +200,7 @@ func (r *Repository) Migrate(ctx context.Context) error { game_bet_user TINYINT(1) NOT NULL DEFAULT 0, self_game_bet_user TINYINT(1) NOT NULL DEFAULT 0, probability_game_bet_user TINYINT(1) NOT NULL DEFAULT 0, chat_sent TINYINT(1) NOT NULL DEFAULT 0, follow_user TINYINT(1) NOT NULL DEFAULT 0, follow_room TINYINT(1) NOT NULL DEFAULT 0, gift_panel_open TINYINT(1) NOT NULL DEFAULT 0, probability_game_panel_open TINYINT(1) NOT NULL DEFAULT 0, - app_stay_ms BIGINT NOT NULL DEFAULT 0, room_stay_ms BIGINT NOT NULL DEFAULT 0, mic_stay_ms BIGINT NOT NULL DEFAULT 0, + app_stay_ms BIGINT NOT NULL DEFAULT 0, room_stay_ms BIGINT NOT NULL DEFAULT 0, room_online_ms BIGINT NOT NULL DEFAULT 0, mic_stay_ms BIGINT NOT NULL DEFAULT 0, recharge_count BIGINT NOT NULL DEFAULT 0, recharge_usd_minor BIGINT NOT NULL DEFAULT 0, google_recharge_usd_minor BIGINT NOT NULL DEFAULT 0, third_party_recharge_usd_minor BIGINT NOT NULL DEFAULT 0, coin_seller_recharge_usd_minor BIGINT NOT NULL DEFAULT 0, first_recharge_usd_minor BIGINT NOT NULL DEFAULT 0, repeat_recharge_usd_minor BIGINT NOT NULL DEFAULT 0, @@ -396,6 +392,7 @@ func (r *Repository) Migrate(ctx context.Context) error { `ALTER TABLE stat_app_day_country ADD COLUMN mic_online_ms BIGINT NOT NULL DEFAULT 0 AFTER salary_transfer_coin`, `ALTER TABLE stat_app_day_country ADD COLUMN mic_online_users BIGINT NOT NULL DEFAULT 0 AFTER mic_online_ms`, `ALTER TABLE stat_game_day_country ADD COLUMN refund_coin BIGINT NOT NULL DEFAULT 0 AFTER payout_coin`, + `ALTER TABLE stat_social_user_day ADD COLUMN room_online_ms BIGINT NOT NULL DEFAULT 0 AFTER room_stay_ms`, `ALTER TABLE stat_app_day_country ADD COLUMN region_id BIGINT NOT NULL DEFAULT 0 AFTER country_id`, `ALTER TABLE stat_user_day_activity ADD COLUMN region_id BIGINT NOT NULL DEFAULT 0 AFTER country_id`, `ALTER TABLE stat_user_registration ADD COLUMN region_id BIGINT NOT NULL DEFAULT 0 AFTER country_id`, @@ -456,6 +453,8 @@ func (r *Repository) Migrate(ctx context.Context) error { {"app_tracking_events", "idx_app_tracking_user", []string{"app_code", "user_id", "occurred_at_ms"}}, {"app_tracking_events", "idx_app_tracking_device", []string{"app_code", "device_id", "occurred_at_ms"}}, {"app_tracking_events", "idx_app_tracking_event_window", []string{"app_code", "event_name", "occurred_at_ms", "identity_key", "device_id"}}, + // 漏斗步骤会一次读取全部事件名;时间列必须紧跟 app_code,避免 event_name IN 覆盖大多数事件时退化为按 App 扫描主键。 + {"app_tracking_events", "idx_app_tracking_time_window", []string{"app_code", "occurred_at_ms", "event_name", "identity_key", "device_id"}}, {"app_tracking_events", "idx_app_tracking_retention_identity", []string{"app_code", "stat_day", "identity_key"}}, {"stat_app_day_country", "idx_stat_app_day_region", []string{"app_code", "stat_tz", "stat_day", "region_id"}}, {"stat_user_day_activity", "idx_stat_user_day_country", []string{"app_code", "stat_tz", "stat_day", "country_id"}}, @@ -707,6 +706,15 @@ type UserMicDailyStatsUpdatedEvent struct { OccurredAtMS int64 } +type UserRoomDailyStatsUpdatedEvent struct { + AppCode string + EventID string + UserID int64 + StatDate string + RoomOnlineDeltaMS int64 + OccurredAtMS int64 +} + type UserActiveEvent struct { AppCode string EventID string @@ -944,12 +952,16 @@ func (r *Repository) ConsumeRecharge(ctx context.Context, event RechargeEvent) e `, event.USDMinor, newUserUSD, coinSellerUSD, mifapayUSD, googleUSD, coinSellerTransferCoin, paidUsers, nowMS, appcode.Normalize(event.AppCode), scope.tz, scope.day, countryID, regionID); err != nil { return err } + // 数据需求文档口径:总充值 = Google + 第三方 + 币商。第三方= MiFaPay/V5Pay/USDT 等 + // 非 Google、非币商的支付渠道;币商美元金额走 finance verified 订单链路单独进入币商列。 googleUSD, thirdPartyUSD, coinSellerUSD := int64(0), int64(0), int64(0) switch strings.ToLower(strings.TrimSpace(event.RechargeType)) { case "google", "google_play": googleUSD = event.USDMinor case "coin_seller_transfer", "coin_seller": coinSellerUSD = event.USDMinor + default: + thirdPartyUSD = event.USDMinor } firstRecharge, repeatRecharge, firstUSD, repeatUSD := int64(0), int64(0), int64(0), int64(0) if event.RechargeSequence == 1 { @@ -997,8 +1009,8 @@ func (r *Repository) ConsumeFinanceCoinSellerRechargeOrder(ctx context.Context, } countryID, regionID := normalizeDimension(event.CountryID, event.RegionID) for _, scope := range statDayScopesFromContext(ctx, occurredAtMS) { - // Social BI 的第三方充值口径绑定 finance 工作台 verified 币商订单:金额和次数进入营收, - // 但不置 recharge_user/first/repeat 标记,避免财务补单把付费率和 ARPPU 分母放大。 + // finance 工作台 verified 币商订单只进入总充值和币商充值列(文档:币商充值=财务-代理充值), + // 不再重复写第三方列;也不置 recharge_user/first/repeat 标记,避免财务补单把付费率和 ARPPU 分母放大。 if err := applySocialUserDayDelta(ctx, tx, socialUserDayDelta{ AppCode: event.AppCode, StatTZ: scope.tz, @@ -1009,7 +1021,6 @@ func (r *Repository) ConsumeFinanceCoinSellerRechargeOrder(ctx context.Context, UpdatedAt: nowMS, RechargeCount: 1, RechargeUSDMinor: event.USDMinor, - ThirdPartyRechargeUSDMinor: event.USDMinor, CoinSellerRechargeUSDMinor: event.USDMinor, }); err != nil { return err @@ -1306,7 +1317,9 @@ func (r *Repository) ConsumeWalletBalanceChanged(ctx context.Context, event Wall bizType := strings.ToLower(strings.TrimSpace(event.BizType)) if assetType == "COIN" { coinTotal = totalDelta - if totalDelta < 0 && isConsumedCoinBizType(bizType) { + // 房间 gift_debit 由 RoomGiftSent 计消费,钱包事件只滚动余额;direct_gift_debit + // 是私聊礼物且没有 RoomGiftSent,必须由钱包事实补计,不能按名称一概排除。 + if totalDelta < 0 && !isRoomGiftDebitBizType(bizType) && isConsumedCoinBizType(bizType) { consumed = -totalDelta } if totalDelta > 0 && isOutputCoinBizType(bizType) { @@ -1349,9 +1362,6 @@ func (r *Repository) ConsumeWalletBalanceChanged(ctx context.Context, event Wall return err } otherConsume := consumed - if bizType == "direct_gift_debit" { - otherConsume = 0 - } if otherConsume > 0 || outputCoin > 0 { if err := applySocialUserDayDelta(ctx, tx, socialUserDayDelta{ AppCode: event.AppCode, @@ -1428,17 +1438,20 @@ func (r *Repository) ConsumeUserMicDailyStatsUpdated(ctx context.Context, event } micDelta := after - before if micDelta > 0 { + // 上麦必然已在房内:麦时长事实同时置进房成功标志,避免客户端 room_join 埋点丢失时 + // 出现"有上麦却无进房"的倒挂行。 if err := applySocialUserDayDelta(ctx, tx, socialUserDayDelta{ - AppCode: event.AppCode, - StatTZ: scope.tz, - StatDay: scope.day, - UserID: event.UserID, - CountryID: countryID, - RegionID: regionID, - UpdatedAt: nowMS, - Active: 1, - MicUp: 1, - MicStayMS: micDelta, + AppCode: event.AppCode, + StatTZ: scope.tz, + StatDay: scope.day, + UserID: event.UserID, + CountryID: countryID, + RegionID: regionID, + UpdatedAt: nowMS, + Active: 1, + RoomJoinOK: 1, + MicUp: 1, + MicStayMS: micDelta, }); err != nil { return err } @@ -1448,6 +1461,40 @@ func (r *Repository) ConsumeUserMicDailyStatsUpdated(ctx context.Context, event }) } +func (r *Repository) ConsumeUserRoomDailyStatsUpdated(ctx context.Context, event UserRoomDailyStatsUpdatedEvent) error { + return r.withEvent(ctx, SourceUser, event.EventID, "UserRoomDailyStatsUpdated", func(tx *sql.Tx, nowMS int64) error { + if event.RoomOnlineDeltaMS <= 0 || strings.TrimSpace(event.StatDate) == "" { + return nil + } + countryID, regionID, err := r.dimensionForUser(ctx, tx, event.AppCode, event.UserID, 0, 0) + if err != nil { + return err + } + for _, scope := range statDateScopesFromContext(ctx, event.StatDate) { + if err := ensureAppDay(ctx, tx, event.AppCode, scope.tz, scope.day, countryID, regionID, nowMS); err != nil { + return err + } + // 服务端在房事实落独立列 room_online_ms,不直接累加 room_stay_ms: + // 客户端 room_stay 埋点仍在上报,两条来源同时累加同一列会双计;查询口径取 GREATEST 兜数据断层。 + if err := applySocialUserDayDelta(ctx, tx, socialUserDayDelta{ + AppCode: event.AppCode, + StatTZ: scope.tz, + StatDay: scope.day, + UserID: event.UserID, + CountryID: countryID, + RegionID: regionID, + UpdatedAt: nowMS, + Active: 1, + RoomJoinOK: 1, + RoomOnlineMS: event.RoomOnlineDeltaMS, + }); err != nil { + return err + } + } + return nil + }) +} + func (r *Repository) ConsumeUserActive(ctx context.Context, event UserActiveEvent) error { return r.withEvent(ctx, SourceRoom, event.EventID, event.EventType, func(tx *sql.Tx, nowMS int64) error { countryID, regionID := normalizeDimension(event.CountryID, event.RegionID) @@ -1618,7 +1665,7 @@ func (r *Repository) ConsumeAppTrackingEvents(ctx context.Context, events []AppT } nowMS := time.Now().UTC().UnixMilli() result := AppTrackingReportResult{Accepted: true, Received: len(events), ServerTimeMS: nowMS} - if len(events) == 0 || len(events) > appTrackingMaxBatchSize { + if !apptracking.ValidBatchSize(len(events)) { return AppTrackingReportResult{}, xerr.New(xerr.InvalidArgument, "app tracking events batch is invalid") } tx, err := r.db.BeginTx(ctx, nil) @@ -2053,7 +2100,18 @@ func (r *Repository) dimensionForUser(ctx context.Context, tx *sql.Tx, app strin func isConsumedCoinBizType(value string) bool { switch strings.ToLower(strings.TrimSpace(value)) { - case "direct_gift_debit", "red_packet_create", "wheel_draw_debit": + // vip_purchase / resource_shop_purchase 对应文档消费口径里的"购买VIP、买商城装扮", + // 私聊礼物没有房间事实,direct_gift_debit 必须在钱包消费者计入;房间 gift_debit 仍由 RoomGiftSent 负责。 + case "red_packet_create", "wheel_draw_debit", "vip_purchase", "resource_shop_purchase", "direct_gift_debit": + return true + default: + return false + } +} + +func isRoomGiftDebitBizType(value string) bool { + switch strings.ToLower(strings.TrimSpace(value)) { + case "gift_debit", "robot_gift_debit": return true default: return false @@ -2266,62 +2324,65 @@ func normalizeAppTrackingEvent(ctx context.Context, event AppTrackingEvent, rece if strings.TrimSpace(event.AppCode) == "" { event.AppCode = appcode.FromContext(ctx) } - event.AppCode = appcode.Normalize(event.AppCode) - event.EventID, err = normalizeAppTrackingText(event.EventID, 160) + event.AppCode, err = normalizeAppTrackingText(appcode.Normalize(event.AppCode), apptracking.MaxAppCodeBytes) if err != nil { return AppTrackingEvent{}, "", err } - event.EventName, err = normalizeAppTrackingText(event.EventName, 96) + event.EventID, err = normalizeAppTrackingText(event.EventID, apptracking.MaxEventIDBytes) + if err != nil { + return AppTrackingEvent{}, "", err + } + event.EventName, err = normalizeAppTrackingText(event.EventName, apptracking.MaxEventNameBytes) if err != nil { return AppTrackingEvent{}, "", err } if event.EventID == "" || event.EventName == "" { return AppTrackingEvent{}, "", xerr.New(xerr.InvalidArgument, "app tracking event_id and event_name are required") } - event.EventType, err = normalizeAppTrackingText(event.EventType, 64) + event.EventType, err = normalizeAppTrackingText(event.EventType, apptracking.MaxEventTypeBytes) if err != nil { return AppTrackingEvent{}, "", err } - event.Screen, err = normalizeAppTrackingText(event.Screen, 128) + event.Screen, err = normalizeAppTrackingText(event.Screen, apptracking.MaxScreenBytes) if err != nil { return AppTrackingEvent{}, "", err } - event.TargetType, err = normalizeAppTrackingText(event.TargetType, 64) + event.TargetType, err = normalizeAppTrackingText(event.TargetType, apptracking.MaxTargetTypeBytes) if err != nil { return AppTrackingEvent{}, "", err } - event.TargetID, err = normalizeAppTrackingText(event.TargetID, 128) + event.TargetID, err = normalizeAppTrackingText(event.TargetID, apptracking.MaxTargetIDBytes) if err != nil { return AppTrackingEvent{}, "", err } - event.DeviceID, err = normalizeAppTrackingText(event.DeviceID, 128) + event.DeviceID, err = normalizeAppTrackingText(event.DeviceID, apptracking.MaxDeviceIDBytes) if err != nil { return AppTrackingEvent{}, "", err } if event.UserID <= 0 && event.DeviceID == "" { return AppTrackingEvent{}, "", xerr.New(xerr.InvalidArgument, "anonymous app tracking event requires device_id") } - event.SessionID, err = normalizeAppTrackingText(event.SessionID, 160) + event.SessionID, err = normalizeAppTrackingText(event.SessionID, apptracking.MaxSessionIDBytes) if err != nil { return AppTrackingEvent{}, "", err } - event.Platform, err = normalizeAppTrackingText(strings.ToLower(event.Platform), 32) + event.Platform, err = normalizeAppTrackingText(strings.ToLower(event.Platform), apptracking.MaxPlatformBytes) if err != nil { return AppTrackingEvent{}, "", err } - event.AppVersion, err = normalizeAppTrackingText(event.AppVersion, 64) + event.AppVersion, err = normalizeAppTrackingText(event.AppVersion, apptracking.MaxAppVersionBytes) if err != nil { return AppTrackingEvent{}, "", err } - event.Language, err = normalizeAppTrackingText(event.Language, 32) + event.Language, err = normalizeAppTrackingText(event.Language, apptracking.MaxLanguageBytes) if err != nil { return AppTrackingEvent{}, "", err } - event.Timezone, err = normalizeAppTrackingText(event.Timezone, 64) + event.Timezone, err = normalizeAppTrackingText(event.Timezone, apptracking.MaxTimezoneBytes) if err != nil { return AppTrackingEvent{}, "", err } - event.ErrorCode, err = normalizeAppTrackingText(event.ErrorCode, 64) + event.ErrorCode, err = normalizeAppTrackingText(event.ErrorCode, apptracking.MaxErrorCodeBytes) if err != nil { return AppTrackingEvent{}, "", err } @@ -2339,21 +2400,21 @@ func normalizeAppTrackingEvent(ctx context.Context, event AppTrackingEvent, rece } func normalizeAppTrackingText(value string, maxBytes int) (string, error) { - value = strings.TrimSpace(value) - if maxBytes > 0 && len(value) > maxBytes { + value, ok := apptracking.NormalizeText(value, maxBytes) + if !ok { return "", xerr.New(xerr.InvalidArgument, "app tracking text field is too long") } return value, nil } func normalizeAppTrackingProperties(raw json.RawMessage) (string, error) { - trimmed := bytes.TrimSpace(raw) - if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) { - return "{}", nil - } - if len(trimmed) > appTrackingMaxPropertiesBytes || !json.Valid(trimmed) { + trimmed, ok := apptracking.NormalizeProperties(raw) + if !ok { return "", xerr.New(xerr.InvalidArgument, "app tracking properties_json is invalid") } + if len(trimmed) == 0 { + return "{}", nil + } return string(trimmed), nil } diff --git a/services/statistics-service/internal/storage/mysql/social_requirements_query.go b/services/statistics-service/internal/storage/mysql/social_requirements_query.go index 68dbfb5b..7ffeb34e 100644 --- a/services/statistics-service/internal/storage/mysql/social_requirements_query.go +++ b/services/statistics-service/internal/storage/mysql/social_requirements_query.go @@ -21,16 +21,17 @@ const ( ) type SocialRequirementsQuery struct { - AppCode string - StatTZ string - StartMS int64 - EndMS int64 - CountryID int64 - RegionID int64 - RegionIDs []int64 - Section string - UserRole string - PayerType string + AppCode string + StatTZ string + StartMS int64 + EndMS int64 + CountryID int64 + RegionID int64 + RegionIDs []int64 + Section string + UserRole string + PayerType string + NewUserType string } type SocialRequirements struct { @@ -51,7 +52,20 @@ type SocialRequirementSection struct { Columns []SocialRequirementColumn `json:"columns"` MetricDefinitions []MetricDefinition `json:"metric_definitions"` Total SocialRequirementRow `json:"total"` + Average SocialRequirementRow `json:"average"` DailySeries []SocialRequirementRow `json:"daily_series"` + // P5 游戏 section 附带的按游戏明细:来自 stat_self_game_user_day / stat_game_day_players 投影, + // 不受用户身份/付费身份/是否新用户筛选影响(厂商游戏无按用户金额,无法一致地过滤)。 + GameColumns []SocialRequirementColumn `json:"game_columns,omitempty"` + Games []SocialRequirementGame `json:"games,omitempty"` +} + +type SocialRequirementGame struct { + GameKey string `json:"game_key"` + GameSource string `json:"game_source"` + GameName string `json:"game_name"` + Total SocialRequirementRow `json:"total"` + DailySeries []SocialRequirementRow `json:"daily_series"` } type SocialRequirementColumn struct { @@ -62,8 +76,9 @@ type SocialRequirementColumn struct { } type SocialRequirementFilter struct { - UserRole string `json:"user_role"` - PayerType string `json:"payer_type"` + UserRole string `json:"user_role"` + PayerType string `json:"payer_type"` + NewUserType string `json:"new_user_type"` } type SocialRequirementRow struct { @@ -111,6 +126,7 @@ type socialRequirementUserDay struct { ProbabilityPanel int64 AppStayMS int64 RoomStayMS int64 + RoomOnlineMS int64 MicStayMS int64 RechargeCount int64 RechargeUSDMinor int64 @@ -148,6 +164,14 @@ type socialRequirementAggregate struct { RegisteredRoomGift int64 RegisteredRecharge int64 RegisteredGame int64 + RegisteredFollowUser int64 + RegisteredFollowRoom int64 + RegisteredChat int64 + RegisteredAppStayMS int64 + RegisteredRoomStayMS int64 + RegisteredMicStayMS int64 + RegisteredRechargeUSDMinor int64 + ConsumeUsers int64 MicUp int64 Gift int64 NormalGift int64 @@ -218,6 +242,16 @@ func (r *Repository) QuerySocialRequirements(ctx context.Context, query SocialRe } byDay, bySubjectDay := indexSocialRows(rows) sections := socialRequestedSections(query.Section) + var gameBreakdown []SocialRequirementGame + for _, section := range sections { + if section == SocialSectionGame { + gameBreakdown, err = r.querySocialGameBreakdown(ctx, app, statTZ, days, query.CountryID, query.RegionID, query.RegionIDs) + if err != nil { + return SocialRequirements{}, err + } + break + } + } out := SocialRequirements{ AppCode: app, StatTZ: statTZ, @@ -233,9 +267,9 @@ func (r *Repository) QuerySocialRequirements(ctx context.Context, query SocialRe totalAgg := socialRequirementAggregate{} totalRetention := map[string]retentionMetric{} for _, day := range days { - agg := aggregateSocialRows(byDay[day], section, query.UserRole, query.PayerType) + agg := aggregateSocialRows(byDay[day], section, query.UserRole, query.PayerType, query.NewUserType) totalAgg.add(agg) - retention := socialRetentionMetrics(day, byDay[day], bySubjectDay, section, query.UserRole, query.PayerType) + retention := socialRetentionMetrics(day, byDay[day], bySubjectDay, section, query.UserRole, query.PayerType, query.NewUserType) mergeRetentionMetrics(totalRetention, retention) sectionRows = append(sectionRows, SocialRequirementRow{ StatDay: day, @@ -243,19 +277,55 @@ func (r *Repository) QuerySocialRequirements(ctx context.Context, query SocialRe Metrics: socialSectionMetrics(section, agg, retention), }) } - out.Sections = append(out.Sections, SocialRequirementSection{ - Key: section, - Label: socialSectionLabel(section), - Filters: SocialRequirementFilter{UserRole: normalizeSocialRequirementRole(query.UserRole), PayerType: normalizeSocialPayerType(query.PayerType)}, + sectionOut := SocialRequirementSection{ + Key: section, + Label: socialSectionLabel(section), + Filters: SocialRequirementFilter{ + UserRole: normalizeSocialRequirementRole(query.UserRole), + PayerType: normalizeSocialPayerType(query.PayerType), + NewUserType: normalizeSocialNewUserType(query.NewUserType), + }, Columns: socialRequirementColumns(section), MetricDefinitions: socialRequirementMetricDefinitions(section), Total: SocialRequirementRow{StatDay: "total", Label: "汇总", Metrics: socialSectionMetrics(section, totalAgg, totalRetention)}, + Average: socialAverageRow(sectionRows), DailySeries: sectionRows, - }) + } + if section == SocialSectionGame { + sectionOut.GameColumns = socialGameBreakdownColumns() + sectionOut.Games = gameBreakdown + } + out.Sections = append(out.Sections, sectionOut) } return out, nil } +// socialAverageRow 输出按天算术平均的"平均值"行:每个指标对有值的天求均值。 +// 计数/金额类是"日均量",比例类是"日均率"(与汇总行的整段合并口径互补)。 +func socialAverageRow(rows []SocialRequirementRow) SocialRequirementRow { + sums := map[string]float64{} + counts := map[string]int{} + for _, row := range rows { + for key, value := range row.Metrics { + switch v := value.(type) { + case int64: + sums[key] += float64(v) + counts[key]++ + case float64: + sums[key] += v + counts[key]++ + } + } + } + metrics := make(map[string]any, len(sums)) + for key, sum := range sums { + if counts[key] > 0 { + metrics[key] = sum / float64(counts[key]) + } + } + return SocialRequirementRow{StatDay: "average", Label: "平均值", Metrics: metrics} +} + func (r *Repository) querySocialUserDays(ctx context.Context, app string, statTZ string, startDay string, endDay string, countryID int64, regionID int64, regionIDs []int64) ([]socialRequirementUserDay, error) { args := []any{appcode.Normalize(app), normalizeStatTZ(statTZ), startDay, endDay} conditions := []string{"app_code = ?", "stat_tz = ?", "stat_day BETWEEN ? AND ?"} @@ -280,7 +350,7 @@ func (r *Repository) querySocialUserDays(ctx context.Context, app string, statTZ mic_up_success, gift_sent, normal_gift_sent, lucky_gift_sent, super_lucky_gift_sent, recharge_user, first_recharge_user, repeat_recharge_user, game_bet_user, self_game_bet_user, probability_game_bet_user, chat_sent, follow_user, follow_room, gift_panel_open, probability_game_panel_open, - app_stay_ms, room_stay_ms, mic_stay_ms, recharge_count, recharge_usd_minor, + app_stay_ms, room_stay_ms, room_online_ms, mic_stay_ms, recharge_count, recharge_usd_minor, google_recharge_usd_minor, third_party_recharge_usd_minor, coin_seller_recharge_usd_minor, first_recharge_usd_minor, repeat_recharge_usd_minor, normal_gift_coin, lucky_gift_coin, super_lucky_gift_coin, lucky_gift_payout_coin, super_lucky_gift_payout_coin, game_bet_coin, game_bet_count, @@ -302,7 +372,7 @@ func (r *Repository) querySocialUserDays(ctx context.Context, app string, statTZ &item.MicUp, &item.Gift, &item.NormalGift, &item.LuckyGift, &item.SuperGift, &item.Recharge, &item.FirstRecharge, &item.RepeatRecharge, &item.GameBet, &item.SelfGameBet, &item.ProbabilityBet, &item.Chat, &item.FollowUser, &item.FollowRoom, &item.GiftPanel, &item.ProbabilityPanel, - &item.AppStayMS, &item.RoomStayMS, &item.MicStayMS, &item.RechargeCount, &item.RechargeUSDMinor, + &item.AppStayMS, &item.RoomStayMS, &item.RoomOnlineMS, &item.MicStayMS, &item.RechargeCount, &item.RechargeUSDMinor, &item.GoogleRechargeUSDMinor, &item.ThirdPartyRechargeUSDMinor, &item.CoinSellerRechargeUSDMinor, &item.FirstRechargeUSDMinor, &item.RepeatRechargeUSDMinor, &item.NormalGiftCoin, &item.LuckyGiftCoin, &item.SuperGiftCoin, &item.LuckyGiftPayoutCoin, &item.SuperGiftPayoutCoin, &item.GameBetCoin, &item.GameBetCount, @@ -310,6 +380,16 @@ func (r *Repository) querySocialUserDays(ctx context.Context, app string, statTZ &item.OtherConsumeCoin, &item.OutputCoin); err != nil { return nil, err } + // 在房时长口径:客户端 room_stay 埋点(杀进程/闪退整段丢失)和服务端进退房事实 room_online_ms + // 双轨并存,取 GREATEST 避免切换期数据断层;埋点按 app 版本灰度下线后该口径自然收敛到服务端事实。 + if item.RoomStayMS < item.RoomOnlineMS { + item.RoomStayMS = item.RoomOnlineMS + } + // 在房时长下界修正:上麦必在房内,麦时长(服务端事实,权威)是房时长的天然下界。 + // 服务端在房事实上线后保留该钳制作为兜底(如回灌历史区间尚无服务端事实)。 + if item.RoomStayMS < item.MicStayMS { + item.RoomStayMS = item.MicStayMS + } out = append(out, item) } return out, rows.Err() @@ -328,10 +408,10 @@ func indexSocialRows(rows []socialRequirementUserDay) (map[string][]socialRequir return byDay, bySubjectDay } -func aggregateSocialRows(rows []socialRequirementUserDay, section string, role string, payerType string) socialRequirementAggregate { +func aggregateSocialRows(rows []socialRequirementUserDay, section string, role string, payerType string, newUserType string) socialRequirementAggregate { var agg socialRequirementAggregate for _, row := range rows { - if !socialRowMatchesSection(row, section, role, payerType) { + if !socialRowMatchesSection(row, section, role, payerType, newUserType) { continue } agg.AppFirstOpen += flagValue(row.AppFirstOpen) @@ -362,6 +442,26 @@ func aggregateSocialRows(rows []socialRequirementUserDay, section string, role s if row.Registered > 0 && row.GameBet > 0 { agg.RegisteredGame++ } + // P0 的时长/关注/聊天/充值金额按文档限定「当天注册的新用户」,不能混入全体用户。 + if row.Registered > 0 { + if row.FollowUser > 0 { + agg.RegisteredFollowUser++ + } + if row.FollowRoom > 0 { + agg.RegisteredFollowRoom++ + } + if row.Chat > 0 { + agg.RegisteredChat++ + } + agg.RegisteredAppStayMS += row.AppStayMS + agg.RegisteredRoomStayMS += row.RoomStayMS + agg.RegisteredMicStayMS += row.MicStayMS + agg.RegisteredRechargeUSDMinor += row.RechargeUSDMinor + } + // 消费渗透率的分子是「有任一金币消耗行为」的去重用户(送礼/游戏/其他消费),不能把各行为人数直接相加。 + if row.Gift > 0 || row.GameBet > 0 || row.OtherConsumeCoin > 0 { + agg.ConsumeUsers++ + } agg.MicUp += flagValue(row.MicUp) agg.Gift += flagValue(row.Gift) agg.NormalGift += flagValue(row.NormalGift) @@ -419,6 +519,14 @@ func (a *socialRequirementAggregate) add(other socialRequirementAggregate) { a.RegisteredRoomGift += other.RegisteredRoomGift a.RegisteredRecharge += other.RegisteredRecharge a.RegisteredGame += other.RegisteredGame + a.RegisteredFollowUser += other.RegisteredFollowUser + a.RegisteredFollowRoom += other.RegisteredFollowRoom + a.RegisteredChat += other.RegisteredChat + a.RegisteredAppStayMS += other.RegisteredAppStayMS + a.RegisteredRoomStayMS += other.RegisteredRoomStayMS + a.RegisteredMicStayMS += other.RegisteredMicStayMS + a.RegisteredRechargeUSDMinor += other.RegisteredRechargeUSDMinor + a.ConsumeUsers += other.ConsumeUsers a.MicUp += other.MicUp a.Gift += other.Gift a.NormalGift += other.NormalGift @@ -460,15 +568,17 @@ func (a *socialRequirementAggregate) add(other socialRequirementAggregate) { a.OutputCoin += other.OutputCoin } -func socialRetentionMetrics(day string, rows []socialRequirementUserDay, bySubjectDay map[string]map[string]socialRequirementUserDay, section string, role string, payerType string) map[string]retentionMetric { +func socialRetentionMetrics(day string, rows []socialRequirementUserDay, bySubjectDay map[string]map[string]socialRequirementUserDay, section string, role string, payerType string, newUserType string) map[string]retentionMetric { out := map[string]retentionMetric{} for _, row := range rows { - if !socialRowMatchesSection(row, section, role, payerType) { + if !socialRowMatchesSection(row, section, role, payerType, newUserType) { continue } + // 文档口径:用户次留/3/7/15/30日留以「当日全体活跃用户」为基数,次日继续活跃算留存; + // 新用户(注册 cohort)留存只保留在 P0 的分身份次留里。 for _, offset := range []int{1, 3, 7, 15, 30} { - if row.Registered > 0 { - addSocialRetention(out, "registered_d"+intLabel(offset)+"_active", row, bySubjectDay, offset, func(target socialRequirementUserDay) bool { return target.Active > 0 }) + if row.Active > 0 { + addSocialRetention(out, "active_d"+intLabel(offset)+"_active", row, bySubjectDay, offset, func(target socialRequirementUserDay) bool { return target.Active > 0 }) } } if row.RoomJoinOK > 0 { @@ -539,7 +649,7 @@ func socialSectionMetrics(section string, agg socialRequirementAggregate, retent "paid_rate": ratio(agg.Recharge, agg.Active), "arpu_usd_minor": div(agg.RechargeUSDMinor, agg.Active), "arppu_usd_minor": div(ordinaryRechargeUSDMinor, agg.Recharge), - "consumption_penetration_rate": ratio(agg.Gift+agg.GameBet, agg.Active), + "consumption_penetration_rate": ratio(agg.ConsumeUsers, agg.Active), "game_consumption_ratio": ratio(agg.GameBetCoin, totalConsume), "lucky_gift_consumption_ratio": ratio(agg.LuckyGiftCoin+agg.SuperGiftCoin, totalConsume), "normal_gift_consumption_ratio": ratio(agg.NormalGiftCoin, totalConsume), @@ -559,17 +669,18 @@ func socialSectionMetrics(section string, agg socialRequirementAggregate, retent "active_users": agg.Active, "room_join_success_users": agg.RoomJoinOK, "room_join_fail_users": agg.RoomJoinNG, - "room_join_success_rate": ratio(agg.RoomJoinOK, agg.RoomJoinOK+agg.RoomJoinNG), - "room_join_fail_rate": ratio(agg.RoomJoinNG, agg.RoomJoinOK+agg.RoomJoinNG), - "mic_up_users": agg.MicUp, - "mic_up_rate": ratio(agg.MicUp, agg.RoomJoinOK), - "gift_users": agg.Gift, - "gift_rate": ratio(agg.Gift, agg.RoomJoinOK), - "game_users": agg.GameBet, - "game_conversion_rate": ratio(agg.GameBet, agg.Active), - "chat_users": agg.Chat, - "chat_conversion_rate": ratio(agg.Chat, agg.Active), - "avg_app_stay_min": minutesPerUser(agg.AppStayMS, agg.Active), + // 文档口径:进房成功率/失败率的分母都是 DAU,不是进房尝试数。 + "room_join_success_rate": ratio(agg.RoomJoinOK, agg.Active), + "room_join_fail_rate": ratio(agg.RoomJoinNG, agg.Active), + "mic_up_users": agg.MicUp, + "mic_up_rate": ratio(agg.MicUp, agg.RoomJoinOK), + "gift_users": agg.Gift, + "gift_rate": ratio(agg.Gift, agg.RoomJoinOK), + "game_users": agg.GameBet, + "game_conversion_rate": ratio(agg.GameBet, agg.RoomJoinOK), + "chat_users": agg.Chat, + "chat_conversion_rate": ratio(agg.Chat, agg.Active), + "avg_app_stay_min": minutesPerUser(agg.AppStayMS, agg.Active), } case SocialSectionGift: return map[string]any{ @@ -629,27 +740,51 @@ func socialSectionMetrics(section string, agg socialRequirementAggregate, retent "recharge_rate": ratio(agg.RegisteredRecharge, agg.Registered), "game_users": agg.RegisteredGame, "game_conversion_rate": ratio(agg.RegisteredGame, agg.Registered), - "avg_app_stay_min": minutesPerUser(agg.AppStayMS, agg.Active), - "avg_room_stay_min": minutesPerUser(agg.RoomStayMS, agg.RoomJoinOK), - "avg_mic_stay_min": minutesPerUser(agg.MicStayMS, agg.MicUp), - "new_user_recharge_usd_minor": agg.FirstRechargeUSDMinor, - "follow_user_users": agg.FollowUser, - "follow_room_users": agg.FollowRoom, - "chat_users": agg.Chat, + // P0 全部限定当天注册的新用户:时长按新用户累计、除以对应行为的新用户数。 + "avg_app_stay_min": minutesPerUser(agg.RegisteredAppStayMS, agg.Registered), + "avg_room_stay_min": minutesPerUser(agg.RegisteredRoomStayMS, agg.RegisteredRoomJoinOK), + "avg_mic_stay_min": minutesPerUser(agg.RegisteredMicStayMS, agg.RegisteredMicUp), + "new_user_recharge_usd_minor": agg.RegisteredRechargeUSDMinor, + "follow_user_users": agg.RegisteredFollowUser, + "follow_room_users": agg.RegisteredFollowRoom, + "chat_users": agg.RegisteredChat, "new_host_d1_retention_rate": retentionRate(retention, "new_host_d1_active"), "new_user_d1_retention_rate": retentionRate(retention, "new_user_d1_active"), } } } -func socialRowMatchesSection(row socialRequirementUserDay, section string, role string, payerType string) bool { +func socialRowMatchesSection(row socialRequirementUserDay, section string, role string, payerType string, newUserType string) bool { switch section { case SocialSectionNewUsers: return true case SocialSectionGift, SocialSectionGame: - return socialRoleMatches(row, role) + return socialRoleMatches(row, role) && socialNewUserMatches(row, newUserType) default: - return socialRoleMatches(row, role) && socialPayerMatches(row, payerType) + return socialRoleMatches(row, role) && socialPayerMatches(row, payerType) && socialNewUserMatches(row, newUserType) + } +} + +// 是否新用户筛选:新用户 = 当天注册成功的用户(registered_success 标志按行的 stat_day 判定)。 +func socialNewUserMatches(row socialRequirementUserDay, newUserType string) bool { + switch normalizeSocialNewUserType(newUserType) { + case "new": + return row.Registered > 0 + case "old": + return row.Registered == 0 + default: + return true + } +} + +func normalizeSocialNewUserType(value string) string { + switch strings.ToLower(strings.TrimSpace(value)) { + case "new": + return "new" + case "old", "existing", "non_new": + return "old" + default: + return "all" } } @@ -741,8 +876,9 @@ func socialRequirementMetricDefinitions(section string) []MetricDefinition { switch section { case SocialSectionRevenue: return []MetricDefinition{ - {Metric: "recharge_usd_minor", Definition: "普通 WalletRechargeRecorded 用户充值和 finance verified 币商充值订单汇总,单位 USD minor。"}, - {Metric: "third_party_recharge_usd_minor", Definition: "finance 工作台币商充值订单 verify_status=verified 后按 created_at_ms 计入;普通 MifaPay/V5Pay/USDT 用户充值不进入此列。"}, + {Metric: "recharge_usd_minor", Definition: "普通 WalletRechargeRecorded 用户充值和 finance verified 币商充值订单汇总,单位 USD minor;总充值 = Google + 第三方 + 币商。"}, + {Metric: "third_party_recharge_usd_minor", Definition: "MiFaPay/V5Pay/USDT 等非 Google、非币商支付渠道的用户充值金额。"}, + {Metric: "coin_seller_recharge_usd_minor", Definition: "finance 工作台币商充值订单 verify_status=verified 后按 created_at_ms 计入。"}, {Metric: "repurchase_rate", Definition: "复购充值用户 / 当日充值用户。"}, {Metric: "paid_rate", Definition: "充值用户 / DAU,支持用户身份和当日付费身份筛选。"}, {Metric: "arpu_usd_minor", Definition: "充值金额 / DAU。"}, @@ -750,7 +886,7 @@ func socialRequirementMetricDefinitions(section string) []MetricDefinition { } case SocialSectionRetention: return []MetricDefinition{ - {Metric: "registered_d1_active_rate", Definition: "注册 cohort 在第 1 个统计日后的活跃留存。D3/D7/D15/D30 同理。"}, + {Metric: "active_d1_active_rate", Definition: "当日全体活跃用户(进入 app)在次日继续活跃的比例;D3/D7/D15/D30 同理。不是新用户留存。"}, {Metric: "after_room_d1_room_rate", Definition: "进房 cohort 在次日再次进房的留存。"}, {Metric: "after_recharge_d1_recharge_rate", Definition: "充值 cohort 在次日再次充值的留存。"}, } @@ -807,23 +943,23 @@ func socialRequirementColumns(section string) []SocialRequirementColumn { } case SocialSectionRetention: return []SocialRequirementColumn{ - {Key: "registered_d1_active_base_users", Label: "新用户D1基数", Type: "count"}, - {Key: "registered_d1_active_users", Label: "新用户D1留存", Type: "count"}, - {Key: "registered_d1_active_rate", Label: "新用户D1留存率", Type: "ratio"}, - {Key: "registered_d3_active_rate", Label: "新用户D3留存率", Type: "ratio"}, - {Key: "registered_d7_active_rate", Label: "新用户D7留存率", Type: "ratio"}, - {Key: "registered_d15_active_rate", Label: "新用户D15留存率", Type: "ratio"}, - {Key: "registered_d30_active_rate", Label: "新用户D30留存率", Type: "ratio"}, - {Key: "after_room_d1_active_rate", Label: "进房后D1活跃留存", Type: "ratio"}, - {Key: "after_room_d1_room_rate", Label: "进房后D1进房留存", Type: "ratio"}, - {Key: "after_mic_d1_active_rate", Label: "上麦后D1活跃留存", Type: "ratio"}, - {Key: "after_mic_d1_mic_rate", Label: "上麦后D1上麦留存", Type: "ratio"}, - {Key: "after_gift_d1_active_rate", Label: "送礼后D1活跃留存", Type: "ratio"}, - {Key: "after_gift_d1_gift_rate", Label: "送礼后D1送礼留存", Type: "ratio"}, - {Key: "after_game_d1_active_rate", Label: "游戏后D1活跃留存", Type: "ratio"}, - {Key: "after_game_d1_game_rate", Label: "游戏后D1游戏留存", Type: "ratio"}, - {Key: "after_recharge_d1_active_rate", Label: "充值后D1活跃留存", Type: "ratio"}, - {Key: "after_recharge_d1_recharge_rate", Label: "充值后D1充值留存", Type: "ratio"}, + {Key: "active_d1_active_base_users", Label: "活跃用户基数", Type: "count", Tooltip: "当日进入 app 的全体活跃用户数(不限新用户)"}, + {Key: "active_d1_active_users", Label: "次留人数", Type: "count", Tooltip: "当日活跃用户中,次日继续进入 app 的人数"}, + {Key: "active_d1_active_rate", Label: "用户次留", Type: "ratio", Tooltip: "昨日活跃用户中,今日继续活跃的比例"}, + {Key: "active_d3_active_rate", Label: "用户3日留", Type: "ratio"}, + {Key: "active_d7_active_rate", Label: "用户7日留", Type: "ratio"}, + {Key: "active_d15_active_rate", Label: "用户15日留", Type: "ratio"}, + {Key: "active_d30_active_rate", Label: "用户30日留", Type: "ratio"}, + {Key: "after_room_d1_active_rate", Label: "进房后次留(活跃)", Type: "ratio", Tooltip: "当天进房后,次日继续登录"}, + {Key: "after_room_d1_room_rate", Label: "进房后次留(进房)", Type: "ratio", Tooltip: "当天进房后,次日继续进房"}, + {Key: "after_mic_d1_active_rate", Label: "上麦后次留(活跃)", Type: "ratio", Tooltip: "当天上麦后,次日继续登录"}, + {Key: "after_mic_d1_mic_rate", Label: "上麦后次留(上麦)", Type: "ratio", Tooltip: "当天上麦后,次日继续上麦"}, + {Key: "after_gift_d1_active_rate", Label: "送礼后次留(活跃)", Type: "ratio", Tooltip: "当天送礼后,次日继续登录"}, + {Key: "after_gift_d1_gift_rate", Label: "送礼后次留(送礼)", Type: "ratio", Tooltip: "当天送礼后,次日继续送礼"}, + {Key: "after_game_d1_active_rate", Label: "游戏次留(活跃)", Type: "ratio", Tooltip: "当天押注后,次日继续登录"}, + {Key: "after_game_d1_game_rate", Label: "游戏次留(游戏)", Type: "ratio", Tooltip: "当天押注后,次日继续押注"}, + {Key: "after_recharge_d1_active_rate", Label: "充值后次留(活跃)", Type: "ratio", Tooltip: "当天充值后,次日继续活跃"}, + {Key: "after_recharge_d1_recharge_rate", Label: "充值后次留(充值)", Type: "ratio", Tooltip: "当天充值后,次日继续充值"}, } case SocialSectionActive: return []SocialRequirementColumn{ @@ -900,13 +1036,13 @@ func socialRequirementColumns(section string) []SocialRequirementColumn { {Key: "recharge_rate", Label: "新用户充值率", Type: "ratio"}, {Key: "game_users", Label: "新用户游戏", Type: "count"}, {Key: "game_conversion_rate", Label: "新用户游戏转化率", Type: "ratio"}, - {Key: "avg_app_stay_min", Label: "人均App停留(分钟)", Type: "number"}, - {Key: "avg_room_stay_min", Label: "人均房间停留(分钟)", Type: "number"}, - {Key: "avg_mic_stay_min", Label: "人均麦上停留(分钟)", Type: "number"}, - {Key: "new_user_recharge_usd_minor", Label: "新用户充值金额", Type: "money_minor"}, - {Key: "follow_user_users", Label: "关注用户人数", Type: "count"}, - {Key: "follow_room_users", Label: "关注房间人数", Type: "count"}, - {Key: "chat_users", Label: "聊天用户", Type: "count"}, + {Key: "avg_app_stay_min", Label: "新用户平均停留(分钟)", Type: "number", Tooltip: "当天注册的新用户 App 停留总时长 / 新用户注册成功人数"}, + {Key: "avg_room_stay_min", Label: "新用户平均进房时长(分钟)", Type: "number", Tooltip: "当天注册的新用户房间停留总时长 / 新用户进房成功人数"}, + {Key: "avg_mic_stay_min", Label: "新用户平均上麦时长(分钟)", Type: "number", Tooltip: "当天注册的新用户麦上总时长 / 新用户上麦人数"}, + {Key: "new_user_recharge_usd_minor", Label: "新用户充值金额", Type: "money_minor", Tooltip: "当天注册的新用户全渠道充值金额"}, + {Key: "follow_user_users", Label: "新用户关注行为人数", Type: "count", Tooltip: "当天注册的新用户中发生过关注用户行为的人数"}, + {Key: "follow_room_users", Label: "新用户关注房间人数", Type: "count", Tooltip: "当天注册的新用户中发生过关注房间行为的人数"}, + {Key: "chat_users", Label: "新用户聊天人数", Type: "count", Tooltip: "当天注册的新用户中发送过 IM 消息(房间或私聊)的人数"}, {Key: "new_host_d1_retention_rate", Label: "新主播D1留存", Type: "ratio"}, {Key: "new_user_d1_retention_rate", Label: "新普通用户D1留存", Type: "ratio"}, } @@ -954,6 +1090,218 @@ func socialDayRange(startDay string, endDay string) []string { return days } +type socialGameKey struct { + Source string + Platform string + GameID string +} + +type socialGameDayData struct { + Users map[string]map[int64]bool // stat_day -> 押注用户集合 + Coin map[string]int64 // stat_day -> 押注金币 +} + +// querySocialGameBreakdown 输出 P5 的按游戏明细:押注用户、同游戏次留、ARPPU、押注金币。 +// 自研游戏读 stat_self_game_user_day(用户级、含金额);厂商/概率游戏用户集读 stat_game_day_players、 +// 金额读 stat_game_day_country(该表无用户级金额)。次留需要展示末日的次日数据,成员集多读 1 天。 +func (r *Repository) querySocialGameBreakdown(ctx context.Context, app string, statTZ string, days []string, countryID int64, regionID int64, regionIDs []int64) ([]SocialRequirementGame, error) { + if len(days) == 0 { + return nil, nil + } + startDay := days[0] + memberEndDay := addStatDays(days[len(days)-1], 1) + games := map[socialGameKey]*socialGameDayData{} + ensure := func(key socialGameKey) *socialGameDayData { + if games[key] == nil { + games[key] = &socialGameDayData{Users: map[string]map[int64]bool{}, Coin: map[string]int64{}} + } + return games[key] + } + scopeConditions := func(base []string, args []any) ([]string, []any) { + if countryID > 0 { + base = append(base, "country_id = ?") + args = append(args, countryID) + } + if regionID > 0 { + base = append(base, "region_id = ?") + args = append(args, regionID) + } else if ids := normalizePositiveIDs(regionIDs); len(ids) > 0 { + placeholders := make([]string, 0, len(ids)) + for _, id := range ids { + placeholders = append(placeholders, "?") + args = append(args, id) + } + base = append(base, "region_id IN ("+strings.Join(placeholders, ",")+")") + } + return base, args + } + + selfConditions, selfArgs := scopeConditions( + []string{"app_code = ?", "stat_tz = ?", "stat_day BETWEEN ? AND ?", "match_count > 0"}, + []any{appcode.Normalize(app), normalizeStatTZ(statTZ), startDay, memberEndDay}, + ) + selfRows, err := r.db.QueryContext(ctx, ` + SELECT DATE_FORMAT(stat_day, '%Y-%m-%d'), game_id, user_id, stake_coin + FROM stat_self_game_user_day + WHERE `+strings.Join(selfConditions, " AND "), selfArgs...) + if err != nil { + return nil, err + } + defer selfRows.Close() + for selfRows.Next() { + var day, gameID string + var userID, stakeCoin int64 + if err := selfRows.Scan(&day, &gameID, &userID, &stakeCoin); err != nil { + return nil, err + } + data := ensure(socialGameKey{Source: "self", GameID: gameID}) + if data.Users[day] == nil { + data.Users[day] = map[int64]bool{} + } + data.Users[day][userID] = true + data.Coin[day] += stakeCoin + } + if err := selfRows.Err(); err != nil { + return nil, err + } + + vendorConditions, vendorArgs := scopeConditions( + []string{"app_code = ?", "stat_tz = ?", "stat_day BETWEEN ? AND ?"}, + []any{appcode.Normalize(app), normalizeStatTZ(statTZ), startDay, memberEndDay}, + ) + vendorRows, err := r.db.QueryContext(ctx, ` + SELECT DATE_FORMAT(stat_day, '%Y-%m-%d'), platform_code, game_id, user_id + FROM stat_game_day_players + WHERE `+strings.Join(vendorConditions, " AND "), vendorArgs...) + if err != nil { + return nil, err + } + defer vendorRows.Close() + for vendorRows.Next() { + var day, platform, gameID string + var userID int64 + if err := vendorRows.Scan(&day, &platform, &gameID, &userID); err != nil { + return nil, err + } + data := ensure(socialGameKey{Source: "vendor", Platform: platform, GameID: gameID}) + if data.Users[day] == nil { + data.Users[day] = map[int64]bool{} + } + data.Users[day][userID] = true + } + if err := vendorRows.Err(); err != nil { + return nil, err + } + + turnoverRows, err := r.db.QueryContext(ctx, ` + SELECT DATE_FORMAT(stat_day, '%Y-%m-%d'), platform_code, game_id, SUM(turnover_coin) + FROM stat_game_day_country + WHERE `+strings.Join(vendorConditions, " AND ")+` + GROUP BY stat_day, platform_code, game_id`, vendorArgs...) + if err != nil { + return nil, err + } + defer turnoverRows.Close() + for turnoverRows.Next() { + var day, platform, gameID string + var turnover int64 + if err := turnoverRows.Scan(&day, &platform, &gameID, &turnover); err != nil { + return nil, err + } + key := socialGameKey{Source: "vendor", Platform: platform, GameID: gameID} + if games[key] == nil { + // 只有金额没有用户投影的历史脏数据不单独成行,避免出现"有流水无用户"的幽灵游戏。 + continue + } + games[key].Coin[day] += turnover + } + if err := turnoverRows.Err(); err != nil { + return nil, err + } + + out := make([]SocialRequirementGame, 0, len(games)) + for key, data := range games { + daily := make([]SocialRequirementRow, 0, len(days)) + var totalUsers, totalCoin int64 + totalRetention := retentionMetric{} + for _, day := range days { + users := int64(len(data.Users[day])) + coin := data.Coin[day] + retention := retentionMetric{Base: users} + nextDay := data.Users[addStatDays(day, 1)] + for userID := range data.Users[day] { + if nextDay[userID] { + retention.Users++ + } + } + totalUsers += users + totalCoin += coin + totalRetention.Base += retention.Base + totalRetention.Users += retention.Users + daily = append(daily, SocialRequirementRow{ + StatDay: day, + Label: day, + Metrics: socialGameRowMetrics(users, coin, retention), + }) + } + if totalUsers == 0 && totalCoin == 0 { + continue + } + keyParts := []string{key.Source} + if strings.TrimSpace(key.Platform) != "" { + keyParts = append(keyParts, strings.TrimSpace(key.Platform)) + } + keyParts = append(keyParts, strings.TrimSpace(key.GameID)) + out = append(out, SocialRequirementGame{ + GameKey: strings.Join(keyParts, ":"), + GameSource: key.Source, + GameName: socialGameDisplayName(key), + Total: SocialRequirementRow{StatDay: "total", Label: "汇总", Metrics: socialGameRowMetrics(totalUsers, totalCoin, totalRetention)}, + DailySeries: daily, + }) + } + sort.Slice(out, func(i, j int) bool { + left := out[i].Total.Metrics["game_bet_coin"].(int64) + right := out[j].Total.Metrics["game_bet_coin"].(int64) + if left != right { + return left > right + } + return out[i].GameName < out[j].GameName + }) + return out, nil +} + +func socialGameRowMetrics(users int64, coin int64, retention retentionMetric) map[string]any { + return map[string]any{ + "game_bet_users": users, + "game_bet_coin": coin, + "game_arppu_coin": div(coin, users), + "game_d1_retention_base_users": retention.Base, + "game_d1_retention_users": retention.Users, + "game_d1_retention_rate": ratio(retention.Users, retention.Base), + } +} + +func socialGameDisplayName(key socialGameKey) string { + name := strings.TrimSpace(key.GameID) + if name == "" { + name = "unknown" + } + if key.Source == "vendor" && strings.TrimSpace(key.Platform) != "" { + return name + " (" + strings.TrimSpace(key.Platform) + ")" + } + return name +} + +func socialGameBreakdownColumns() []SocialRequirementColumn { + return []SocialRequirementColumn{ + {Key: "game_bet_users", Label: "押注用户数", Type: "count", Tooltip: "当日押注过该游戏的去重用户数"}, + {Key: "game_d1_retention_rate", Label: "游戏次留", Type: "ratio", Tooltip: "当日押注该游戏的用户中,次日继续押注同一游戏的比例"}, + {Key: "game_arppu_coin", Label: "ARPPU(金币)", Type: "coin", Tooltip: "该游戏押注金币 / 押注用户数"}, + {Key: "game_bet_coin", Label: "押注金币", Type: "coin"}, + } +} + func normalizePositiveIDs(values []int64) []int64 { seen := map[int64]bool{} out := []int64{} diff --git a/services/statistics-service/internal/storage/mysql/social_room_online_test.go b/services/statistics-service/internal/storage/mysql/social_room_online_test.go new file mode 100644 index 00000000..447eaf20 --- /dev/null +++ b/services/statistics-service/internal/storage/mysql/social_room_online_test.go @@ -0,0 +1,100 @@ +package mysql + +import ( + "context" + "runtime" + "testing" + "time" + + "hyapp/internal/testutil/mysqlschema" + "hyapp/pkg/appcode" +) + +func TestConsumeUserRoomDailyStatsUpdatedProjectsRoomOnline(t *testing.T) { + ctx := context.Background() + _, file, _, _ := runtime.Caller(0) + statsSchema := mysqlschema.New(t, mysqlschema.Config{ + InitDBPath: mysqlschema.InitDBPath(t, file, "..", "..", "..", "deploy", "mysql", "initdb", "001_statistics_service.sql"), + DatabasePrefix: "hy_stats_room_online_test", + }) + repository, err := Open(ctx, statsSchema.DSN) + if err != nil { + t.Fatalf("open repository: %v", err) + } + t.Cleanup(func() { _ = repository.Close() }) + + appCtx := appcode.WithContext(ctx, "lalu") + occurredAt := time.Date(2026, 7, 1, 10, 0, 0, 0, time.UTC).UnixMilli() + + // 客户端 room_stay 埋点先落 60s;服务端事实到达前它是 room_stay_ms 的唯一来源。 + if _, err := repository.ConsumeAppTrackingEvents(appCtx, []AppTrackingEvent{{ + AppCode: "lalu", + EventID: "evt-room-stay-client", + EventName: "room_stay", + UserID: 42, + DeviceID: "dev-42", + DurationMS: 60_000, + Success: true, + OccurredAtMS: occurredAt, + }}); err != nil { + t.Fatalf("consume client room_stay: %v", err) + } + + event := UserRoomDailyStatsUpdatedEvent{ + AppCode: "lalu", + EventID: "UserRoomDailyStatsUpdated:sess-1:2026-07-01", + UserID: 42, + StatDate: "2026-07-01", + RoomOnlineDeltaMS: 600_000, + OccurredAtMS: occurredAt, + } + if err := repository.ConsumeUserRoomDailyStatsUpdated(appCtx, event); err != nil { + t.Fatalf("consume server room online fact: %v", err) + } + // 事件级幂等:同一 event_id 重放不能双计。 + if err := repository.ConsumeUserRoomDailyStatsUpdated(appCtx, event); err != nil { + t.Fatalf("replay server room online fact: %v", err) + } + if err := repository.ConsumeUserRoomDailyStatsUpdated(appCtx, UserRoomDailyStatsUpdatedEvent{ + AppCode: "lalu", + EventID: "UserRoomDailyStatsUpdated:sess-2:2026-07-01", + UserID: 42, + StatDate: "2026-07-01", + RoomOnlineDeltaMS: 300_000, + OccurredAtMS: occurredAt, + }); err != nil { + t.Fatalf("consume second server room online fact: %v", err) + } + + var roomStayMS, roomOnlineMS, roomJoinOK int64 + if err := repository.db.QueryRowContext(ctx, ` + SELECT room_stay_ms, room_online_ms, room_join_success + FROM stat_social_user_day + WHERE app_code = 'lalu' AND stat_tz = 'UTC' AND stat_day = '2026-07-01' AND subject_key = 'u:42' + `).Scan(&roomStayMS, &roomOnlineMS, &roomJoinOK); err != nil { + t.Fatalf("query stat_social_user_day: %v", err) + } + // 双轨分列:客户端埋点只进 room_stay_ms,服务端事实只进 room_online_ms,互不双计。 + if roomStayMS != 60_000 || roomOnlineMS != 900_000 || roomJoinOK != 1 { + t.Fatalf("social user day mismatch: room_stay=%d room_online=%d join_ok=%d", roomStayMS, roomOnlineMS, roomJoinOK) + } + + rows, err := repository.querySocialUserDays(ctx, "lalu", "UTC", "2026-07-01", "2026-07-01", 0, 0, nil) + if err != nil { + t.Fatalf("query social user days: %v", err) + } + var found bool + for _, row := range rows { + if row.Subject != "u:42" { + continue + } + found = true + // 查询口径取 GREATEST(room_stay_ms, room_online_ms):切换期两条来源并存也不会低估在房时长。 + if row.RoomStayMS != 900_000 || row.RoomOnlineMS != 900_000 { + t.Fatalf("query clamp mismatch: %+v", row) + } + } + if !found { + t.Fatalf("subject u:42 not returned by querySocialUserDays: %+v", rows) + } +} diff --git a/services/statistics-service/internal/storage/mysql/social_user_day.go b/services/statistics-service/internal/storage/mysql/social_user_day.go index 42602195..1f5796ba 100644 --- a/services/statistics-service/internal/storage/mysql/social_user_day.go +++ b/services/statistics-service/internal/storage/mysql/social_user_day.go @@ -42,6 +42,7 @@ type socialUserDayDelta struct { ProbabilityPanel int64 AppStayMS int64 RoomStayMS int64 + RoomOnlineMS int64 MicStayMS int64 RechargeCount int64 RechargeUSDMinor int64 @@ -120,6 +121,7 @@ func applySocialUserDayDelta(ctx context.Context, tx *sql.Tx, delta socialUserDa probability_game_panel_open = GREATEST(probability_game_panel_open, ?), app_stay_ms = app_stay_ms + ?, room_stay_ms = room_stay_ms + ?, + room_online_ms = room_online_ms + ?, mic_stay_ms = mic_stay_ms + ?, recharge_count = recharge_count + ?, recharge_usd_minor = recharge_usd_minor + ?, @@ -147,7 +149,7 @@ func applySocialUserDayDelta(ctx context.Context, tx *sql.Tx, delta socialUserDa flagValue(delta.MicUp), flagValue(delta.Gift), flagValue(delta.NormalGift), flagValue(delta.LuckyGift), flagValue(delta.SuperGift), flagValue(delta.Recharge), flagValue(delta.FirstRecharge), flagValue(delta.RepeatRecharge), flagValue(delta.GameBet), flagValue(delta.SelfGameBet), flagValue(delta.ProbabilityBet), flagValue(delta.Chat), flagValue(delta.FollowUser), flagValue(delta.FollowRoom), flagValue(delta.GiftPanel), - flagValue(delta.ProbabilityPanel), normalizeID(delta.AppStayMS), normalizeID(delta.RoomStayMS), normalizeID(delta.MicStayMS), + flagValue(delta.ProbabilityPanel), normalizeID(delta.AppStayMS), normalizeID(delta.RoomStayMS), normalizeID(delta.RoomOnlineMS), normalizeID(delta.MicStayMS), normalizeID(delta.RechargeCount), normalizeID(delta.RechargeUSDMinor), normalizeID(delta.GoogleRechargeUSDMinor), normalizeID(delta.ThirdPartyRechargeUSDMinor), normalizeID(delta.CoinSellerRechargeUSDMinor), normalizeID(delta.FirstRechargeUSDMinor), normalizeID(delta.RepeatRechargeUSDMinor), normalizeID(delta.NormalGiftCoin), normalizeID(delta.LuckyGiftCoin), normalizeID(delta.SuperGiftCoin), normalizeID(delta.LuckyGiftPayoutCoin), @@ -234,6 +236,8 @@ func socialDeltaForAppTrackingEvent(event AppTrackingEvent, scope statDayScope, return socialUserDayDelta{}, false } delta.Active = 1 + // 上麦成功隐含进房成功,补置进房标志,保证进房人数 >= 上麦人数。 + delta.RoomJoinOK = 1 delta.MicUp = 1 case "send_message": if !event.Success && strings.TrimSpace(event.ErrorCode) != "" { diff --git a/services/user-service/Dockerfile b/services/user-service/Dockerfile index 937f8661..233771ea 100644 --- a/services/user-service/Dockerfile +++ b/services/user-service/Dockerfile @@ -19,7 +19,7 @@ COPY . . RUN --mount=type=cache,id=hyapp-go-mod,target=/go/pkg/mod \ --mount=type=cache,id=hyapp-go-build,target=/root/.cache/go-build \ mkdir -p /out && \ - GOWORK=off CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/ ./services/user-service/cmd/server ./cmd/grpc-health-probe + GOWORK=off CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/ ./services/user-service/cmd/server ./services/user-service/cmd/replay-outbox ./cmd/grpc-health-probe FROM alpine:3.20 @@ -30,6 +30,7 @@ WORKDIR /app RUN apk add --no-cache ca-certificates && adduser -D -g '' appuser COPY --from=builder /out/server /app/server +COPY --from=builder /out/replay-outbox /app/replay-outbox COPY --from=builder /out/grpc-health-probe /app/grpc-health-probe COPY services/user-service/configs/config.docker.yaml /app/config.yaml diff --git a/services/user-service/cmd/replay-outbox/main.go b/services/user-service/cmd/replay-outbox/main.go new file mode 100644 index 00000000..667f68cf --- /dev/null +++ b/services/user-service/cmd/replay-outbox/main.go @@ -0,0 +1,59 @@ +// Command replay-outbox publishes a hard-bounded user owner-outbox batch for +// one App without starting the user-service API or unrelated MQ consumers. +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "os" + "strings" + "time" + + "hyapp/pkg/outboxreplay" + "hyapp/services/user-service/internal/app" + "hyapp/services/user-service/internal/config" +) + +func main() { + configPath := flag.String("config", "services/user-service/configs/config.yaml", "path to user-service config") + appCode := flag.String("app-code", "", "exact App partition to replay") + limit := flag.Int("limit", 0, "process-wide maximum rows, 1..500") + execute := flag.Bool("execute", false, "publish the bounded batch; default is a no-write dry-run") + confirmation := flag.String("confirm", "", "required execution confirmation") + timeout := flag.Duration("timeout", 2*time.Minute, "total command timeout, at most 30m") + flag.Parse() + + scope, err := outboxreplay.NewScope(*appCode, *limit, 500) + if err != nil { + fatal(err) + } + if *timeout <= 0 || *timeout > 30*time.Minute { + fatal(errors.New("timeout must be within (0,30m]")) + } + if *execute && strings.TrimSpace(*confirmation) != outboxreplay.ExecuteConfirmation { + fatal(fmt.Errorf("--execute requires --confirm=%s", outboxreplay.ExecuteConfirmation)) + } + cfg, err := config.Load(strings.TrimSpace(*configPath)) + if err != nil { + fatal(err) + } + if !*execute { + fmt.Printf("mode=dry-run app_code=%s limit=%d no_writes=true\n", scope.AppCode, scope.Limit) + return + } + + ctx, cancel := context.WithTimeout(context.Background(), *timeout) + defer cancel() + result, err := app.ReplayUserOutboxOnce(ctx, cfg, scope) + if err != nil { + fatal(fmt.Errorf("user replay stopped after %d rows: %w", result.Processed, err)) + } + fmt.Printf("mode=execute app_code=%s limit=%d processed=%d drained=%t\n", scope.AppCode, scope.Limit, result.Processed, result.Drained) +} + +func fatal(err error) { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) +} diff --git a/services/user-service/configs/config.docker.yaml b/services/user-service/configs/config.docker.yaml index 9f00df4a..f30e1a93 100644 --- a/services/user-service/configs/config.docker.yaml +++ b/services/user-service/configs/config.docker.yaml @@ -36,6 +36,9 @@ invite_recharge_worker: enabled: true mic_time_worker: enabled: true +room_time_worker: + enabled: true + consumer_group: "hyapp-user-roomtime-room-outbox" cp_relationship_worker: enabled: true consumer_group: "hyapp-user-cp-room-outbox" @@ -47,6 +50,7 @@ cp_intimacy_leaderboard: key_prefix: "user:cp_intimacy_leaderboard" outbox_worker: enabled: true + app_codes: [] poll_interval: "1s" batch_size: 100 publish_timeout: "3s" diff --git a/services/user-service/configs/config.tencent.example.yaml b/services/user-service/configs/config.tencent.example.yaml index 6e6c7150..d6bf3dc2 100644 --- a/services/user-service/configs/config.tencent.example.yaml +++ b/services/user-service/configs/config.tencent.example.yaml @@ -36,6 +36,9 @@ invite_recharge_worker: enabled: true mic_time_worker: enabled: true +room_time_worker: + enabled: true + consumer_group: "hyapp-user-roomtime-room-outbox" cp_relationship_worker: enabled: true consumer_group: "hyapp-user-cp-room-outbox" @@ -47,6 +50,8 @@ cp_intimacy_leaderboard: key_prefix: "user:cp_intimacy_leaderboard" outbox_worker: enabled: true + # 空值自动发现全部 App;历史积压回放时先设 ["lalu", "fami"],核对后再加入 "huwaa"。 + app_codes: [] poll_interval: "1s" batch_size: 100 publish_timeout: "3s" diff --git a/services/user-service/configs/config.yaml b/services/user-service/configs/config.yaml index d7559424..c26d979c 100644 --- a/services/user-service/configs/config.yaml +++ b/services/user-service/configs/config.yaml @@ -37,6 +37,9 @@ invite_recharge_worker: enabled: true mic_time_worker: enabled: true +room_time_worker: + enabled: true + consumer_group: "hyapp-user-roomtime-room-outbox" cp_relationship_worker: enabled: true consumer_group: "hyapp-user-cp-room-outbox" @@ -48,6 +51,7 @@ cp_intimacy_leaderboard: key_prefix: "user:cp_intimacy_leaderboard" outbox_worker: enabled: true + app_codes: [] poll_interval: "1s" batch_size: 100 publish_timeout: "3s" diff --git a/services/user-service/deploy/mysql/initdb/001_user_service.sql b/services/user-service/deploy/mysql/initdb/001_user_service.sql index 646fc9e4..f3664040 100644 --- a/services/user-service/deploy/mysql/initdb/001_user_service.sql +++ b/services/user-service/deploy/mysql/initdb/001_user_service.sql @@ -127,6 +127,28 @@ CREATE TABLE IF NOT EXISTS user_follows ( KEY idx_user_follows_follower_status (app_code, follower_user_id, status, followed_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户关注表'; +CREATE TABLE IF NOT EXISTS user_follow_events ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + event_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '关注状态变化事件 ID', + follower_user_id BIGINT NOT NULL COMMENT '关注人用户 ID', + followee_user_id BIGINT NOT NULL COMMENT '被关注人用户 ID', + delta TINYINT NOT NULL COMMENT '关注 +1,取消关注 -1', + occurred_at_ms BIGINT NOT NULL COMMENT '事件时间,UTC epoch ms', + PRIMARY KEY (event_id), + KEY idx_user_follow_events_followee_time (app_code, followee_user_id, occurred_at_ms, event_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户关注净增统计事件表'; + +CREATE TABLE IF NOT EXISTS user_private_message_events ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + event_id VARCHAR(128) NOT NULL COMMENT '腾讯 IM 私信事件幂等 ID', + sender_user_id BIGINT NOT NULL COMMENT '消息发送用户 ID', + target_user_id BIGINT NOT NULL COMMENT '消息接收用户 ID', + occurred_at_ms BIGINT NOT NULL COMMENT '发送时间,UTC epoch ms', + created_at_ms BIGINT NOT NULL COMMENT '落库时间,UTC epoch ms', + PRIMARY KEY (app_code, event_id), + KEY idx_private_message_target_time (app_code, target_user_id, occurred_at_ms, sender_user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户私信发送事实,仅保存统计字段'; + CREATE TABLE IF NOT EXISTS user_friend_applications ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', requester_user_id BIGINT NOT NULL COMMENT '申请人用户 ID', @@ -193,6 +215,62 @@ CREATE TABLE IF NOT EXISTS manager_user_blocks ( KEY idx_manager_user_blocks_expire (app_code, status, blocked_until_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='经理中心用户封禁记录表'; +CREATE TABLE IF NOT EXISTS admin_user_bans ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + ban_id VARCHAR(96) NOT NULL COMMENT '管理员封禁事实 ID', + command_id VARCHAR(128) NOT NULL COMMENT '后台请求幂等命令 ID', + target_user_id BIGINT NOT NULL COMMENT '被封禁用户 ID', + target_old_status VARCHAR(32) NOT NULL COMMENT '创建事实前的用户状态,仅供审计', + expires_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '到期时间,UTC epoch ms;0 表示永久', + status VARCHAR(32) NOT NULL COMMENT 'active/released/expired', + reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '封禁原因', + operator_admin_id BIGINT NOT NULL COMMENT '创建封禁的后台管理员 ID', + released_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '手动解封管理员 ID;系统到期为 0', + released_reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '释放原因', + released_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '释放时间,UTC epoch ms', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, ban_id), + UNIQUE KEY uk_admin_user_bans_command (app_code, command_id), + KEY idx_admin_user_bans_target (app_code, target_user_id, status, created_at_ms), + KEY idx_admin_user_bans_expire (app_code, status, expires_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='后台管理员用户封禁事实表'; + +-- 空 ban_id 解封会按用户选择最新 active 管理员事实;独立命令表把一次选择固定到 resolved_ban_id, +-- 避免网络重试继续释放下一条重叠封禁。 +CREATE TABLE IF NOT EXISTS admin_user_ban_release_commands ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + command_id VARCHAR(128) NOT NULL COMMENT '稳定解封命令 ID,通常取 request_id', + target_user_id BIGINT NOT NULL COMMENT '目标用户 ID', + requested_ban_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '调用方指定 ban_id;空表示选择最新 active 管理员封禁', + resolved_ban_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '本命令实际释放的 ban_id', + release_status VARCHAR(32) NOT NULL COMMENT 'released/expired', + operator_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '手动操作管理员 ID;系统到期为 0', + reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '释放原因快照,用于校验幂等载荷', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, command_id), + KEY idx_admin_ban_release_resolved (app_code, resolved_ban_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='后台管理员解封命令幂等事实表'; + +-- 后台强制解封会同时释放管理员和经理封禁;独立命令事实保证 gRPC 超时重试不会释放操作完成后新产生的封禁。 +CREATE TABLE IF NOT EXISTS admin_user_force_unban_commands ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + command_id VARCHAR(128) NOT NULL COMMENT '稳定强制解封命令 ID,取 request_id', + target_user_id BIGINT NOT NULL COMMENT '目标用户 ID', + operator_admin_id BIGINT NOT NULL COMMENT '执行强制解封的后台管理员 ID', + reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '管理员解封原因快照,用于校验幂等载荷', + released_admin_ban_count BIGINT NOT NULL DEFAULT 0 COMMENT '本命令释放的管理员封禁数量', + released_manager_block_count BIGINT NOT NULL DEFAULT 0 COMMENT '本命令释放的经理封禁数量', + old_status VARCHAR(32) NOT NULL DEFAULT '' COMMENT '首次执行前用户主状态', + new_status VARCHAR(32) NOT NULL DEFAULT '' COMMENT '首次执行后用户主状态', + status_restored BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否把 banned/disabled 恢复为 active', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, command_id), + KEY idx_admin_force_unban_target (app_code, target_user_id, created_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='后台管理员强制解封幂等事实表'; + CREATE TABLE IF NOT EXISTS user_reports ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', report_id VARCHAR(96) NOT NULL COMMENT '举报单业务 ID', @@ -738,6 +816,70 @@ CREATE TABLE IF NOT EXISTS user_mic_event_consumption ( KEY idx_user_mic_event_user (app_code, user_id, created_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户麦位事件消费位点表'; +CREATE TABLE IF NOT EXISTS user_room_sessions ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + room_session_id VARCHAR(128) NOT NULL COMMENT '在房会话 ID,取 RoomUserJoined 事件 ID', + user_id BIGINT NOT NULL COMMENT '用户 ID', + room_id VARCHAR(64) NOT NULL COMMENT '房间 ID', + status VARCHAR(32) NOT NULL COMMENT '业务状态 open/ended', + is_robot TINYINT(1) NOT NULL DEFAULT 0 COMMENT '进房时固化的机器人标记,机器人会话不产生统计事实', + joined_at_ms BIGINT NOT NULL COMMENT '进房时间,UTC epoch ms', + ended_at_ms BIGINT NULL COMMENT '离房时间,UTC epoch ms', + end_reason VARCHAR(64) NOT NULL DEFAULT '' COMMENT '结束原因', + room_online_ms BIGINT NOT NULL DEFAULT 0 COMMENT '在房毫秒值', + opened_event_id VARCHAR(128) NOT NULL COMMENT '进房事件 ID', + closed_event_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '闭合事件 ID', + last_event_id VARCHAR(128) NOT NULL COMMENT '最后事件 ID', + last_room_version BIGINT NOT NULL DEFAULT 0 COMMENT '最后房间版本', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, room_session_id), + KEY idx_user_room_sessions_user_status (app_code, user_id, status, joined_at_ms), + KEY idx_user_room_sessions_room_user (app_code, room_id, user_id, status), + KEY idx_user_room_sessions_open_timeout (app_code, status, joined_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户在房会话表'; + +CREATE TABLE IF NOT EXISTS user_room_daily_stats ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + user_id BIGINT NOT NULL COMMENT '用户 ID', + stat_date CHAR(10) NOT NULL COMMENT '统计日期,UTC', + room_online_ms BIGINT NOT NULL DEFAULT 0 COMMENT '在房毫秒值', + session_count BIGINT NOT NULL DEFAULT 0 COMMENT '会话数量', + first_room_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '首次在房时间,UTC epoch ms', + last_room_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '最后在房时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, user_id, stat_date), + KEY idx_user_room_daily_date (app_code, stat_date, room_online_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户在房每日统计表'; + +CREATE TABLE IF NOT EXISTS user_room_orphan_leaves ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + event_id VARCHAR(128) NOT NULL COMMENT '离房事件 ID', + room_id VARCHAR(64) NOT NULL COMMENT '房间 ID', + user_id BIGINT NOT NULL COMMENT '用户 ID', + left_at_ms BIGINT NOT NULL COMMENT '离房时间,UTC epoch ms', + reason VARCHAR(64) NOT NULL DEFAULT '' COMMENT '离房原因', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + PRIMARY KEY (app_code, event_id), + KEY idx_user_room_orphan_room_user (app_code, room_id, user_id, left_at_ms), + KEY idx_user_room_orphan_left_at (app_code, left_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='先于进房事实到达的离房事件暂存表'; + +CREATE TABLE IF NOT EXISTS user_room_event_consumption ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + event_id VARCHAR(128) NOT NULL COMMENT '事件幂等 ID', + event_type VARCHAR(64) NOT NULL COMMENT '事件类型', + room_id VARCHAR(64) NOT NULL COMMENT '房间 ID', + user_id BIGINT NOT NULL COMMENT '用户 ID', + status VARCHAR(32) NOT NULL COMMENT '业务状态', + skip_reason VARCHAR(128) NOT NULL DEFAULT '' COMMENT '跳过原因', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + consumed_at_ms BIGINT NULL COMMENT '消费时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, event_id), + KEY idx_user_room_event_user (app_code, user_id, created_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户在房事件消费位点表'; + CREATE TABLE IF NOT EXISTS host_profiles ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', user_id BIGINT NOT NULL PRIMARY KEY COMMENT '用户 ID', @@ -784,6 +926,8 @@ CREATE TABLE IF NOT EXISTS manager_profiles ( can_grant_vip TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心赠送 VIP', can_update_user_level TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心升级用户等级', can_add_bd_leader TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心添加 BD Leader', + can_add_bd_leader_in_region TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否允许把 BD Leader 邀请范围从本国家扩展到经理所属区域', + can_manage_cross_region TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否允许经理中心所有目标用户操作跨区域执行', can_add_admin TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心添加 Admin', can_add_superadmin TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心添加 Superadmin', can_block_user TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心封禁用户', @@ -1238,6 +1382,8 @@ CREATE TABLE IF NOT EXISTS login_audit ( provider VARCHAR(64) NULL COMMENT '提供方', channel VARCHAR(32) NOT NULL DEFAULT '' COMMENT '渠道', platform VARCHAR(16) NOT NULL DEFAULT '' COMMENT '平台', + app_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '本次认证客户端版本', + build_number VARCHAR(64) NOT NULL DEFAULT '' COMMENT '本次认证客户端构建号', result VARCHAR(32) NOT NULL COMMENT '结果', failure_code VARCHAR(64) NULL COMMENT '失败编码', client_ip VARCHAR(64) NULL COMMENT '客户端IP', @@ -1250,6 +1396,7 @@ CREATE TABLE IF NOT EXISTS login_audit ( KEY idx_login_audit_request_id (app_code, request_id), KEY idx_login_audit_created_at (app_code, created_at_ms), KEY idx_login_audit_user_created (app_code, user_id, created_at_ms), + KEY idx_login_audit_latest_success (app_code, user_id, result, blocked, login_type, created_at_ms, id), KEY idx_login_audit_blocked (app_code, blocked, created_at_ms), KEY idx_login_audit_ip_country (app_code, ip_country_code, created_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='登录审计表'; @@ -1346,3 +1493,22 @@ CREATE TABLE IF NOT EXISTS login_risk_user_whitelist ( KEY idx_login_risk_user_whitelist_enabled (app_code, enabled, user_id), KEY idx_login_risk_user_whitelist_updated (app_code, updated_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='登录风险用户白名单表'; + +CREATE TABLE IF NOT EXISTS user_devices ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + user_id BIGINT NOT NULL COMMENT '用户 ID', + device_id VARCHAR(128) NOT NULL COMMENT '设备 ID,与 auth_sessions.device_id 同源', + platform VARCHAR(32) NOT NULL DEFAULT '' COMMENT '平台,android/ios', + device_model VARCHAR(128) NOT NULL DEFAULT '' COMMENT '设备型号,例如 Redmi Note 8 Pro', + manufacturer VARCHAR(64) NOT NULL DEFAULT '' COMMENT '设备厂商,例如 Xiaomi/Apple', + os_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '系统版本', + imei VARCHAR(64) NOT NULL DEFAULT '' COMMENT '设备 IMEI,Android 10+ 无特权权限时为空', + app_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '最近一次认证客户端版本', + build_number VARCHAR(64) NOT NULL DEFAULT '' COMMENT '最近一次认证客户端构建号', + first_seen_at_ms BIGINT NOT NULL COMMENT '首次认证成功时间,UTC epoch ms', + last_seen_at_ms BIGINT NOT NULL COMMENT '最近认证成功时间,UTC epoch ms', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, user_id, device_id), + KEY idx_user_devices_user_last_seen (app_code, user_id, last_seen_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户设备档案表,认证成功后按设备聚合最新上报资料'; diff --git a/services/user-service/deploy/mysql/migrations/010_admin_user_bans.sql b/services/user-service/deploy/mysql/migrations/010_admin_user_bans.sql new file mode 100644 index 00000000..abfeb90c --- /dev/null +++ b/services/user-service/deploy/mysql/migrations/010_admin_user_bans.sql @@ -0,0 +1,42 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE hyapp_user; + +-- 管理员封禁与经理中心封禁保持独立事实,解封时在 user-service 内同时检查两种来源。 +CREATE TABLE IF NOT EXISTS admin_user_bans ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + ban_id VARCHAR(96) NOT NULL COMMENT '管理员封禁事实 ID', + command_id VARCHAR(128) NOT NULL COMMENT '后台请求幂等命令 ID', + target_user_id BIGINT NOT NULL COMMENT '被封禁用户 ID', + target_old_status VARCHAR(32) NOT NULL COMMENT '创建事实前的用户状态,仅供审计', + expires_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '到期时间,UTC epoch ms;0 表示永久', + status VARCHAR(32) NOT NULL COMMENT 'active/released/expired', + reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '封禁原因', + operator_admin_id BIGINT NOT NULL COMMENT '创建封禁的后台管理员 ID', + released_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '手动解封管理员 ID;系统到期为 0', + released_reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '释放原因', + released_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '释放时间,UTC epoch ms', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, ban_id), + UNIQUE KEY uk_admin_user_bans_command (app_code, command_id), + KEY idx_admin_user_bans_target (app_code, target_user_id, status, created_at_ms), + KEY idx_admin_user_bans_expire (app_code, status, expires_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='后台管理员用户封禁事实表'; + +-- 解封命令必须独立保存幂等事实:后台在合并摘要只看到经理封禁时会按用户释放最新管理员封禁, +-- 若 HTTP/gRPC 超时后重试,不能继续释放下一条重叠的管理员封禁。 +CREATE TABLE IF NOT EXISTS admin_user_ban_release_commands ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + command_id VARCHAR(128) NOT NULL COMMENT '稳定解封命令 ID,通常取 request_id', + target_user_id BIGINT NOT NULL COMMENT '目标用户 ID', + requested_ban_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '调用方指定 ban_id;空表示选择最新 active 管理员封禁', + resolved_ban_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '本命令实际释放的 ban_id', + release_status VARCHAR(32) NOT NULL COMMENT 'released/expired', + operator_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '手动操作管理员 ID;系统到期为 0', + reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '释放原因快照,用于校验幂等载荷', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, command_id), + KEY idx_admin_ban_release_resolved (app_code, resolved_ban_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='后台管理员解封命令幂等事实表'; diff --git a/services/user-service/deploy/mysql/migrations/011_user_room_sessions.sql b/services/user-service/deploy/mysql/migrations/011_user_room_sessions.sql new file mode 100644 index 00000000..572058f2 --- /dev/null +++ b/services/user-service/deploy/mysql/migrations/011_user_room_sessions.sql @@ -0,0 +1,69 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE hyapp_user; + +-- 用户在房时长服务端事实:消费 room outbox 的 RoomUserJoined/RoomUserLeft/RoomUserKicked, +-- 替代只靠客户端 room_stay 埋点的口径(杀进程丢时长导致进房时长偏低甚至小于上麦时长)。 +CREATE TABLE IF NOT EXISTS user_room_sessions ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + room_session_id VARCHAR(128) NOT NULL COMMENT '在房会话 ID,取 RoomUserJoined 事件 ID', + user_id BIGINT NOT NULL COMMENT '用户 ID', + room_id VARCHAR(64) NOT NULL COMMENT '房间 ID', + status VARCHAR(32) NOT NULL COMMENT '业务状态 open/ended', + is_robot TINYINT(1) NOT NULL DEFAULT 0 COMMENT '进房时固化的机器人标记,机器人会话不产生统计事实', + joined_at_ms BIGINT NOT NULL COMMENT '进房时间,UTC epoch ms', + ended_at_ms BIGINT NULL COMMENT '离房时间,UTC epoch ms', + end_reason VARCHAR(64) NOT NULL DEFAULT '' COMMENT '结束原因', + room_online_ms BIGINT NOT NULL DEFAULT 0 COMMENT '在房毫秒值', + opened_event_id VARCHAR(128) NOT NULL COMMENT '进房事件 ID', + closed_event_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '闭合事件 ID', + last_event_id VARCHAR(128) NOT NULL COMMENT '最后事件 ID', + last_room_version BIGINT NOT NULL DEFAULT 0 COMMENT '最后房间版本', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, room_session_id), + KEY idx_user_room_sessions_user_status (app_code, user_id, status, joined_at_ms), + KEY idx_user_room_sessions_room_user (app_code, room_id, user_id, status), + KEY idx_user_room_sessions_open_timeout (app_code, status, joined_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户在房会话表'; + +CREATE TABLE IF NOT EXISTS user_room_daily_stats ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + user_id BIGINT NOT NULL COMMENT '用户 ID', + stat_date CHAR(10) NOT NULL COMMENT '统计日期,UTC', + room_online_ms BIGINT NOT NULL DEFAULT 0 COMMENT '在房毫秒值', + session_count BIGINT NOT NULL DEFAULT 0 COMMENT '会话数量', + first_room_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '首次在房时间,UTC epoch ms', + last_room_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '最后在房时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, user_id, stat_date), + KEY idx_user_room_daily_date (app_code, stat_date, room_online_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户在房每日统计表'; + +CREATE TABLE IF NOT EXISTS user_room_orphan_leaves ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + event_id VARCHAR(128) NOT NULL COMMENT '离房事件 ID', + room_id VARCHAR(64) NOT NULL COMMENT '房间 ID', + user_id BIGINT NOT NULL COMMENT '用户 ID', + left_at_ms BIGINT NOT NULL COMMENT '离房时间,UTC epoch ms', + reason VARCHAR(64) NOT NULL DEFAULT '' COMMENT '离房原因', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + PRIMARY KEY (app_code, event_id), + KEY idx_user_room_orphan_room_user (app_code, room_id, user_id, left_at_ms), + KEY idx_user_room_orphan_left_at (app_code, left_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='先于进房事实到达的离房事件暂存表'; + +CREATE TABLE IF NOT EXISTS user_room_event_consumption ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + event_id VARCHAR(128) NOT NULL COMMENT '事件幂等 ID', + event_type VARCHAR(64) NOT NULL COMMENT '事件类型', + room_id VARCHAR(64) NOT NULL COMMENT '房间 ID', + user_id BIGINT NOT NULL COMMENT '用户 ID', + status VARCHAR(32) NOT NULL COMMENT '业务状态', + skip_reason VARCHAR(128) NOT NULL DEFAULT '' COMMENT '跳过原因', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + consumed_at_ms BIGINT NULL COMMENT '消费时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, event_id), + KEY idx_user_room_event_user (app_code, user_id, created_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户在房事件消费位点表'; diff --git a/services/user-service/deploy/mysql/migrations/012_login_audit_client_version.sql b/services/user-service/deploy/mysql/migrations/012_login_audit_client_version.sql new file mode 100644 index 00000000..ea1a138a --- /dev/null +++ b/services/user-service/deploy/mysql/migrations/012_login_audit_client_version.sql @@ -0,0 +1,32 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE hyapp_user; + +-- 登录版本画像必须记录每次真实认证携带的客户端版本,而不是复用用户注册时的一次性版本快照。 +-- 所有 DDL 都先检查 information_schema,使已通过 initdb 创建新字段的环境也可以安全重复执行。 +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'login_audit' AND COLUMN_NAME = 'app_version') = 0, + 'ALTER TABLE login_audit ADD COLUMN app_version VARCHAR(64) NOT NULL DEFAULT '''' COMMENT ''本次认证客户端版本'' AFTER platform', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'login_audit' AND COLUMN_NAME = 'build_number') = 0, + 'ALTER TABLE login_audit ADD COLUMN build_number VARCHAR(64) NOT NULL DEFAULT '''' COMMENT ''本次认证客户端构建号'' AFTER app_version', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'login_audit' AND INDEX_NAME = 'idx_login_audit_latest_success') = 0, + 'ALTER TABLE login_audit ADD INDEX idx_login_audit_latest_success (app_code, user_id, result, blocked, login_type, created_at_ms, id)', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/services/user-service/deploy/mysql/migrations/013_user_devices.sql b/services/user-service/deploy/mysql/migrations/013_user_devices.sql new file mode 100644 index 00000000..aff2ac74 --- /dev/null +++ b/services/user-service/deploy/mysql/migrations/013_user_devices.sql @@ -0,0 +1,26 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE hyapp_user; + +-- 后台用户详情设备 tab 需要注册之后的持续设备画像:users.register_device/os_version 只是注册时一次性快照, +-- 真实客户端此前从未上报,线上均为空。user_devices 在每次认证成功(登录/注册/refresh)后按 +-- (app_code, user_id, device_id) 聚合客户端设备头,字段以“非空覆盖”合并,空值不抹掉历史上报。 +-- CREATE TABLE IF NOT EXISTS 使已通过 initdb 建表的环境可以安全重复执行。 +CREATE TABLE IF NOT EXISTS user_devices ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + user_id BIGINT NOT NULL COMMENT '用户 ID', + device_id VARCHAR(128) NOT NULL COMMENT '设备 ID,与 auth_sessions.device_id 同源', + platform VARCHAR(32) NOT NULL DEFAULT '' COMMENT '平台,android/ios', + device_model VARCHAR(128) NOT NULL DEFAULT '' COMMENT '设备型号,例如 Redmi Note 8 Pro', + manufacturer VARCHAR(64) NOT NULL DEFAULT '' COMMENT '设备厂商,例如 Xiaomi/Apple', + os_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '系统版本', + imei VARCHAR(64) NOT NULL DEFAULT '' COMMENT '设备 IMEI,Android 10+ 无特权权限时为空', + app_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '最近一次认证客户端版本', + build_number VARCHAR(64) NOT NULL DEFAULT '' COMMENT '最近一次认证客户端构建号', + first_seen_at_ms BIGINT NOT NULL COMMENT '首次认证成功时间,UTC epoch ms', + last_seen_at_ms BIGINT NOT NULL COMMENT '最近认证成功时间,UTC epoch ms', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, user_id, device_id), + KEY idx_user_devices_user_last_seen (app_code, user_id, last_seen_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户设备档案表,认证成功后按设备聚合最新上报资料'; diff --git a/services/user-service/deploy/mysql/migrations/014_host_center_engagement_stats.sql b/services/user-service/deploy/mysql/migrations/014_host_center_engagement_stats.sql new file mode 100644 index 00000000..8a37f90f --- /dev/null +++ b/services/user-service/deploy/mysql/migrations/014_host_center_engagement_stats.sql @@ -0,0 +1,23 @@ +USE hyapp_user; + +CREATE TABLE IF NOT EXISTS user_follow_events ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + event_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '关注状态变化事件 ID', + follower_user_id BIGINT NOT NULL COMMENT '关注人用户 ID', + followee_user_id BIGINT NOT NULL COMMENT '被关注人用户 ID', + delta TINYINT NOT NULL COMMENT '关注 +1,取消关注 -1', + occurred_at_ms BIGINT NOT NULL COMMENT '事件时间,UTC epoch ms', + PRIMARY KEY (event_id), + KEY idx_user_follow_events_followee_time (app_code, followee_user_id, occurred_at_ms, event_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户关注净增统计事件表'; + +CREATE TABLE IF NOT EXISTS user_private_message_events ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + event_id VARCHAR(128) NOT NULL COMMENT '腾讯 IM 私信事件幂等 ID', + sender_user_id BIGINT NOT NULL COMMENT '消息发送用户 ID', + target_user_id BIGINT NOT NULL COMMENT '消息接收用户 ID', + occurred_at_ms BIGINT NOT NULL COMMENT '发送时间,UTC epoch ms', + created_at_ms BIGINT NOT NULL COMMENT '落库时间,UTC epoch ms', + PRIMARY KEY (app_code, event_id), + KEY idx_private_message_target_time (app_code, target_user_id, occurred_at_ms, sender_user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户私信发送事实,仅保存统计字段'; diff --git a/services/user-service/deploy/mysql/migrations/015_admin_force_unban.sql b/services/user-service/deploy/mysql/migrations/015_admin_force_unban.sql new file mode 100644 index 00000000..36264db2 --- /dev/null +++ b/services/user-service/deploy/mysql/migrations/015_admin_force_unban.sql @@ -0,0 +1,21 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE hyapp_user; + +-- 后台强制解封会同时释放管理员和经理封禁;独立命令事实保证 gRPC 超时重试不会释放操作完成后新产生的封禁。 +CREATE TABLE IF NOT EXISTS admin_user_force_unban_commands ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + command_id VARCHAR(128) NOT NULL COMMENT '稳定强制解封命令 ID,取 request_id', + target_user_id BIGINT NOT NULL COMMENT '目标用户 ID', + operator_admin_id BIGINT NOT NULL COMMENT '执行强制解封的后台管理员 ID', + reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '管理员解封原因快照,用于校验幂等载荷', + released_admin_ban_count BIGINT NOT NULL DEFAULT 0 COMMENT '本命令释放的管理员封禁数量', + released_manager_block_count BIGINT NOT NULL DEFAULT 0 COMMENT '本命令释放的经理封禁数量', + old_status VARCHAR(32) NOT NULL DEFAULT '' COMMENT '首次执行前用户主状态', + new_status VARCHAR(32) NOT NULL DEFAULT '' COMMENT '首次执行后用户主状态', + status_restored BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否把 banned/disabled 恢复为 active', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, command_id), + KEY idx_admin_force_unban_target (app_code, target_user_id, created_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='后台管理员强制解封幂等事实表'; diff --git a/services/user-service/internal/app/app.go b/services/user-service/internal/app/app.go index 77a9b9cb..1a2680dc 100644 --- a/services/user-service/internal/app/app.go +++ b/services/user-service/internal/app/app.go @@ -20,6 +20,7 @@ import ( "hyapp/pkg/healthhttp" "hyapp/pkg/idgen" "hyapp/pkg/logx" + "hyapp/pkg/outboxpartition" "hyapp/pkg/rocketmqx" "hyapp/pkg/roommq" serviceapp "hyapp/pkg/servicekit/app" @@ -33,6 +34,7 @@ import ( cpdomain "hyapp/services/user-service/internal/domain/cp" invitedomain "hyapp/services/user-service/internal/domain/invite" mictimedomain "hyapp/services/user-service/internal/domain/mictime" + roomtimedomain "hyapp/services/user-service/internal/domain/roomtime" "hyapp/services/user-service/internal/integration/activityclient" "hyapp/services/user-service/internal/integration/roomclient" "hyapp/services/user-service/internal/integration/walletclient" @@ -41,6 +43,7 @@ import ( hostservice "hyapp/services/user-service/internal/service/host" inviteservice "hyapp/services/user-service/internal/service/invite" mictimeservice "hyapp/services/user-service/internal/service/mictime" + roomtimeservice "hyapp/services/user-service/internal/service/roomtime" userservice "hyapp/services/user-service/internal/service/user" mysqlstorage "hyapp/services/user-service/internal/storage/mysql" grpcserver "hyapp/services/user-service/internal/transport/grpc" @@ -64,6 +67,8 @@ type App struct { mqConsumers []*rocketmqx.Consumer // userOutboxProducer 把 user_outbox 事实发布到 RocketMQ,供统计等读模型消费。 userOutboxProducer *rocketmqx.Producer + // userOutboxPartitions 缓存真实 App 列表并提供跨 worker 的公平轮转游标。 + userOutboxPartitions *outboxpartition.Partitions // roomConn 是低频平台治理调用 room-service 的 gRPC 连接。 roomConn *grpc.ClientConn // activityConn 是注册完成后触发注册奖励的 gRPC 连接。 @@ -78,6 +83,8 @@ type App struct { inviteSvc *inviteservice.Service // micTimeSvc 消费 room mic outbox,维护用户维度麦上时长 read model。 micTimeSvc *mictimeservice.Service + // roomTimeSvc 消费 room presence outbox,维护用户维度在房时长 read model。 + roomTimeSvc *roomtimeservice.Service // cpSvc 消费 room gift outbox,维护 CP/兄弟/姐妹申请和亲密值。 cpSvc *cpservice.Service // loginRiskRedisClose 关闭登录风险 Redis cache;cache 不作为启动硬依赖。 @@ -103,6 +110,14 @@ func New(cfg config.Config) (*App, error) { if err != nil { return nil, err } + if cfg.MySQLAutoMigrate { + // 在线迁移必须在创建 gRPC listener 前完成;否则实例可能先通过端口探测,再因 login_audit + // 缺列导致“登录成功但审计丢失”。迁移失败时直接关闭连接池并阻止实例接流量。 + if err := mysqlRepo.Migrate(startupCtx); err != nil { + _ = mysqlRepo.Close() + return nil, err + } + } // listener 在 New 阶段创建,端口占用会作为启动失败返回。 listener, err := net.Listen("tcp", cfg.GRPCAddr) if err != nil { @@ -133,6 +148,7 @@ func New(cfg config.Config) (*App, error) { hostRepo := mysqlRepo.HostRepository() inviteRepo := mysqlRepo.InviteRepository() micTimeRepo := mysqlRepo.MicTimeRepository() + roomTimeRepo := mysqlRepo.RoomTimeRepository() cpRepo := mysqlRepo.CPRepository() var ipDecisionCache authservice.IPDecisionCache var registerRiskConfigCache authservice.RegisterRiskConfigCache @@ -313,12 +329,13 @@ func New(cfg config.Config) (*App, error) { ) inviteSvc := inviteservice.New(inviteRepo) micTimeSvc := mictimeservice.New(micTimeRepo) + roomTimeSvc := roomtimeservice.New(roomTimeRepo) cpSvc := cpservice.New(cpRepo, // CP service 同时承担送礼事件消费和榜单读模型刷新;Redis/wallet 依赖只在榜单相关方法里被强校验。 cpservice.WithIntimacyLeaderboardStore(cpLeaderboardStore), cpservice.WithAvatarFrameReader(avatarFrameReader), ) - mqConsumers, err := newMQConsumers(cfg, inviteSvc, micTimeSvc, cpSvc) + mqConsumers, err := newMQConsumers(cfg, inviteSvc, micTimeSvc, roomTimeSvc, cpSvc) if err != nil { if loginRiskRedisClose != nil { _ = loginRiskRedisClose() @@ -366,6 +383,7 @@ func New(cfg config.Config) (*App, error) { } userServer := grpcserver.NewServer(authSvc, userSvc, hostSvc) userServer.SetMicTimeService(micTimeSvc) + userServer.SetRoomTimeService(roomTimeSvc) userServer.SetCPService(cpSvc) // 多个 protobuf service 共用一个 Server 适配器,领域逻辑仍拆在 auth/user/host service。 userv1.RegisterAuthServiceServer(server, userServer) @@ -420,6 +438,7 @@ func New(cfg config.Config) (*App, error) { mysqlRepo: mysqlRepo, mqConsumers: mqConsumers, userOutboxProducer: userOutboxProducer, + userOutboxPartitions: outboxpartition.New(cfg.OutboxWorker.AppCodes, outboxpartition.DefaultDiscoveryRefreshInterval), roomConn: roomConn, activityConn: activityConn, walletConn: walletConn, @@ -427,6 +446,7 @@ func New(cfg config.Config) (*App, error) { userSvc: userSvc, inviteSvc: inviteSvc, micTimeSvc: micTimeSvc, + roomTimeSvc: roomTimeSvc, cpSvc: cpSvc, loginRiskRedisClose: loginRiskRedisClose, cpLeaderboardRedisClose: cpLeaderboardRedisClose, @@ -523,7 +543,18 @@ func (a *App) runUserOutboxWorker() { } func (a *App) processUserOutboxBatch(workerID string) (int, error) { - records, err := a.mysqlRepo.ClaimPendingUserOutbox(a.workers.Context(), workerID, a.cfg.OutboxWorker.BatchSize) + ctx := a.workers.Context() + appCodes, discoveryErr := a.userOutboxPartitions.Resolve(ctx, a.mysqlRepo.ListUserOutboxAppCodes) + if discoveryErr != nil { + if len(appCodes) == 0 { + return 0, discoveryErr + } + // 发现刷新失败时沿用旧列表;缓存会在五分钟后再试,不阻断既有 App 的事实发布。 + logx.Warn(ctx, "user_outbox_app_discovery_stale", slog.String("worker_id", workerID), slog.String("error", discoveryErr.Error())) + } + records, err := outboxpartition.ClaimFair(ctx, a.userOutboxPartitions.Order(appCodes), a.cfg.OutboxWorker.BatchSize, func(claimCtx context.Context, appCode string, limit int) ([]mysqlstorage.UserOutboxRecord, error) { + return a.mysqlRepo.ClaimPendingUserOutbox(appcode.WithContext(claimCtx, appCode), workerID, limit) + }) if err != nil { return 0, err } @@ -630,14 +661,20 @@ func (i tencentIMAccountImporter) ImportAccount(ctx context.Context, userID int6 }) } -func newMQConsumers(cfg config.Config, inviteSvc *inviteservice.Service, micTimeSvc *mictimeservice.Service, cpSvc *cpservice.Service) ([]*rocketmqx.Consumer, error) { - consumers := make([]*rocketmqx.Consumer, 0, 3) +func newMQConsumers(cfg config.Config, inviteSvc *inviteservice.Service, micTimeSvc *mictimeservice.Service, roomTimeSvc *roomtimeservice.Service, cpSvc *cpservice.Service) ([]*rocketmqx.Consumer, error) { + consumers := make([]*rocketmqx.Consumer, 0, 4) if cfg.InviteRechargeWorker.Enabled && cfg.RocketMQ.WalletOutbox.Enabled { + // 邀请关系只消费充值成功事实。兼容期仍订阅 legacy Tag 以排空 producer + // 切换前的存量消息,typed 消息则在 Broker 侧只投递充值事件。 + walletTagExpression, err := walletmq.LegacyCompatibleTagExpression(walletmq.EventTypeWalletRechargeRecorded) + if err != nil { + return nil, err + } consumer, err := rocketmqx.NewConsumer(walletOutboxConsumerConfig(cfg.RocketMQ)) if err != nil { return nil, err } - if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletTagExpression, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { event, ok, err := inviteRechargeEventFromWalletMessage(message.Body) if err != nil || !ok { return err @@ -656,7 +693,7 @@ func newMQConsumers(cfg config.Config, inviteSvc *inviteservice.Service, micTime shutdownMQConsumers(consumers) return nil, err } - if err := consumer.Subscribe(cfg.RocketMQ.RoomOutbox.Topic, roommq.TagRoomOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + if err := subscribeRoomOutboxEvents(consumer, cfg.RocketMQ.RoomOutbox.Topic, []string{roommq.EventTypeRoomMicChanged}, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { event, ok, err := roomMicEventFromRoomMessage(message.Body) if err != nil || !ok { return err @@ -670,6 +707,31 @@ func newMQConsumers(cfg config.Config, inviteSvc *inviteservice.Service, micTime } consumers = append(consumers, consumer) } + if cfg.RoomTimeWorker.Enabled && cfg.RocketMQ.RoomOutbox.Enabled { + // 在房时长消费使用独立 consumer group,保证和麦位时长/CP 读模型各自拥有 MQ 位点和重试节奏。 + consumer, err := rocketmqx.NewConsumer(roomTimeOutboxConsumerConfig(cfg)) + if err != nil { + shutdownMQConsumers(consumers) + return nil, err + } + if err := subscribeRoomOutboxEvents(consumer, cfg.RocketMQ.RoomOutbox.Topic, []string{ + roommq.EventTypeRoomUserJoined, + roommq.EventTypeRoomUserLeft, + roommq.EventTypeRoomUserKicked, + }, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + event, ok, err := roomPresenceEventFromRoomMessage(message.Body) + if err != nil || !ok { + return err + } + _, err = roomTimeSvc.ConsumeRoomPresenceEvent(appcode.WithContext(ctx, event.AppCode), event) + return err + }); err != nil { + _ = consumer.Shutdown() + shutdownMQConsumers(consumers) + return nil, err + } + consumers = append(consumers, consumer) + } if cfg.CPRelationshipWorker.Enabled && cfg.RocketMQ.RoomOutbox.Enabled { // CP 关系消费使用独立 consumer group,保证和麦位时长读模型各自拥有 MQ 位点和重试节奏。 consumer, err := rocketmqx.NewConsumer(cpRoomOutboxConsumerConfig(cfg)) @@ -677,7 +739,7 @@ func newMQConsumers(cfg config.Config, inviteSvc *inviteservice.Service, micTime shutdownMQConsumers(consumers) return nil, err } - if err := consumer.Subscribe(cfg.RocketMQ.RoomOutbox.Topic, roommq.TagRoomOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + if err := subscribeRoomOutboxEvents(consumer, cfg.RocketMQ.RoomOutbox.Topic, []string{roommq.EventTypeRoomGiftSent}, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { event, ok, err := roomGiftCPEventFromRoomMessage(message.Body) if err != nil || !ok { return err @@ -694,6 +756,17 @@ func newMQConsumers(cfg config.Config, inviteSvc *inviteservice.Service, micTime return consumers, nil } +// subscribeRoomOutboxEvents 是 user-service 的 room typed-tag 迁移边界。所有 +// consumer group 在 Phase A 都保留 legacy 分支以排空旧消息,Phase B 的 typed +// 消息才由 Broker 精确路由到麦时长、在房时长或 CP 关系各自的业务位点。 +func subscribeRoomOutboxEvents(consumer *rocketmqx.Consumer, topic string, eventTypes []string, handler rocketmqx.Handler) error { + tagExpression, err := roommq.LegacyCompatibleTagExpression(eventTypes...) + if err != nil { + return err + } + return consumer.Subscribe(topic, tagExpression, handler) +} + func inviteRechargeEventFromWalletMessage(body []byte) (invitedomain.RechargeEvent, bool, error) { message, err := walletmq.DecodeWalletOutboxMessage(body) if err != nil { @@ -750,6 +823,46 @@ func roomMicEventFromRoomMessage(body []byte) (mictimedomain.RoomMicEvent, bool, }, true, nil } +func roomPresenceEventFromRoomMessage(body []byte) (roomtimedomain.RoomPresenceEvent, bool, error) { + envelope, _, err := roommq.DecodeRoomOutboxMessage(body) + if err != nil { + return roomtimedomain.RoomPresenceEvent{}, false, err + } + event := roomtimedomain.RoomPresenceEvent{ + AppCode: appcode.Normalize(envelope.GetAppCode()), + EventID: envelope.GetEventId(), + EventType: envelope.GetEventType(), + RoomID: envelope.GetRoomId(), + RoomVersion: envelope.GetRoomVersion(), + OccurredAtMs: envelope.GetOccurredAtMs(), + } + switch envelope.GetEventType() { + case roomtimedomain.RoomUserJoinedEventType: + var joined roomeventsv1.RoomUserJoined + if err := proto.Unmarshal(envelope.GetBody(), &joined); err != nil { + return roomtimedomain.RoomPresenceEvent{}, false, err + } + event.UserID = joined.GetUserId() + event.IsRobot = joined.GetIsRobot() + case roomtimedomain.RoomUserLeftEventType: + var left roomeventsv1.RoomUserLeft + if err := proto.Unmarshal(envelope.GetBody(), &left); err != nil { + return roomtimedomain.RoomPresenceEvent{}, false, err + } + event.UserID = left.GetUserId() + case roomtimedomain.RoomUserKickedEventType: + // 踢人/系统驱逐/关房清场只发 RoomUserKicked,不再补 RoomUserLeft;presence 主体是 target_user_id。 + var kicked roomeventsv1.RoomUserKicked + if err := proto.Unmarshal(envelope.GetBody(), &kicked); err != nil { + return roomtimedomain.RoomPresenceEvent{}, false, err + } + event.UserID = kicked.GetTargetUserId() + default: + return roomtimedomain.RoomPresenceEvent{}, false, nil + } + return event, true, nil +} + func roomGiftCPEventFromRoomMessage(body []byte) (cpdomain.GiftEvent, bool, error) { envelope, _, err := roommq.DecodeRoomOutboxMessage(body) if err != nil { @@ -835,6 +948,10 @@ func cpRoomOutboxConsumerConfig(cfg config.Config) rocketmqx.ConsumerConfig { return rocketmqConsumerConfig(cfg.RocketMQ, cfg.CPRelationshipWorker.ConsumerGroup, cfg.RocketMQ.RoomOutbox.ConsumerMaxReconsumeTimes) } +func roomTimeOutboxConsumerConfig(cfg config.Config) rocketmqx.ConsumerConfig { + return rocketmqConsumerConfig(cfg.RocketMQ, cfg.RoomTimeWorker.ConsumerGroup, cfg.RocketMQ.RoomOutbox.ConsumerMaxReconsumeTimes) +} + func userOutboxProducerConfig(cfg config.RocketMQConfig) rocketmqx.ProducerConfig { return rocketmqx.ProducerConfig{ EndpointConfig: rocketmqx.EndpointConfig{ diff --git a/services/user-service/internal/app/outbox_replay.go b/services/user-service/internal/app/outbox_replay.go new file mode 100644 index 00000000..11efadd6 --- /dev/null +++ b/services/user-service/internal/app/outbox_replay.go @@ -0,0 +1,60 @@ +package app + +import ( + "context" + "errors" + "fmt" + "time" + + "hyapp/pkg/outboxpartition" + "hyapp/pkg/outboxreplay" + "hyapp/pkg/rocketmqx" + serviceapp "hyapp/pkg/servicekit/app" + servicemq "hyapp/pkg/servicekit/mq" + "hyapp/services/user-service/internal/config" + mysqlstorage "hyapp/services/user-service/internal/storage/mysql" +) + +// ReplayUserOutboxOnce processes a process-wide bounded App batch without +// starting user-service listeners or its unrelated consumers. Claiming one row +// per iteration keeps the existing 30-second row lease attached only to the +// message currently being published. +func ReplayUserOutboxOnce(ctx context.Context, cfg config.Config, scope outboxreplay.Scope) (outboxreplay.Result, error) { + validatedScope, err := outboxreplay.NewScope(scope.AppCode, scope.Limit, 500) + if err != nil { + return outboxreplay.Result{}, err + } + if !cfg.RocketMQ.UserOutbox.Enabled { + return outboxreplay.Result{}, errors.New("user RocketMQ outbox is not enabled") + } + repository, err := mysqlstorage.Open(ctx, cfg.MySQLDSN) + if err != nil { + return outboxreplay.Result{}, err + } + defer func() { _ = repository.Close() }() + producer, err := rocketmqx.NewProducer(userOutboxProducerConfig(cfg.RocketMQ)) + if err != nil { + return outboxreplay.Result{}, err + } + if err := servicemq.StartProducers([]*rocketmqx.Producer{producer}); err != nil { + return outboxreplay.Result{}, err + } + defer servicemq.ShutdownProducers([]*rocketmqx.Producer{producer}) + + replayConfig := cfg + replayConfig.OutboxWorker.BatchSize = 1 + replayConfig.OutboxWorker.AppCodes = []string{validatedScope.AppCode} + workers := serviceapp.NewBackground(ctx) + defer workers.StopAndWait() + replayApp := &App{ + mysqlRepo: repository, + userOutboxProducer: producer, + userOutboxPartitions: outboxpartition.New(replayConfig.OutboxWorker.AppCodes, outboxpartition.DefaultDiscoveryRefreshInterval), + cfg: replayConfig, + workers: workers, + } + workerID := fmt.Sprintf("ops-user-outbox-%s-%d", validatedScope.AppCode, time.Now().UTC().UnixMilli()) + return outboxreplay.Run(ctx, validatedScope, func(context.Context) (int, error) { + return replayApp.processUserOutboxBatch(workerID) + }) +} diff --git a/services/user-service/internal/config/config.go b/services/user-service/internal/config/config.go index 41db61fa..697dca98 100644 --- a/services/user-service/internal/config/config.go +++ b/services/user-service/internal/config/config.go @@ -10,6 +10,7 @@ import ( "hyapp/pkg/appcode" "hyapp/pkg/configx" "hyapp/pkg/logx" + "hyapp/pkg/outboxpartition" "hyapp/pkg/tencentim" ) @@ -39,6 +40,8 @@ type Config struct { InviteRechargeWorker InviteRechargeWorkerConfig `yaml:"invite_recharge_worker"` // MicTimeWorker 控制 room-service 麦位事件消费和用户麦上时长聚合。 MicTimeWorker MicTimeWorkerConfig `yaml:"mic_time_worker"` + // RoomTimeWorker 控制 room-service 进退房事件消费和用户在房时长聚合。 + RoomTimeWorker RoomTimeWorkerConfig `yaml:"room_time_worker"` // CPRelationshipWorker 控制 room-service 送礼事件消费和 CP 关系申请/亲密值维护。 CPRelationshipWorker CPRelationshipWorkerConfig `yaml:"cp_relationship_worker"` // CPIntimacyLeaderboard 控制 CP 亲密值排行榜 Redis 读模型。 @@ -186,6 +189,14 @@ type MicTimeWorkerConfig struct { Enabled bool `yaml:"enabled"` } +// RoomTimeWorkerConfig 保存房间进退房事件消费策略。 +type RoomTimeWorkerConfig struct { + // Enabled 控制是否启动 room outbox MQ 消费;关闭后查询仍可读取已聚合数据。 + Enabled bool `yaml:"enabled"` + // ConsumerGroup 必须独立于麦位/CP 消费,避免读模型共享 MQ 位点。 + ConsumerGroup string `yaml:"consumer_group"` +} + // CPRelationshipWorkerConfig 保存 CP 礼物事件消费策略。 type CPRelationshipWorkerConfig struct { // Enabled 控制是否启动 room gift MQ 消费;关闭后只能处理历史已消费关系。 @@ -205,7 +216,9 @@ type CPIntimacyLeaderboardConfig struct { // OutboxWorkerConfig 保存 user_outbox MQ 发布策略。 type OutboxWorkerConfig struct { - Enabled bool `yaml:"enabled"` + Enabled bool `yaml:"enabled"` + // AppCodes 是可投递租户 allowlist;空值使用低频数据库发现,非空支持按 App 灰度回放。 + AppCodes []string `yaml:"app_codes"` PollInterval time.Duration `yaml:"poll_interval"` BatchSize int `yaml:"batch_size"` PublishTimeout time.Duration `yaml:"publish_timeout"` @@ -290,6 +303,10 @@ func Default() Config { MicTimeWorker: MicTimeWorkerConfig{ Enabled: false, }, + RoomTimeWorker: RoomTimeWorkerConfig{ + Enabled: false, + ConsumerGroup: "hyapp-user-roomtime-room-outbox", + }, CPRelationshipWorker: CPRelationshipWorkerConfig{ Enabled: false, ConsumerGroup: "hyapp-user-cp-room-outbox", @@ -455,6 +472,13 @@ func Load(path string) (Config, error) { if cfg.MicTimeWorker.Enabled && !cfg.RocketMQ.RoomOutbox.Enabled { return Config{}, errors.New("mic_time_worker requires rocketmq.room_outbox.enabled") } + cfg.RoomTimeWorker.ConsumerGroup = strings.TrimSpace(cfg.RoomTimeWorker.ConsumerGroup) + if cfg.RoomTimeWorker.ConsumerGroup == "" { + cfg.RoomTimeWorker.ConsumerGroup = "hyapp-user-roomtime-room-outbox" + } + if cfg.RoomTimeWorker.Enabled && !cfg.RocketMQ.RoomOutbox.Enabled { + return Config{}, errors.New("room_time_worker requires rocketmq.room_outbox.enabled") + } cfg.CPRelationshipWorker.ConsumerGroup = strings.TrimSpace(cfg.CPRelationshipWorker.ConsumerGroup) if cfg.CPRelationshipWorker.ConsumerGroup == "" { cfg.CPRelationshipWorker.ConsumerGroup = "hyapp-user-cp-room-outbox" @@ -471,6 +495,7 @@ func Load(path string) (Config, error) { if cfg.OutboxWorker.PublishTimeout <= 0 { cfg.OutboxWorker.PublishTimeout = 3 * time.Second } + cfg.OutboxWorker.AppCodes = outboxpartition.Normalize(cfg.OutboxWorker.AppCodes, false) if cfg.OutboxWorker.Enabled && !cfg.RocketMQ.UserOutbox.Enabled { return Config{}, errors.New("outbox_worker requires rocketmq.user_outbox.enabled") } diff --git a/services/user-service/internal/config/outbox_worker_test.go b/services/user-service/internal/config/outbox_worker_test.go new file mode 100644 index 00000000..7196709a --- /dev/null +++ b/services/user-service/internal/config/outbox_worker_test.go @@ -0,0 +1,32 @@ +package config + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestOutboxWorkerAppCodesNormalizeForStagedReplay(t *testing.T) { + path := filepath.Join(t.TempDir(), "config.yaml") + if err := os.WriteFile(path, []byte("outbox_worker:\n app_codes: [' LALU ', fami, HUWAA, fami]\n"), 0o600); err != nil { + t.Fatalf("write config: %v", err) + } + cfg, err := Load(path) + if err != nil { + t.Fatalf("load staged outbox config: %v", err) + } + if got := strings.Join(cfg.OutboxWorker.AppCodes, ","); got != "lalu,fami,huwaa" { + t.Fatalf("normalized outbox app_codes = %q", got) + } +} + +func TestLocalOutboxWorkerEmptyAppCodesUsesDiscovery(t *testing.T) { + cfg, err := Load("../../configs/config.yaml") + if err != nil { + t.Fatalf("load local config: %v", err) + } + if len(cfg.OutboxWorker.AppCodes) != 0 { + t.Fatalf("local empty app_codes must retain automatic discovery: %v", cfg.OutboxWorker.AppCodes) + } +} diff --git a/services/user-service/internal/domain/auth/auth.go b/services/user-service/internal/domain/auth/auth.go index 11771e3d..d3351aea 100644 --- a/services/user-service/internal/domain/auth/auth.go +++ b/services/user-service/internal/domain/auth/auth.go @@ -118,6 +118,10 @@ type LoginAudit struct { Channel string // Platform 是客户端平台快照,例如 android/ios。 Platform string + // AppVersion 是本次认证请求携带的客户端语义版本;空值表示客户端未上报,不能回退到注册版本。 + AppVersion string + // BuildNumber 是本次认证请求携带的精确构建号,用于区分同一语义版本的不同安装包。 + BuildNumber string // Result 是 success 或 failed。 Result string // FailureCode 保存稳定错误 reason,不能写敏感凭证。 @@ -136,6 +140,39 @@ type LoginAudit struct { CreatedAtMs int64 } +// UserDevice 是认证成功后按 (app_code, user_id, device_id) 聚合的设备档案快照。 +// 它只服务后台设备画像展示,不参与登录判定;字段允许为空,客户端上报能力决定完整度。 +type UserDevice struct { + // AppCode 是设备档案所属 App 租户键。 + AppCode string + // UserID 是设备归属用户。 + UserID int64 + // DeviceID 与 auth_sessions.device_id 同源(android_ / ios_)。 + DeviceID string + // Platform 是客户端平台快照,例如 android/ios。 + Platform string + // DeviceModel 是设备型号,例如 Redmi Note 8 Pro。 + DeviceModel string + // Manufacturer 是设备厂商,例如 Xiaomi/Apple。 + Manufacturer string + // OSVersion 是系统版本,例如 Android 的 "13" 或 iOS 的 "17.5"。 + OSVersion string + // IMEI 在 Android 10+ 无特权权限时为空,字段保留给可获取的旧设备。 + IMEI string + // AppVersion 是最近一次认证成功时的客户端语义版本。 + AppVersion string + // BuildNumber 是最近一次认证成功时的客户端构建号。 + BuildNumber string + // FirstSeenAtMs 是该设备首次通过认证的时间。 + FirstSeenAtMs int64 + // LastSeenAtMs 是该设备最近一次认证成功时间,后台按它倒序展示设备列表。 + LastSeenAtMs int64 + // CreatedAtMs 是档案创建时间。 + CreatedAtMs int64 + // UpdatedAtMs 是档案更新时间。 + UpdatedAtMs int64 +} + // ThirdPartyProfile 是三方凭证校验后的稳定身份结果。 type ThirdPartyProfile struct { // ProviderSubject 是三方平台内稳定身份。 diff --git a/services/user-service/internal/domain/host/host.go b/services/user-service/internal/domain/host/host.go index 1fea0ab3..c3954e8b 100644 --- a/services/user-service/internal/domain/host/host.go +++ b/services/user-service/internal/domain/host/host.go @@ -231,23 +231,24 @@ type BDProfile struct { // ManagerProfile 是经理身份事实。 // 经理是 BD Leader/Admin 的上级,和 Host/Agency/BD 链路没有父子关系。 type ManagerProfile struct { - UserID int64 - RegionID int64 - Status string - Contact string - CanGrantAvatarFrame bool - CanGrantVehicle bool - CanGrantBadge bool - CanGrantVIP bool - CanUpdateUserLevel bool - CanAddBDLeader bool - CanAddAdmin bool - CanAddSuperadmin bool - CanBlockUser bool - CanTransferCountry bool - CreatedByAdminID int64 - CreatedAtMs int64 - UpdatedAtMs int64 + UserID int64 + RegionID int64 + Status string + Contact string + CanGrantAvatarFrame bool + CanGrantVehicle bool + CanGrantBadge bool + CanGrantVIP bool + CanUpdateUserLevel bool + CanAddBDLeader bool + CanManageCrossRegion bool + CanAddAdmin bool + CanAddSuperadmin bool + CanBlockUser bool + CanTransferCountry bool + CreatedByAdminID int64 + CreatedAtMs int64 + UpdatedAtMs int64 } // CoinSellerProfile 是币商角色事实,余额不保存在 user-service。 diff --git a/services/user-service/internal/domain/host/stats.go b/services/user-service/internal/domain/host/stats.go new file mode 100644 index 00000000..a4ab84ae --- /dev/null +++ b/services/user-service/internal/domain/host/stats.go @@ -0,0 +1,25 @@ +package host + +// EngagementStats 聚合 user-service 拥有的 App 在线、有效麦时、私信发送者和粉丝净增事实。 +type EngagementStats struct { + OnlineDurationMS int64 + ValidMicDurationMS int64 + ValidMicDays int64 + PrivateMessageSenders int64 + NewFollowers int64 +} + +// EngagementStatsQuery 使用左闭右开 UTC 毫秒区间,避免跨页/跨日查询重复统计边界事件。 +type EngagementStatsQuery struct { + HostUserID int64 + StartAtMS int64 + EndAtMS int64 +} + +// PrivateMessageEvent 是腾讯 IM 已发送私信的最小可审计事实,不保存消息正文。 +type PrivateMessageEvent struct { + EventID string + SenderUserID int64 + TargetUserID int64 + OccurredAtMS int64 +} diff --git a/services/user-service/internal/domain/roomtime/roomtime.go b/services/user-service/internal/domain/roomtime/roomtime.go new file mode 100644 index 00000000..4db5ef40 --- /dev/null +++ b/services/user-service/internal/domain/roomtime/roomtime.go @@ -0,0 +1,69 @@ +// Package roomtime defines user-scoped in-room duration facts. +package roomtime + +const ( + // RoomUserJoinedEventType opens an in-room session when the durable outbox fact arrives. + RoomUserJoinedEventType = "RoomUserJoined" + // RoomUserLeftEventType closes an in-room session on voluntary or stale-swept leave. + RoomUserLeftEventType = "RoomUserLeft" + // RoomUserKickedEventType closes an in-room session; kick/system evict/close room 只发该事实,不再补 RoomUserLeft。 + RoomUserKickedEventType = "RoomUserKicked" + + // SessionStatusOpen means the user is considered inside the room by the read model. + SessionStatusOpen = "open" + // SessionStatusEnded is terminal and must never be reopened by replayed events. + SessionStatusEnded = "ended" + + // EventTypeUserRoomDailyStatsUpdated is emitted for each UTC day touched by a closed session. + EventTypeUserRoomDailyStatsUpdated = "UserRoomDailyStatsUpdated" + + // EndReasonLeft closes a session from RoomUserLeft. + EndReasonLeft = "left" + // EndReasonKicked closes a session from RoomUserKicked(含系统驱逐与关房清场). + EndReasonKicked = "kicked" + // EndReasonSupersededByJoin closes a lingering open session in another room when a newer join arrives. + EndReasonSupersededByJoin = "superseded_by_join" + // EndReasonSupersededByRejoin closes a lingering open session in the same room when a newer join arrives. + EndReasonSupersededByRejoin = "superseded_by_rejoin" + // EndReasonOrphanLeft closes a session synthesized from a leave fact that arrived before its join fact. + EndReasonOrphanLeft = "orphan_left" + + // CompensationReasonOpenSessionMaxWindow closes open sessions at a configured safety ceiling. + CompensationReasonOpenSessionMaxWindow = "compensated_open_session_max_window" +) + +// RoomPresenceEvent is the decoded room presence fact used by user-service in-room duration aggregation. +type RoomPresenceEvent struct { + AppCode string + EventID string + EventType string + RoomID string + RoomVersion int64 + OccurredAtMs int64 + // UserID is the presence subject: RoomUserJoined/RoomUserLeft 的 user_id,RoomUserKicked 的 target_user_id。 + UserID int64 + // IsRobot 只在 RoomUserJoined 上有意义,会话开启时固化,机器人会话不产生统计事实。 + IsRobot bool +} + +// ApplyResult describes one idempotent event application. +type ApplyResult struct { + Consumed bool + Skipped bool + Opened bool + Closed bool + Reason string +} + +// CompensationOptions bounds abnormal open sessions so missing leave events cannot accumulate forever. +type CompensationOptions struct { + NowMs int64 + BatchSize int + OpenSessionMaxAgeMs int64 +} + +// CompensationResult summarizes one compensation scan. +type CompensationResult struct { + ClosedCount int + OrphanLeavesDeleted int +} diff --git a/services/user-service/internal/domain/user/admin.go b/services/user-service/internal/domain/user/admin.go new file mode 100644 index 00000000..b5714133 --- /dev/null +++ b/services/user-service/internal/domain/user/admin.go @@ -0,0 +1,56 @@ +package user + +const ( + // AdminRoleHost 表示当前有效主播身份。 + AdminRoleHost = "host" + // AdminRoleAgency 表示当前有效公会 owner 身份,普通公会成员不会获得该角色。 + AdminRoleAgency = "agency" + // AdminRoleBD 表示当前有效普通 BD 身份。 + AdminRoleBD = "bd" + // AdminRoleBDLeader 表示当前有效 BD Leader 身份;它可以和普通 BD 身份同时存在。 + AdminRoleBDLeader = "bd_leader" + // AdminRoleCoinSeller 表示当前有效币商身份。 + AdminRoleCoinSeller = "coin_seller" + // AdminRoleManager 表示当前有效经理身份。 + AdminRoleManager = "manager" +) + +const ( + // BanSourceAdmin 表示有效封禁来自后台管理员事实表。 + BanSourceAdmin = "admin" + // BanSourceManager 表示有效封禁来自经理中心事实表。 + BanSourceManager = "manager" + // BanSourceLegacy 表示历史 banned/disabled 状态没有可管理的封禁事实,必须按永久处理。 + BanSourceLegacy = "legacy" +) + +// ActiveBanSummary 是后台列表读取的合并封禁视图。 +// 多个来源重叠时,Permanent 只要命中任一永久事实即为 true;否则 ExpiresAtMs 取最晚到期时间。 +type ActiveBanSummary struct { + Active bool + Source string + BanID string + Permanent bool + ExpiresAtMs int64 + UserStatus Status + OperatorType string + OperatorUserID int64 + Reason string + CreatedAtMs int64 + ActiveBanCount int32 +} + +// AdminProfile 是 user-service 为后台用户列表提供的 owner 事实投影。 +// 钱包余额、等级等其他 owner 的字段由 admin-server 分别批量聚合,不能塞进该查询回查跨库数据。 +type AdminProfile struct { + User User + BirthDate string + RegisterDevice string + LastSuccessLoginAtMs int64 + Roles []string + Ban ActiveBanSummary + LastOperatorType string + LastOperatorUserID int64 + LastOperationReason string + LastOperationAtMs int64 +} diff --git a/services/user-service/internal/domain/user/user.go b/services/user-service/internal/domain/user/user.go index b4a5255a..f47e4cb0 100644 --- a/services/user-service/internal/domain/user/user.go +++ b/services/user-service/internal/domain/user/user.go @@ -934,7 +934,7 @@ func ValidResolvableDisplayUserID(value string) bool { // ValidAdminPrettyDisplayUserID 校验后台直接发放的靓号内容。 func ValidAdminPrettyDisplayUserID(value string) bool { - // 后台发放支持阿拉伯语、英文和数字;禁止空白、控制字符、emoji 与标点,避免登录和展示链路出现不可输入字符。 + // 靓号是运营展示文本而非登录凭据,必须接受 Unicode 字体、符号和 emoji;仍拒绝空值、首尾空白及控制字符,避免换行等不可见控制码污染列表、日志和协议载荷。 if value == "" || strings.TrimSpace(value) != value { return false } @@ -942,10 +942,9 @@ func ValidAdminPrettyDisplayUserID(value string) bool { return false } for _, r := range value { - if unicode.IsLetter(r) || unicode.IsDigit(r) || unicode.IsMark(r) { - continue + if unicode.IsControl(r) { + return false } - return false } return true } diff --git a/services/user-service/internal/domain/user/user_test.go b/services/user-service/internal/domain/user/user_test.go index cd238451..cd418902 100644 --- a/services/user-service/internal/domain/user/user_test.go +++ b/services/user-service/internal/domain/user/user_test.go @@ -23,14 +23,14 @@ func TestValidDisplayUserIDAllowsFiveDigitSeedAccounts(t *testing.T) { func TestValidResolvableDisplayUserIDAllowsDefaultAndPrettyIDs(t *testing.T) { t.Parallel() - valid := []string{"10001", "123456", "1111cc1111", "VIP2026", "ملك123", "Cafe\u0301123"} + valid := []string{"10001", "123456", "1111cc1111", "VIP2026", "ملك123", "Cafe\u0301123", "𝕸⃝é中⚡️™"} for _, value := range valid { if !ValidResolvableDisplayUserID(value) { t.Fatalf("resolvable display_user_id %q should be valid", value) } } - invalid := []string{"", " ", "VIP-2026", "VIP 2026", "VIP😀", "\nVIP", "VIP!"} + invalid := []string{"", " ", " VIP", "VIP ", "\nVIP", "VIP\x00"} for _, value := range invalid { if ValidResolvableDisplayUserID(value) { t.Fatalf("resolvable display_user_id %q should be invalid", value) @@ -41,14 +41,15 @@ func TestValidResolvableDisplayUserIDAllowsDefaultAndPrettyIDs(t *testing.T) { func TestValidAdminPrettyDisplayUserID(t *testing.T) { t.Parallel() - valid := []string{"VIP2026", "ملك123", "Cafe\u0301123"} + // Unicode 字体可能含数学字母、组合标记、符号及 emoji;后台靓号必须完整保留这些展示字符。 + valid := []string{"VIP2026", "ملك123", "Cafe\u0301123", "𝕸⃝é中⚡️™", "VIP-2026", "VIP 2026", "VIP😀", "VIP!"} for _, value := range valid { if !ValidAdminPrettyDisplayUserID(value) { t.Fatalf("admin pretty display_user_id %q should be valid", value) } } - invalid := []string{"", " ", "VIP-2026", "VIP 2026", "VIP😀", "\nVIP", "VIP!"} + invalid := []string{"", " ", " VIP", "VIP ", "\nVIP", "VIP\x00"} for _, value := range invalid { if ValidAdminPrettyDisplayUserID(value) { t.Fatalf("admin pretty display_user_id %q should be invalid", value) diff --git a/services/user-service/internal/service/auth/audit.go b/services/user-service/internal/service/auth/audit.go index b9562034..6e56b16a 100644 --- a/services/user-service/internal/service/auth/audit.go +++ b/services/user-service/internal/service/auth/audit.go @@ -3,9 +3,11 @@ package auth import ( "context" + "log/slog" "strings" "hyapp/pkg/appcode" + "hyapp/pkg/logx" "hyapp/pkg/xerr" authdomain "hyapp/services/user-service/internal/domain/auth" ) @@ -24,10 +26,22 @@ type Meta struct { CountryByIP string // Platform 是客户端平台快照,认证链路只做审计和风险任务透传。 Platform string + // AppVersion 是本次登录客户端版本;只记录本次请求,不能沿用用户注册时的历史版本。 + AppVersion string + // BuildNumber 是本次登录客户端构建号;与 AppVersion 一起用于精确定位客户端包。 + BuildNumber string // Language 是客户端语言偏好,gateway 只用作弱信号快拦。 Language string // Timezone 是客户端 IANA 时区,gateway 只用作弱信号快拦。 Timezone string + // DeviceModel 是客户端上报的设备型号快照(例如 Redmi Note 8 Pro);只进设备档案,不参与认证判定。 + DeviceModel string + // DeviceManufacturer 是设备厂商快照(例如 Xiaomi/Apple)。 + DeviceManufacturer string + // OSVersion 是客户端系统版本快照(例如 Android 13 的 "13")。 + OSVersion string + // IMEI 是客户端可获取时上报的设备 IMEI;Android 10+ 无特权权限拿不到,预期常为空。 + IMEI string } func (s *Service) audit(ctx context.Context, meta Meta, userID int64, loginType string, provider string, result string, failureCode string) { @@ -39,6 +53,8 @@ func (s *Service) audit(ctx context.Context, meta Meta, userID int64, loginType Provider: provider, Channel: auditChannel(loginType, provider), Platform: normalizePlatform(meta.Platform), + AppVersion: normalizeAuditClientVersion(meta.AppVersion), + BuildNumber: normalizeAuditClientVersion(meta.BuildNumber), Result: result, FailureCode: failureCode, ClientIP: meta.ClientIP, @@ -54,8 +70,17 @@ func (s *Service) recordAudit(ctx context.Context, audit authdomain.LoginAudit) return } - // 审计失败不影响登录主链路,所以这里有意忽略错误。 - _ = s.authRepository.RecordLoginAudit(ctx, audit) + // 审计失败不影响登录主链路,但必须暴露日志;否则漏执行迁移或数据库瞬断会让版本画像静默停留在旧值。 + if err := s.authRepository.RecordLoginAudit(ctx, audit); err != nil { + logx.Error( + logx.With(ctx, slog.String("request_id", audit.RequestID), slog.String("app_code", audit.AppCode)), + "login_audit_write_failed", + err, + slog.String("component", "user_auth"), + slog.String("login_type", audit.LoginType), + slog.Int64("user_id", audit.UserID), + ) + } } // RecordLoginBlocked 写入 gateway fast guard 拒绝的登录审计;失败不应影响 gateway 返回阻断响应。 @@ -71,6 +96,8 @@ func (s *Service) RecordLoginBlocked(ctx context.Context, meta Meta, channel str Provider: blockedProvider(channel), Channel: normalizeChannel(channel), Platform: normalizePlatform(firstNonEmpty(platform, meta.Platform)), + AppVersion: normalizeAuditClientVersion(meta.AppVersion), + BuildNumber: normalizeAuditClientVersion(meta.BuildNumber), Result: "blocked", FailureCode: string(xerr.AuthLoginBlocked), ClientIP: meta.ClientIP, @@ -86,6 +113,17 @@ func (s *Service) RecordLoginBlocked(ctx context.Context, meta Meta, channel str return s.authRepository.RecordLoginAudit(ctx, audit) == nil } +func normalizeAuditClientVersion(value string) string { + value = strings.TrimSpace(value) + runes := []rune(value) + if len(runes) <= 64 { + return value + } + // 客户端 header 不受 proto 长度约束。审计元数据超长时截断而不是让 INSERT 失败, + // 否则登录虽成功却丢失整条审计,Dashboard 也无法确定该用户最近一次成功登录。 + return string(runes[:64]) +} + func auditChannel(loginType string, provider string) string { if loginType == loginPassword { return "account" diff --git a/services/user-service/internal/service/auth/password.go b/services/user-service/internal/service/auth/password.go index c179c23b..ce0c95b3 100644 --- a/services/user-service/internal/service/auth/password.go +++ b/services/user-service/internal/service/auth/password.go @@ -67,6 +67,8 @@ func (s *Service) LoginPassword(ctx context.Context, displayUserID string, passw } s.audit(ctx, meta, user.UserID, loginPassword, "", resultSuccess, "") + // 登录成功即刷新设备档案;写入是 best-effort,不影响 token 返回。 + s.recordUserDevice(ctx, meta, user.UserID, deviceID) token, err := s.issueToken(user, session.SessionID, refreshToken, session.ExpiresAtMs) if err != nil { return authdomain.Token{}, err diff --git a/services/user-service/internal/service/auth/quick_account.go b/services/user-service/internal/service/auth/quick_account.go index 4cc0d30f..4d60025b 100644 --- a/services/user-service/internal/service/auth/quick_account.go +++ b/services/user-service/internal/service/auth/quick_account.go @@ -18,6 +18,8 @@ func (s *Service) QuickCreateAccount(ctx context.Context, password string, regis registration.AppCode = meta.AppCode registration = normalizeThirdPartyRegistration(registration, meta) meta.Platform = registration.Platform + meta.AppVersion = registration.AppVersion + meta.BuildNumber = registration.BuildNumber meta.Language = registration.Language meta.Timezone = registration.Timezone password = strings.TrimSpace(password) @@ -119,6 +121,8 @@ func (s *Service) QuickCreateAccount(ctx context.Context, password string, regis }, nil } s.audit(ctx, meta, user.UserID, loginPassword, "", resultSuccess, "") + // 快捷账号也是可登录主体,设备档案与普通注册保持一致;机器人分支在上方已提前返回。 + s.recordUserDevice(ctx, deviceMetaWithRegistration(meta, registration), user.UserID, registration.DeviceID) token, tokenErr := s.issueToken(user, session.SessionID, refreshToken, session.ExpiresAtMs) if tokenErr != nil { return authdomain.Token{}, tokenErr diff --git a/services/user-service/internal/service/auth/service.go b/services/user-service/internal/service/auth/service.go index 019b78da..d7480dbb 100644 --- a/services/user-service/internal/service/auth/service.go +++ b/services/user-service/internal/service/auth/service.go @@ -54,6 +54,8 @@ type AuthRepository interface { FindActiveSessionByRefreshHash(ctx context.Context, refreshTokenHash string, nowMs int64) (authdomain.Session, error) // FindActiveSessionByID 通过 session_id 查找未过期未吊销 session,用于只重签 access token 的链路。 FindActiveSessionByID(ctx context.Context, sessionID string, nowMs int64) (authdomain.Session, error) + // FindLatestActiveSessionByUser 返回用户最新未过期未吊销 session,供后台重签 access token;无会话返回 NotFound。 + FindLatestActiveSessionByUser(ctx context.Context, userID int64, nowMs int64) (authdomain.Session, error) // ReplaceSession 原子吊销旧 session 并创建新 session。 ReplaceSession(ctx context.Context, oldSessionID string, newSession authdomain.Session, revokedAtMs int64) error // RevokeSession 按 session_id 或 refresh token hash 吊销 session。 @@ -80,6 +82,8 @@ type AuthRepository interface { UpsertRegisterRiskConfig(ctx context.Context, config authdomain.RegisterRiskConfig) (authdomain.RegisterRiskConfig, error) // RecordLoginAudit 记录登录审计,主链路不会因为审计失败而失败。 RecordLoginAudit(ctx context.Context, audit authdomain.LoginAudit) error + // UpsertUserDevice 以“非空覆盖”语义合并设备档案;认证主链路不会因为它失败而失败。 + UpsertUserDevice(ctx context.Context, device authdomain.UserDevice) error // CreateLoginIPRiskJob 创建登录后异步 IP 风控任务,失败只影响复核能力。 CreateLoginIPRiskJob(ctx context.Context, job authdomain.LoginIPRiskJob) error // ClaimLoginIPRiskJobs 按 DB 租约认领待处理风控任务。 diff --git a/services/user-service/internal/service/auth/service_test.go b/services/user-service/internal/service/auth/service_test.go index 255555f4..bd7cca34 100644 --- a/services/user-service/internal/service/auth/service_test.go +++ b/services/user-service/internal/service/auth/service_test.go @@ -348,7 +348,11 @@ func TestThirdPartyUserSetsPasswordThenLogsIn(t *testing.T) { t.Fatalf("expected PASSWORD_ALREADY_SET, got %v", err) } - loginToken, err := svc.LoginPassword(ctx, displayUserID, "secret-pass", "ios", authservice.Meta{RequestID: "req-login"}) + loginToken, err := svc.LoginPassword(ctx, displayUserID, "secret-pass", "ios", authservice.Meta{ + RequestID: "req-login", + AppVersion: " 2.4.0 ", + BuildNumber: "24001", + }) if err != nil { t.Fatalf("LoginPassword failed: %v", err) } @@ -361,6 +365,19 @@ func TestThirdPartyUserSetsPasswordThenLogsIn(t *testing.T) { if got, want := accessTokenExpUnix(t, loginToken.AccessToken), now.Unix()+testAccessTokenTTLSec; got != want { t.Fatalf("password login access token exp mismatch: got=%d want=%d", got, want) } + var auditedAppVersion string + var auditedBuildNumber string + if err := repository.RawDB().QueryRowContext(ctx, ` + SELECT app_version, build_number + FROM login_audit + WHERE app_code = 'lalu' AND request_id = 'req-login' AND user_id = ? + AND login_type = 'password' AND result = 'success' AND blocked = 0 + `, loginToken.UserID).Scan(&auditedAppVersion, &auditedBuildNumber); err != nil { + t.Fatalf("query password login client version audit failed: %v", err) + } + if auditedAppVersion != "2.4.0" || auditedBuildNumber != "24001" { + t.Fatalf("password login client version audit mismatch: app_version=%q build_number=%q", auditedAppVersion, auditedBuildNumber) + } if _, err := svc.LoginPassword(ctx, displayUserID, "wrong", "ios", authservice.Meta{}); !xerr.IsCode(err, xerr.AuthFailed) { t.Fatalf("expected AUTH_FAILED for wrong password, got %v", err) @@ -368,7 +385,11 @@ func TestThirdPartyUserSetsPasswordThenLogsIn(t *testing.T) { now = now.Add(time.Second) // refresh 成功后必须返回新的 refresh token,并吊销旧 token。 - refreshToken, err := svc.RefreshToken(ctx, loginToken.RefreshToken, "ios", authservice.Meta{RequestID: "req-refresh"}) + refreshToken, err := svc.RefreshToken(ctx, loginToken.RefreshToken, "ios", authservice.Meta{ + RequestID: "req-refresh", + AppVersion: "2.5.0", + BuildNumber: "25001", + }) if err != nil { t.Fatalf("RefreshToken failed: %v", err) } @@ -387,6 +408,17 @@ func TestThirdPartyUserSetsPasswordThenLogsIn(t *testing.T) { if got, want := cache.revTTL["lalu:"+loginToken.SessionID], time.Duration(testAccessTokenTTLSec+60)*time.Second; got != want { t.Fatalf("refresh denylist TTL mismatch: got=%s want=%s", got, want) } + if err := repository.RawDB().QueryRowContext(ctx, ` + SELECT app_version, build_number + FROM login_audit + WHERE app_code = 'lalu' AND request_id = 'req-refresh' AND user_id = ? + AND login_type = 'refresh' AND result = 'success' AND blocked = 0 + `, loginToken.UserID).Scan(&auditedAppVersion, &auditedBuildNumber); err != nil { + t.Fatalf("query refresh client version audit failed: %v", err) + } + if auditedAppVersion != "2.5.0" || auditedBuildNumber != "25001" { + t.Fatalf("refresh client version audit mismatch: app_version=%q build_number=%q", auditedAppVersion, auditedBuildNumber) + } if _, err := svc.RefreshToken(ctx, loginToken.RefreshToken, "ios", authservice.Meta{}); !xerr.IsCode(err, xerr.SessionRevoked) { t.Fatalf("expected old refresh token revoked, got %v", err) diff --git a/services/user-service/internal/service/auth/third_party.go b/services/user-service/internal/service/auth/third_party.go index f6902c07..1651eabd 100644 --- a/services/user-service/internal/service/auth/third_party.go +++ b/services/user-service/internal/service/auth/third_party.go @@ -48,6 +48,8 @@ func (s *Service) LoginThirdParty(ctx context.Context, provider string, credenti credential = strings.TrimSpace(credential) registration = normalizeThirdPartyRegistration(registration, meta) meta.Platform = registration.Platform + meta.AppVersion = registration.AppVersion + meta.BuildNumber = registration.BuildNumber meta.Language = registration.Language meta.Timezone = registration.Timezone if provider == "" || credential == "" { @@ -104,6 +106,8 @@ func (s *Service) RegisterThirdParty(ctx context.Context, provider string, crede credential = strings.TrimSpace(credential) registration = normalizeThirdPartyRegistration(registration, meta) meta.Platform = registration.Platform + meta.AppVersion = registration.AppVersion + meta.BuildNumber = registration.BuildNumber meta.Language = registration.Language meta.Timezone = registration.Timezone if provider == "" || credential == "" { @@ -241,6 +245,8 @@ func (s *Service) loginExistingThirdParty(ctx context.Context, identity authdoma } s.audit(ctx, meta, user.UserID, loginThird, identity.Provider, resultSuccess, "") + // 老用户换机三方登录也要立即出现在设备 tab;header 缺失时回退 body 设备字段。 + s.recordUserDevice(ctx, deviceMetaWithRegistration(meta, registration), user.UserID, registration.DeviceID) // isNewUser=false 表示本次只创建 session,没有创建用户。 token, err := s.issueToken(user, session.SessionID, refreshToken, session.ExpiresAtMs) if err != nil { @@ -292,6 +298,8 @@ func (s *Service) registerExistingThirdParty(ctx context.Context, identity authd } s.audit(ctx, meta, updated.UserID, loginThird, identity.Provider, resultSuccess, "") + // 补齐资料完成注册同样属于认证成功,设备档案与 session 创建保持同步。 + s.recordUserDevice(ctx, deviceMetaWithRegistration(meta, registration), updated.UserID, registration.DeviceID) token, err := s.issueToken(updated, session.SessionID, refreshToken, session.ExpiresAtMs) if err != nil { return authdomain.Token{}, false, err @@ -350,6 +358,8 @@ func (s *Service) createThirdPartyUser(ctx context.Context, provider string, pro if err == nil { // repository 事务成功后,用户、默认短号、三方绑定和首个 session 同时存在。 s.audit(ctx, meta, user.UserID, loginThird, provider, resultSuccess, "") + // 新注册用户的首个设备档案与注册事务同请求写入,后台无需等下次登录。 + s.recordUserDevice(ctx, deviceMetaWithRegistration(meta, registration), user.UserID, registration.DeviceID) token, tokenErr := s.issueToken(user, session.SessionID, refreshToken, session.ExpiresAtMs) if tokenErr != nil { return authdomain.Token{}, false, tokenErr @@ -432,6 +442,8 @@ func (s *Service) createRegisteredThirdPartyUser(ctx context.Context, provider s err = s.authRepository.CreateThirdPartyUser(ctx, user, displayIdentity, thirdParty, session, inviteBind, maxAccountsPerDevice) if err == nil { s.audit(ctx, meta, user.UserID, loginThird, provider, resultSuccess, "") + // 注册专用入口创建的 completed 用户同样落首个设备档案。 + s.recordUserDevice(ctx, deviceMetaWithRegistration(meta, registration), user.UserID, registration.DeviceID) token, tokenErr := s.issueToken(user, session.SessionID, refreshToken, session.ExpiresAtMs) if tokenErr != nil { return authdomain.Token{}, false, tokenErr diff --git a/services/user-service/internal/service/auth/token.go b/services/user-service/internal/service/auth/token.go index e0c52cd2..33b4e93d 100644 --- a/services/user-service/internal/service/auth/token.go +++ b/services/user-service/internal/service/auth/token.go @@ -80,6 +80,8 @@ func (s *Service) RefreshToken(ctx context.Context, refreshToken string, deviceI } s.audit(ctx, meta, session.UserID, loginRefresh, "", resultSuccess, "") + // refresh 是登录态存续的主路径;老设备升级新客户端后,设备档案主要在这里补齐。 + s.recordUserDevice(ctx, meta, session.UserID, deviceID) return s.issueToken(user, newSession.SessionID, newRefreshToken, newSession.ExpiresAtMs) } @@ -125,6 +127,45 @@ func (s *Service) IssueAccessTokenForSession(ctx context.Context, userID int64, return s.issueToken(user, session.SessionID, "", session.ExpiresAtMs) } +// AdminIssueUserAccessToken 供后台按用户当前最新 active session 重签 access token。 +// 与 IssueAccessTokenForSession 的关键差异:这里不写 login_audit,也不更新 session 行。 +// 后台取 token 不是用户行为,写审计会污染“最近活跃 = GREATEST(session.updated, audit.created)”口径; +// 取用责任由 admin 侧操作日志承担。签发本身复用 issueToken 的 claim 组装,不复制 JWT 逻辑。 +func (s *Service) AdminIssueUserAccessToken(ctx context.Context, userID int64, meta Meta) (authdomain.Token, authdomain.Session, error) { + if userID <= 0 { + return authdomain.Token{}, authdomain.Session{}, xerr.New(xerr.InvalidArgument, "user_id is required") + } + if s.authRepository == nil { + return authdomain.Token{}, authdomain.Session{}, xerr.New(xerr.Unavailable, "auth repository is not configured") + } + if !s.TokenConfigValid() { + // 签发配置残缺时 fail closed,不能签出 gateway 无法验证的 token。 + return authdomain.Token{}, authdomain.Session{}, xerr.New(xerr.Unavailable, "token config is invalid") + } + + nowMs := s.now().UnixMilli() + // 没有可信 sid 输入,取最新 active session 近似客户端当前会话;无会话时透传 NotFound 给后台展示。 + session, err := s.authRepository.FindLatestActiveSessionByUser(ctx, userID, nowMs) + if err != nil { + return authdomain.Token{}, authdomain.Session{}, err + } + user, err := s.freshUser(ctx, userID, nowMs, meta.RequestID) + if err != nil { + return authdomain.Token{}, authdomain.Session{}, err + } + if !user.CanLogin() { + // 禁用用户不能经由后台间接拿到有效 token,语义与所有签发路径一致。 + return authdomain.Token{}, authdomain.Session{}, xerr.New(xerr.UserDisabled, "user is disabled") + } + + // refreshToken 传空:后台没有轮换会话的理由,响应中也绝不携带 refresh 原文。 + token, err := s.issueToken(user, session.SessionID, "", session.ExpiresAtMs) + if err != nil { + return authdomain.Token{}, authdomain.Session{}, err + } + return token, session, nil +} + // Logout 失效指定 session 或 refresh token。 func (s *Service) Logout(ctx context.Context, sessionID string, refreshToken string, meta Meta) (bool, error) { if strings.TrimSpace(sessionID) == "" && strings.TrimSpace(refreshToken) == "" { diff --git a/services/user-service/internal/service/auth/user_device.go b/services/user-service/internal/service/auth/user_device.go new file mode 100644 index 00000000..f130ca7c --- /dev/null +++ b/services/user-service/internal/service/auth/user_device.go @@ -0,0 +1,73 @@ +package auth + +import ( + "context" + "log/slog" + "strings" + + "hyapp/pkg/appcode" + "hyapp/pkg/logx" + authdomain "hyapp/services/user-service/internal/domain/auth" +) + +// recordUserDevice 在认证成功后合并当前设备档案。 +// 该写入是 best-effort:device_id 为空(老客户端/后台调用)直接跳过;写失败只记日志, +// 绝不让已经成功的登录/注册/refresh 因为设备画像落库失败而回吐错误。 +func (s *Service) recordUserDevice(ctx context.Context, meta Meta, userID int64, deviceID string) { + deviceID = strings.TrimSpace(deviceID) + if s.authRepository == nil || userID <= 0 || deviceID == "" { + return + } + + nowMs := s.now().UnixMilli() + device := authdomain.UserDevice{ + AppCode: appcode.Normalize(meta.AppCode), + UserID: userID, + DeviceID: truncateDeviceField(deviceID, 128), + Platform: truncateDeviceField(normalizePlatform(meta.Platform), 32), + DeviceModel: truncateDeviceField(meta.DeviceModel, 128), + Manufacturer: truncateDeviceField(meta.DeviceManufacturer, 64), + OSVersion: truncateDeviceField(meta.OSVersion, 64), + IMEI: truncateDeviceField(meta.IMEI, 64), + AppVersion: normalizeAuditClientVersion(meta.AppVersion), + BuildNumber: normalizeAuditClientVersion(meta.BuildNumber), + // first_seen 只在 INSERT 生效;ON DUPLICATE KEY 更新时只有 last_seen/updated 前移。 + FirstSeenAtMs: nowMs, + LastSeenAtMs: nowMs, + CreatedAtMs: nowMs, + UpdatedAtMs: nowMs, + } + if err := s.authRepository.UpsertUserDevice(ctx, device); err != nil { + // 失败必须可见:设备 tab 静默缺数据比登录多一条错误日志更难排查。 + logx.Error( + logx.With(ctx, slog.String("request_id", meta.RequestID), slog.String("app_code", device.AppCode)), + "user_device_upsert_failed", + err, + slog.String("component", "user_auth"), + slog.Int64("user_id", userID), + slog.String("device_id", device.DeviceID), + ) + } +} + +// deviceMetaWithRegistration 用注册 body 的设备字段补齐 Meta 缺口。 +// 设备资料有两个来源:gateway 设备头(新客户端全局注入)和注册 body(旧协议字段); +// 任意一侧可能单独缺失,落档案前统一取“先 header 后 body”的非空值。 +func deviceMetaWithRegistration(meta Meta, registration authdomain.ThirdPartyRegistration) Meta { + meta.Platform = firstNonEmpty(meta.Platform, registration.Platform) + meta.DeviceModel = firstNonEmpty(meta.DeviceModel, registration.Device) + meta.OSVersion = firstNonEmpty(meta.OSVersion, registration.OSVersion) + meta.AppVersion = firstNonEmpty(meta.AppVersion, registration.AppVersion) + meta.BuildNumber = firstNonEmpty(meta.BuildNumber, registration.BuildNumber) + return meta +} + +// truncateDeviceField 按列宽截断客户端可控字符串,超长上报不能让整条设备档案 INSERT 失败。 +func truncateDeviceField(value string, maxRunes int) string { + value = strings.TrimSpace(value) + runes := []rune(value) + if len(runes) <= maxRunes { + return value + } + return string(runes[:maxRunes]) +} diff --git a/services/user-service/internal/service/auth/user_device_admin_token_test.go b/services/user-service/internal/service/auth/user_device_admin_token_test.go new file mode 100644 index 00000000..6a5c0928 --- /dev/null +++ b/services/user-service/internal/service/auth/user_device_admin_token_test.go @@ -0,0 +1,246 @@ +// user_device_admin_token_test 验证认证成功后的设备档案聚合和后台按用户重签 access token 的语义。 +package auth_test + +import ( + "context" + "testing" + "time" + + "hyapp/pkg/xerr" + authservice "hyapp/services/user-service/internal/service/auth" + "hyapp/services/user-service/internal/testutil/mysqltest" +) + +type userDeviceRow struct { + Platform string + DeviceModel string + Manufacturer string + OSVersion string + IMEI string + AppVersion string + BuildNumber string + FirstSeenAtMs int64 + LastSeenAtMs int64 +} + +func queryUserDevice(t *testing.T, repository *mysqltest.Repository, userID int64, deviceID string) userDeviceRow { + t.Helper() + var row userDeviceRow + if err := repository.RawDB().QueryRowContext(context.Background(), ` + SELECT platform, device_model, manufacturer, os_version, imei, app_version, build_number, first_seen_at_ms, last_seen_at_ms + FROM user_devices + WHERE app_code = 'lalu' AND user_id = ? AND device_id = ? + `, userID, deviceID).Scan(&row.Platform, &row.DeviceModel, &row.Manufacturer, &row.OSVersion, &row.IMEI, &row.AppVersion, &row.BuildNumber, &row.FirstSeenAtMs, &row.LastSeenAtMs); err != nil { + t.Fatalf("query user_devices failed: %v", err) + } + return row +} + +func countUserDevices(t *testing.T, repository *mysqltest.Repository, userID int64) int { + t.Helper() + var count int + if err := repository.RawDB().QueryRowContext(context.Background(), ` + SELECT COUNT(*) FROM user_devices WHERE app_code = 'lalu' AND user_id = ? + `, userID).Scan(&count); err != nil { + t.Fatalf("count user_devices failed: %v", err) + } + return count +} + +func countLoginAudit(t *testing.T, repository *mysqltest.Repository, userID int64) int { + t.Helper() + var count int + if err := repository.RawDB().QueryRowContext(context.Background(), ` + SELECT COUNT(*) FROM login_audit WHERE app_code = 'lalu' AND user_id = ? + `, userID).Scan(&count); err != nil { + t.Fatalf("count login_audit failed: %v", err) + } + return count +} + +func TestThirdPartyLoginUpsertsUserDeviceWithNonEmptyWins(t *testing.T) { + // 首次注册写全量设备资料;后续登录只带部分字段时,空值不能抹掉已上报的型号/厂商,last_seen 必须前移。 + ctx := context.Background() + repository := mysqltest.NewRepository(t) + seedCountry(t, repository, "SG") + now := time.UnixMilli(1000) + authSvc := newAuthService(repository, &now, []int64{910001, 910002}, []string{"7100001", "7100002"}) + + // 与真实链路一致:gateway 把版本头同时写入 Meta 和注册字段(LoginThirdParty 会用注册字段覆盖 Meta)。 + registration := thirdPartyRegistration("android") + registration.AppVersion = "1.2.0" + registration.BuildNumber = "120" + token, _, err := authSvc.LoginThirdParty(ctx, "wechat", "openid-device-upsert", registration, authservice.Meta{ + AppCode: "lalu", + RequestID: "req-device-register", + Platform: "android", + AppVersion: "1.2.0", + BuildNumber: "120", + DeviceModel: "Redmi Note 8 Pro", + DeviceManufacturer: "Xiaomi", + OSVersion: "13", + }) + if err != nil { + t.Fatalf("LoginThirdParty failed: %v", err) + } + + row := queryUserDevice(t, repository, token.UserID, registration.DeviceID) + if row.DeviceModel != "Redmi Note 8 Pro" || row.Manufacturer != "Xiaomi" || row.OSVersion != "13" || row.Platform != "android" { + t.Fatalf("registered device profile mismatch: %+v", row) + } + if row.AppVersion != "1.2.0" || row.BuildNumber != "120" || row.IMEI != "" { + t.Fatalf("registered device version snapshot mismatch: %+v", row) + } + if row.FirstSeenAtMs != 1000 || row.LastSeenAtMs != 1000 { + t.Fatalf("registered device seen timestamps mismatch: %+v", row) + } + + // 二次登录只携带新版本号,设备头缺失:型号/厂商/系统版本保持旧值,版本与 last_seen 前移,first_seen 不变。 + now = time.UnixMilli(9000) + reloginRegistration := registration + reloginRegistration.AppVersion = "1.3.0" + reloginRegistration.BuildNumber = "130" + if _, _, err := authSvc.LoginThirdParty(ctx, "wechat", "openid-device-upsert", reloginRegistration, authservice.Meta{ + AppCode: "lalu", + RequestID: "req-device-relogin", + Platform: "android", + AppVersion: "1.3.0", + BuildNumber: "130", + }); err != nil { + t.Fatalf("second LoginThirdParty failed: %v", err) + } + + row = queryUserDevice(t, repository, token.UserID, registration.DeviceID) + if row.DeviceModel != "Redmi Note 8 Pro" || row.Manufacturer != "Xiaomi" || row.OSVersion != "13" { + t.Fatalf("non-empty wins violated: %+v", row) + } + if row.AppVersion != "1.3.0" || row.BuildNumber != "130" { + t.Fatalf("app version must follow latest login: %+v", row) + } + if row.FirstSeenAtMs != 1000 || row.LastSeenAtMs != 9000 { + t.Fatalf("seen timestamps mismatch after relogin: %+v", row) + } + if count := countUserDevices(t, repository, token.UserID); count != 1 { + t.Fatalf("same device must stay one row, got %d", count) + } +} + +func TestRefreshTokenUpsertsUserDeviceFromMeta(t *testing.T) { + // refresh 是登录态存续主路径:升级新客户端后设备头应通过 refresh 补齐设备档案。 + ctx := context.Background() + repository := mysqltest.NewRepository(t) + seedCountry(t, repository, "SG") + now := time.UnixMilli(1000) + authSvc := newAuthService(repository, &now, []int64{910003}, []string{"7100003"}) + + registration := thirdPartyRegistration("ios") + token, _, err := authSvc.LoginThirdParty(ctx, "wechat", "openid-device-refresh", registration, authservice.Meta{AppCode: "lalu", RequestID: "req-login"}) + if err != nil { + t.Fatalf("LoginThirdParty failed: %v", err) + } + + now = time.UnixMilli(5000) + if _, err := authSvc.RefreshToken(ctx, token.RefreshToken, registration.DeviceID, authservice.Meta{ + AppCode: "lalu", + RequestID: "req-refresh", + Platform: "ios", + AppVersion: "2.0.0", + DeviceModel: "iPhone17,2", + DeviceManufacturer: "Apple", + OSVersion: "18.1", + }); err != nil { + t.Fatalf("RefreshToken failed: %v", err) + } + + row := queryUserDevice(t, repository, token.UserID, registration.DeviceID) + if row.DeviceModel != "iPhone17,2" || row.Manufacturer != "Apple" || row.OSVersion != "18.1" || row.AppVersion != "2.0.0" { + t.Fatalf("refresh must backfill device profile: %+v", row) + } + if row.FirstSeenAtMs != 1000 || row.LastSeenAtMs != 5000 { + t.Fatalf("refresh seen timestamps mismatch: %+v", row) + } +} + +func TestAdminIssueUserAccessTokenUsesNewestSessionWithoutAuditOrSessionMutation(t *testing.T) { + // 后台重签必须选最新 active session、不写 login_audit、不更新 session 行—— + // 否则会污染“最近活跃/最近登录”口径,让运营误判用户在线。 + ctx := context.Background() + repository := mysqltest.NewRepository(t) + seedCountry(t, repository, "SG") + now := time.UnixMilli(1000) + authSvc := newAuthService(repository, &now, []int64{910004}, []string{"7100004"}) + + registration := thirdPartyRegistration("android") + first, _, err := authSvc.LoginThirdParty(ctx, "wechat", "openid-admin-token", registration, authservice.Meta{AppCode: "lalu", RequestID: "req-login-1"}) + if err != nil { + t.Fatalf("first LoginThirdParty failed: %v", err) + } + // 同一用户第二台设备再登录,形成两个 active session;最新 created_at_ms 的必须胜出。 + now = time.UnixMilli(2000) + secondRegistration := registration + secondRegistration.DeviceID = "dev-android-second" + second, _, err := authSvc.LoginThirdParty(ctx, "wechat", "openid-admin-token", secondRegistration, authservice.Meta{AppCode: "lalu", RequestID: "req-login-2"}) + if err != nil { + t.Fatalf("second LoginThirdParty failed: %v", err) + } + if first.SessionID == second.SessionID { + t.Fatalf("expected two distinct sessions") + } + + auditBefore := countLoginAudit(t, repository, first.UserID) + var updatedBefore int64 + if err := repository.RawDB().QueryRowContext(ctx, ` + SELECT updated_at_ms FROM auth_sessions WHERE app_code = 'lalu' AND session_id = ? + `, second.SessionID).Scan(&updatedBefore); err != nil { + t.Fatalf("query session updated_at failed: %v", err) + } + + now = time.UnixMilli(60000) + token, session, err := authSvc.AdminIssueUserAccessToken(ctx, first.UserID, authservice.Meta{AppCode: "lalu", RequestID: "req-admin-token"}) + if err != nil { + t.Fatalf("AdminIssueUserAccessToken failed: %v", err) + } + if token.SessionID != second.SessionID || session.SessionID != second.SessionID { + t.Fatalf("admin token must bind newest active session: token=%s session=%s want=%s", token.SessionID, session.SessionID, second.SessionID) + } + if session.DeviceID != secondRegistration.DeviceID { + t.Fatalf("admin token session device mismatch: %+v", session) + } + if token.AccessToken == "" || token.RefreshToken != "" || token.TokenType != "Bearer" { + t.Fatalf("admin token must be access-only Bearer: %+v", token) + } + + if auditAfter := countLoginAudit(t, repository, first.UserID); auditAfter != auditBefore { + t.Fatalf("admin issue must not write login_audit: before=%d after=%d", auditBefore, auditAfter) + } + var updatedAfter int64 + if err := repository.RawDB().QueryRowContext(ctx, ` + SELECT updated_at_ms FROM auth_sessions WHERE app_code = 'lalu' AND session_id = ? + `, second.SessionID).Scan(&updatedAfter); err != nil { + t.Fatalf("query session updated_at failed: %v", err) + } + if updatedAfter != updatedBefore { + t.Fatalf("admin issue must not mutate session row: before=%d after=%d", updatedBefore, updatedAfter) + } +} + +func TestAdminIssueUserAccessTokenReturnsNotFoundWithoutActiveSession(t *testing.T) { + // 无活跃会话时必须返回 NotFound:token 是无状态 JWT,没有 session 就没有可重签对象,后台要能明确展示。 + ctx := context.Background() + repository := mysqltest.NewRepository(t) + seedCountry(t, repository, "SG") + now := time.UnixMilli(1000) + authSvc := newAuthService(repository, &now, []int64{910005}, []string{"7100005"}) + + token, _, err := authSvc.LoginThirdParty(ctx, "wechat", "openid-admin-no-session", thirdPartyRegistration("android"), authservice.Meta{AppCode: "lalu", RequestID: "req-login"}) + if err != nil { + t.Fatalf("LoginThirdParty failed: %v", err) + } + if _, err := authSvc.Logout(ctx, token.SessionID, "", authservice.Meta{AppCode: "lalu", RequestID: "req-logout"}); err != nil { + t.Fatalf("Logout failed: %v", err) + } + + if _, _, err := authSvc.AdminIssueUserAccessToken(ctx, token.UserID, authservice.Meta{AppCode: "lalu", RequestID: "req-admin-token"}); !xerr.IsCode(err, xerr.NotFound) { + t.Fatalf("expected NotFound without active session, got %v", err) + } +} diff --git a/services/user-service/internal/service/host/business.go b/services/user-service/internal/service/host/business.go index e081757e..0753a4d4 100644 --- a/services/user-service/internal/service/host/business.go +++ b/services/user-service/internal/service/host/business.go @@ -16,10 +16,13 @@ const ( CapabilityManagerGrantVIP = "manager_grant_vip" CapabilityManagerUpdateUserLevel = "manager_update_user_level" CapabilityManagerAddBDLeader = "manager_add_bd_leader" - CapabilityManagerAddAdmin = "manager_add_admin" - CapabilityManagerAddSuperadmin = "manager_add_superadmin" - CapabilityManagerBlockUser = "manager_block_user" - CapabilityManagerTransferCountry = "manager_transfer_user_country" + CapabilityManagerCrossRegion = "manager_cross_region" + // CapabilityManagerAddBDLeaderInRegion 兼容上一版 gateway,底层读取同一个通用跨区经理开关。 + CapabilityManagerAddBDLeaderInRegion = "manager_add_bd_leader_in_region" + CapabilityManagerAddAdmin = "manager_add_admin" + CapabilityManagerAddSuperadmin = "manager_add_superadmin" + CapabilityManagerBlockUser = "manager_block_user" + CapabilityManagerTransferCountry = "manager_transfer_user_country" ) // CheckBusinessCapability 是 App/H5 业务入口的服务端身份边界。 @@ -49,6 +52,8 @@ func (s *Service) CheckBusinessCapability(ctx context.Context, actorUserID int64 CapabilityManagerGrantVIP, CapabilityManagerUpdateUserLevel, CapabilityManagerAddBDLeader, + CapabilityManagerCrossRegion, + CapabilityManagerAddBDLeaderInRegion, CapabilityManagerAddAdmin, CapabilityManagerAddSuperadmin, CapabilityManagerBlockUser, diff --git a/services/user-service/internal/service/host/commands.go b/services/user-service/internal/service/host/commands.go index 78bd6ea7..a578b374 100644 --- a/services/user-service/internal/service/host/commands.go +++ b/services/user-service/internal/service/host/commands.go @@ -1,6 +1,6 @@ package host -// SearchAgenciesCommand 描述同国家 Agency 搜索条件。 +// SearchAgenciesCommand 描述 Agency 搜索条件;范围由持久化层按 app_code 的角色区域策略决定。 type SearchAgenciesCommand struct { UserID int64 Keyword string diff --git a/services/user-service/internal/service/host/role_scope_policy.go b/services/user-service/internal/service/host/role_scope_policy.go new file mode 100644 index 00000000..56478939 --- /dev/null +++ b/services/user-service/internal/service/host/role_scope_policy.go @@ -0,0 +1,84 @@ +package host + +import ( + "strings" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" +) + +type RoleScope string + +const ( + RoleScopeCountry RoleScope = "country" + RoleScopeRegion RoleScope = "region" + RoleScopeGlobal RoleScope = "global" + + RoleScopeSceneOrganizationExpansion = "organization_role_expansion" + RoleScopeSceneManagerOperations = "manager_operations" + // RoleScopeSceneManagerAddBDLeader 兼容上一版 gateway/admin 的策略查询,解析后统一返回 manager_operations。 + RoleScopeSceneManagerAddBDLeader = "manager_add_bd_leader" +) + +// RoleScopePolicy 把 App 固有边界和可运营扩展能力分开;业务入口只根据解析结果判断目标范围。 +type RoleScopePolicy struct { + Scene string + BaseScope RoleScope + ExpandedScope RoleScope + RegionExpansionConfigurable bool +} + +var defaultRoleScopePolicies = map[string]RoleScopePolicy{ + RoleScopeSceneOrganizationExpansion: { + Scene: RoleScopeSceneOrganizationExpansion, + BaseScope: RoleScopeRegion, + ExpandedScope: RoleScopeRegion, + }, + RoleScopeSceneManagerOperations: { + Scene: RoleScopeSceneManagerOperations, + BaseScope: RoleScopeCountry, + ExpandedScope: RoleScopeRegion, + RegionExpansionConfigurable: true, + }, +} + +var appRoleScopePolicyOverrides = map[string]map[string]RoleScopePolicy{ + "huwaa": { + RoleScopeSceneOrganizationExpansion: { + Scene: RoleScopeSceneOrganizationExpansion, + BaseScope: RoleScopeGlobal, + ExpandedScope: RoleScopeGlobal, + }, + RoleScopeSceneManagerOperations: { + Scene: RoleScopeSceneManagerOperations, + BaseScope: RoleScopeGlobal, + ExpandedScope: RoleScopeGlobal, + }, + }, +} + +// ResolveRoleScopePolicy 是 App 差异的唯一解析入口;新增 App 只扩展注册表,不修改搜索或写入 handler。 +func ResolveRoleScopePolicy(rawAppCode string, rawScene string) (RoleScopePolicy, error) { + scene := strings.ToLower(strings.TrimSpace(rawScene)) + if scene == RoleScopeSceneManagerAddBDLeader { + scene = RoleScopeSceneManagerOperations + } + policy, ok := defaultRoleScopePolicies[scene] + if !ok { + return RoleScopePolicy{}, xerr.New(xerr.InvalidArgument, "role scope scene is invalid") + } + if overrides := appRoleScopePolicyOverrides[appcode.Normalize(rawAppCode)]; overrides != nil { + if override, exists := overrides[scene]; exists { + policy = override + } + } + return policy, nil +} + +// EffectiveScope 只允许声明为 configurable 的策略接受经理个人扩区开关。 +func (p RoleScopePolicy) EffectiveScope(regionExpansionEnabled bool) RoleScope { + if p.RegionExpansionConfigurable && regionExpansionEnabled { + return p.ExpandedScope + } + return p.BaseScope +} diff --git a/services/user-service/internal/service/host/role_scope_policy_test.go b/services/user-service/internal/service/host/role_scope_policy_test.go new file mode 100644 index 00000000..2549c67e --- /dev/null +++ b/services/user-service/internal/service/host/role_scope_policy_test.go @@ -0,0 +1,40 @@ +package host + +import "testing" + +func TestResolveRoleScopePolicyKeepsAppDifferencesInRegistry(t *testing.T) { + tests := []struct { + name string + appCode string + baseScope RoleScope + configurable bool + expanded RoleScope + }{ + {name: "default app", appCode: "lalu", baseScope: RoleScopeCountry, configurable: true, expanded: RoleScopeRegion}, + {name: "normalized huwaa", appCode: " HUWAa ", baseScope: RoleScopeGlobal, configurable: false, expanded: RoleScopeGlobal}, + } + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + policy, err := ResolveRoleScopePolicy(testCase.appCode, RoleScopeSceneManagerOperations) + if err != nil { + t.Fatalf("resolve policy: %v", err) + } + if policy.BaseScope != testCase.baseScope || policy.RegionExpansionConfigurable != testCase.configurable { + t.Fatalf("policy mismatch: %+v", policy) + } + if got := policy.EffectiveScope(true); got != testCase.expanded { + t.Fatalf("expanded scope = %q, want %q", got, testCase.expanded) + } + }) + } +} + +func TestResolveRoleScopePolicyKeepsLegacyManagerSceneCompatible(t *testing.T) { + policy, err := ResolveRoleScopePolicy("lalu", RoleScopeSceneManagerAddBDLeader) + if err != nil { + t.Fatalf("resolve legacy manager scene: %v", err) + } + if policy.Scene != RoleScopeSceneManagerOperations || policy.ExpandedScope != RoleScopeRegion { + t.Fatalf("legacy scene must resolve to general manager policy: %+v", policy) + } +} diff --git a/services/user-service/internal/service/host/service.go b/services/user-service/internal/service/host/service.go index 8b7d82fe..cc46da30 100644 --- a/services/user-service/internal/service/host/service.go +++ b/services/user-service/internal/service/host/service.go @@ -57,6 +57,8 @@ type Repository interface { HasActiveManagerCapability(ctx context.Context, userID int64, capability string) (bool, error) ListAgencyMembers(ctx context.Context, agencyID int64, status string) ([]hostdomain.AgencyMembership, error) ListAgencyApplications(ctx context.Context, agencyID int64, status string) ([]hostdomain.AgencyApplication, error) + GetHostEngagementStats(ctx context.Context, query hostdomain.EngagementStatsQuery) (hostdomain.EngagementStats, error) + RecordPrivateMessageEvent(ctx context.Context, event hostdomain.PrivateMessageEvent) (bool, error) } // IDGenerator 生成 Host 领域内部事实 ID。 @@ -106,7 +108,7 @@ func WithClock(now func() time.Time) Option { } } -// SearchAgencies 返回当前用户同国家可加入的有效 Agency。 +// SearchAgencies 返回当前用户可加入的有效 Agency;持久化层按 app_code 决定 Huwaa 全域或其他 App 同国家范围。 func (s *Service) SearchAgencies(ctx context.Context, userID int64, keyword string, pageSize int32) ([]hostdomain.Agency, error) { if s.repository == nil { return nil, xerr.New(xerr.Unavailable, "host repository is not configured") @@ -120,7 +122,7 @@ func (s *Service) SearchAgencies(ctx context.Context, userID int64, keyword stri limit = 50 } - // 业务层只负责输入归一化;用户国家过滤由持久化层读取 users 当前事实完成。 + // 业务层只负责输入归一化;App 级区域策略和用户国家事实都由持久化层统一执行。 return s.repository.SearchAgencies(ctx, SearchAgenciesCommand{ UserID: userID, Keyword: strings.TrimSpace(keyword), @@ -282,7 +284,7 @@ func (s *Service) InviteBD(ctx context.Context, command InviteBDInput) (hostdoma if err := s.requireWriteDependencies(); err != nil { return hostdomain.RoleInvitation{}, err } - // 负责人身份、区域、目标是否已有 BD 都交给持久化层在同一事务中校验,避免发出不可处理的待确认消息。 + // 负责人身份、App 级区域策略、目标是否已有 BD 都在同一事务中校验,避免发出不可处理的待确认消息。 if strings.TrimSpace(command.CommandID) == "" || command.InviterUserID <= 0 || command.TargetUserID <= 0 { return hostdomain.RoleInvitation{}, xerr.New(xerr.InvalidArgument, "command_id, inviter_user_id and target_user_id are required") } diff --git a/services/user-service/internal/service/host/service_test.go b/services/user-service/internal/service/host/service_test.go index 34f304d7..6f0ebaff 100644 --- a/services/user-service/internal/service/host/service_test.go +++ b/services/user-service/internal/service/host/service_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "hyapp/pkg/appcode" "hyapp/pkg/xerr" hostdomain "hyapp/services/user-service/internal/domain/host" userdomain "hyapp/services/user-service/internal/domain/user" @@ -36,8 +37,13 @@ func seedActiveUser(t *testing.T, repository *mysqltest.Repository, userID int64 } func seedActiveUserInCountry(t *testing.T, repository *mysqltest.Repository, userID int64, regionID int64, country string, avatar string) { + seedActiveUserInAppAndCountry(t, repository, appcode.Default, userID, regionID, country, avatar) +} + +func seedActiveUserInAppAndCountry(t *testing.T, repository *mysqltest.Repository, app string, userID int64, regionID int64, country string, avatar string) { t.Helper() repository.PutUser(userdomain.User{ + AppCode: app, UserID: userID, DefaultDisplayUserID: displayID(userID), CurrentDisplayUserID: displayID(userID), @@ -129,6 +135,16 @@ func TestCheckBusinessCapabilityRequiresActiveManagerProfile(t *testing.T) { t.Fatalf("active manager should be allowed: allowed=%v reason=%q", allowed, reason) } + allowed, reason, err = svc.CheckBusinessCapability(ctx, 101, hostservice.CapabilityManagerCrossRegion) + if err != nil || allowed || reason != "manager_capability_required" { + t.Fatalf("region expansion must default closed: allowed=%v reason=%q err=%v", allowed, reason, err) + } + repository.SetManagerCrossRegion(101, true) + allowed, reason, err = svc.CheckBusinessCapability(ctx, 101, hostservice.CapabilityManagerCrossRegion) + if err != nil || !allowed || reason != "" { + t.Fatalf("enabled region expansion should be allowed: allowed=%v reason=%q err=%v", allowed, reason, err) + } + repository.SetManagerGrantVIP(101, false) allowed, reason, err = svc.CheckBusinessCapability(ctx, 101, hostservice.CapabilityManagerGrantVIP) if err != nil { @@ -241,6 +257,47 @@ func TestApplyReviewApproveCreatesHostMembershipIdempotently(t *testing.T) { } } +func TestDefaultAppHostApplicationStillRejectsCrossRegionAtApplyAndReview(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + seedActiveUser(t, repository, 101, 10) + seedActiveUser(t, repository, 202, 20) + seedActiveUser(t, repository, 203, 10) + repository.PutAgency(hostdomain.Agency{ + AgencyID: 301, + OwnerUserID: 101, + RegionID: 10, + Status: hostdomain.AgencyStatusActive, + JoinEnabled: true, + }) + svc := newHostService(repository, 1_500, 2_500) + + if _, err := svc.ApplyToAgency(ctx, hostservice.ApplyToAgencyInput{ + CommandID: "lalu-apply-cross-region", + UserID: 202, + AgencyID: 301, + }); xerr.CodeOf(err) != xerr.PermissionDenied { + t.Fatalf("default App must keep apply-time region boundary: %v", err) + } + application, err := svc.ApplyToAgency(ctx, hostservice.ApplyToAgencyInput{ + CommandID: "lalu-apply-before-region-drift", + UserID: 203, + AgencyID: 301, + }) + if err != nil { + t.Fatalf("same-region application setup failed: %v", err) + } + seedActiveUser(t, repository, 203, 20) + if _, err := svc.ReviewAgencyApplication(ctx, hostservice.ReviewAgencyApplicationInput{ + CommandID: "lalu-review-after-region-drift", + ReviewerUserID: 101, + ApplicationID: application.ApplicationID, + Decision: hostdomain.ApplicationDecisionApprove, + }); xerr.CodeOf(err) != xerr.PermissionDenied { + t.Fatalf("default App must keep review-time region boundary: %v", err) + } +} + func TestAcceptAgencyInvitationCreatesOwnerHostAndPreservesExistingHostBinding(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) @@ -660,6 +717,129 @@ func TestRoleInvitationCreateRejectsCrossRegionWithRegionMismatch(t *testing.T) } } +func TestHuwaaAgencyAndBDInvitationsAllowCrossRegionAtCreateAndAccept(t *testing.T) { + ctx := appcode.WithContext(context.Background(), "huwaa") + repository := mysqltest.NewRepository(t) + for _, region := range []userdomain.Region{ + {AppCode: "huwaa", RegionID: 10, RegionCode: "R10", Name: "Region 10", Status: userdomain.RegionStatusActive}, + {AppCode: "huwaa", RegionID: 20, RegionCode: "R20", Name: "Region 20", Status: userdomain.RegionStatusActive}, + } { + repository.PutRegion(region) + } + seedActiveUserInAppAndCountry(t, repository, "huwaa", 711, 10, "US", "") + seedActiveUserInAppAndCountry(t, repository, "huwaa", 712, 20, "CA", "") + seedActiveUserInAppAndCountry(t, repository, "huwaa", 722, 20, "GB", "") + + // 先通过正式写入口建立 inviter 的 Leader + 普通 BD 双身份,后续两类邀请都使用同一套真实权限事实。 + setupSvc := newHostService(repository, 20_000, 21_000) + if _, err := setupSvc.CreateBDLeader(ctx, hostservice.CreateBDLeaderInput{ + CommandID: "huwaa-create-cross-region-inviter", + AdminUserID: 1, + TargetUserID: 711, + }); err != nil { + t.Fatalf("CreateBDLeader inviter failed: %v", err) + } + svc := newHostService(repository, 21_000, 22_000) + + agencyInvitation, err := svc.InviteAgency(ctx, hostservice.InviteAgencyInput{ + CommandID: "huwaa-invite-agency-cross-region", + InviterUserID: 711, + TargetUserID: 712, + AgencyName: "Huwaa Cross Region Agency", + }) + if err != nil { + t.Fatalf("Huwaa InviteAgency should allow cross-region target: %v", err) + } + agencyResult, err := svc.ProcessRoleInvitation(ctx, hostservice.ProcessRoleInvitationInput{ + CommandID: "huwaa-accept-agency-cross-region", + ActorUserID: 712, + InvitationID: agencyInvitation.InvitationID, + Action: hostdomain.InvitationActionAccept, + }) + if err != nil { + t.Fatalf("Huwaa agency invitation accept should allow cross-region organization link: %v", err) + } + if agencyResult.Agency.RegionID != 20 || agencyResult.Agency.ParentBDUserID != 711 { + t.Fatalf("cross-region agency must keep target region and inviter relationship: %+v", agencyResult.Agency) + } + // 产品只放开 Host 主动申请;Agency owner 主动邀请 Host 仍沿用原区域边界,避免把例外扩散到未授权流程。 + if _, err := svc.InviteHost(ctx, hostservice.InviteHostInput{ + CommandID: "huwaa-invite-host-cross-region-still-blocked", + InviterUserID: 712, + TargetUserID: 711, + }); xerr.CodeOf(err) != xerr.RegionMismatch { + t.Fatalf("Huwaa Agency-to-Host invitation must remain region-scoped: %v", err) + } + + bdInvitation, err := svc.InviteBD(ctx, hostservice.InviteBDInput{ + CommandID: "huwaa-invite-bd-cross-region", + InviterUserID: 711, + TargetUserID: 722, + }) + if err != nil { + t.Fatalf("Huwaa InviteBD should allow cross-region target: %v", err) + } + bdResult, err := svc.ProcessRoleInvitation(ctx, hostservice.ProcessRoleInvitationInput{ + CommandID: "huwaa-accept-bd-cross-region", + ActorUserID: 722, + InvitationID: bdInvitation.InvitationID, + Action: hostdomain.InvitationActionAccept, + }) + if err != nil { + t.Fatalf("Huwaa BD invitation accept should allow cross-region leader link: %v", err) + } + if bdResult.BDProfile.RegionID != 20 || bdResult.BDProfile.ParentLeaderUserID != 711 { + t.Fatalf("cross-region BD must keep target region and inviter leader: %+v", bdResult.BDProfile) + } +} + +func TestHuwaaHostApplicationSearchAndReviewAllowCrossRegion(t *testing.T) { + ctx := appcode.WithContext(context.Background(), "huwaa") + repository := mysqltest.NewRepository(t) + seedActiveUserInAppAndCountry(t, repository, "huwaa", 731, 10, "US", "") + seedActiveUserInAppAndCountry(t, repository, "huwaa", 732, 20, "CA", "") + svc := newHostService(repository, 22_000, 23_000) + + created, err := svc.CreateAgency(ctx, hostservice.CreateAgencyInput{ + CommandID: "huwaa-create-host-application-agency", + AdminUserID: 1, + OwnerUserID: 731, + Name: "Huwaa Global Agency", + JoinEnabled: true, + }) + if err != nil { + t.Fatalf("CreateAgency failed: %v", err) + } + agencies, err := svc.SearchAgencies(ctx, 732, "", 20) + if err != nil { + t.Fatalf("Huwaa SearchAgencies failed: %v", err) + } + if len(agencies) != 1 || agencies[0].AgencyID != created.Agency.AgencyID { + t.Fatalf("Huwaa host applicant must discover cross-country agency: %+v", agencies) + } + + application, err := svc.ApplyToAgency(ctx, hostservice.ApplyToAgencyInput{ + CommandID: "huwaa-apply-host-cross-region", + UserID: 732, + AgencyID: created.Agency.AgencyID, + }) + if err != nil { + t.Fatalf("Huwaa host application should allow cross-region agency: %v", err) + } + result, err := svc.ReviewAgencyApplication(ctx, hostservice.ReviewAgencyApplicationInput{ + CommandID: "huwaa-review-host-cross-region", + ReviewerUserID: 731, + ApplicationID: application.ApplicationID, + Decision: hostdomain.ApplicationDecisionApprove, + }) + if err != nil { + t.Fatalf("Huwaa host application review should preserve cross-region relationship: %v", err) + } + if result.Membership.RegionID != 20 || result.Membership.AgencyID != created.Agency.AgencyID || result.Application.Status != hostdomain.ApplicationStatusApproved { + t.Fatalf("cross-region host application facts mismatch: %+v", result) + } +} + func TestRoleInvitationAcceptRejectsRegionDriftWithRegionMismatch(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) @@ -1690,6 +1870,45 @@ func TestListActiveCoinSellersInMyRegionFiltersByCurrentUserRegion(t *testing.T) } } +func TestHuwaaCoinSellerListAndSubApplicationRemainRegionScoped(t *testing.T) { + ctx := appcode.WithContext(context.Background(), "huwaa") + repository := mysqltest.NewRepository(t) + for _, region := range []userdomain.Region{ + {AppCode: "huwaa", RegionID: 70, RegionCode: "R70", Name: "Region 70", Status: userdomain.RegionStatusActive}, + {AppCode: "huwaa", RegionID: 71, RegionCode: "R71", Name: "Region 71", Status: userdomain.RegionStatusActive}, + } { + repository.PutRegion(region) + } + seedActiveUserInAppAndCountry(t, repository, "huwaa", 940, 70, "US", "") + seedActiveUserInAppAndCountry(t, repository, "huwaa", 941, 70, "US", "") + seedActiveUserInAppAndCountry(t, repository, "huwaa", 942, 71, "CA", "") + svc := newHostService(repository, 23_000, 24_000) + for _, userID := range []int64{941, 942} { + if _, err := svc.CreateCoinSeller(ctx, hostservice.CreateCoinSellerInput{ + CommandID: fmt.Sprintf("huwaa-create-coin-seller-%d", userID), + AdminUserID: 1, + TargetUserID: userID, + }); err != nil { + t.Fatalf("CreateCoinSeller(%d) failed: %v", userID, err) + } + } + + items, err := svc.ListActiveCoinSellersInMyRegion(ctx, 940) + if err != nil { + t.Fatalf("ListActiveCoinSellersInMyRegion failed: %v", err) + } + if len(items) != 1 || items[0].UserID != 941 { + t.Fatalf("Huwaa coin-seller list must remain same-region: %+v", items) + } + if _, err := svc.SubmitSubCoinSellerApplication(ctx, hostservice.SubmitSubCoinSellerApplicationInput{ + CommandID: "huwaa-submit-cross-region-sub-seller", + ParentUserID: 941, + TargetUserID: 942, + }); xerr.CodeOf(err) != xerr.RegionMismatch { + t.Fatalf("Huwaa sub coin-seller invitation must remain region-scoped: %v", err) + } +} + func TestCreateSubCoinSellerRPCRequiresAdminReview(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) diff --git a/services/user-service/internal/service/host/stats.go b/services/user-service/internal/service/host/stats.go new file mode 100644 index 00000000..fd2eb54a --- /dev/null +++ b/services/user-service/internal/service/host/stats.go @@ -0,0 +1,38 @@ +package host + +import ( + "context" + "strings" + "time" + + "hyapp/pkg/xerr" + hostdomain "hyapp/services/user-service/internal/domain/host" +) + +const maxHostStatsRange = 366 * 24 * time.Hour + +// GetHostEngagementStats 读取 user-service 拥有的统计事实;收益和送礼人数由 wallet-service 聚合。 +func (s *Service) GetHostEngagementStats(ctx context.Context, query hostdomain.EngagementStatsQuery) (hostdomain.EngagementStats, error) { + if s.repository == nil { + return hostdomain.EngagementStats{}, xerr.New(xerr.Unavailable, "host repository is not configured") + } + if query.HostUserID <= 0 || query.StartAtMS <= 0 || query.EndAtMS <= query.StartAtMS { + return hostdomain.EngagementStats{}, xerr.New(xerr.InvalidArgument, "host stats range is invalid") + } + if query.EndAtMS-query.StartAtMS > int64(maxHostStatsRange/time.Millisecond) { + return hostdomain.EngagementStats{}, xerr.New(xerr.InvalidArgument, "host stats range is too large") + } + return s.repository.GetHostEngagementStats(ctx, query) +} + +// RecordPrivateMessageEvent 只保存发送者、接收者和事件时间;消息正文不进入统计库。 +func (s *Service) RecordPrivateMessageEvent(ctx context.Context, event hostdomain.PrivateMessageEvent) (bool, error) { + if s.repository == nil { + return false, xerr.New(xerr.Unavailable, "host repository is not configured") + } + event.EventID = strings.TrimSpace(event.EventID) + if event.EventID == "" || len(event.EventID) > 128 || event.SenderUserID <= 0 || event.TargetUserID <= 0 || event.OccurredAtMS <= 0 { + return false, xerr.New(xerr.InvalidArgument, "private message event is invalid") + } + return s.repository.RecordPrivateMessageEvent(ctx, event) +} diff --git a/services/user-service/internal/service/roomtime/service.go b/services/user-service/internal/service/roomtime/service.go new file mode 100644 index 00000000..c778611a --- /dev/null +++ b/services/user-service/internal/service/roomtime/service.go @@ -0,0 +1,65 @@ +// Package roomtime runs room presence outbox consumers and exposes in-room duration compensation. +package roomtime + +import ( + "context" + "time" + + "hyapp/pkg/xerr" + roomtimedomain "hyapp/services/user-service/internal/domain/roomtime" +) + +// Repository applies room presence events and compensates abnormal open sessions. +type Repository interface { + ProcessRoomPresenceEvent(ctx context.Context, event roomtimedomain.RoomPresenceEvent) (roomtimedomain.ApplyResult, error) + CompensateOpenSessions(ctx context.Context, options roomtimedomain.CompensationOptions) (roomtimedomain.CompensationResult, error) +} + +// Service owns the user in-room duration read model. +type Service struct { + repository Repository +} + +// CompensationWorkerOptions controls abnormal open session closure. +type CompensationWorkerOptions struct { + WorkerID string + BatchSize int + OpenSessionMaxAge time.Duration +} + +// New creates a room time service with the repository that owns idempotent application. +func New(repository Repository) *Service { + return &Service{repository: repository} +} + +// ConsumeRoomPresenceEvent applies one MQ-delivered RoomUserJoined/RoomUserLeft/RoomUserKicked fact. +func (s *Service) ConsumeRoomPresenceEvent(ctx context.Context, event roomtimedomain.RoomPresenceEvent) (roomtimedomain.ApplyResult, error) { + if s == nil || s.repository == nil { + return roomtimedomain.ApplyResult{}, xerr.New(xerr.Unavailable, "room time repository is not configured") + } + switch event.EventType { + case roomtimedomain.RoomUserJoinedEventType, roomtimedomain.RoomUserLeftEventType, roomtimedomain.RoomUserKickedEventType: + default: + return roomtimedomain.ApplyResult{Skipped: true, Reason: "unsupported_event_type"}, nil + } + return s.repository.ProcessRoomPresenceEvent(ctx, event) +} + +// ProcessOpenSessionCompensationBatch exposes one bounded compensation scan for cron-service. +func (s *Service) ProcessOpenSessionCompensationBatch(ctx context.Context, options CompensationWorkerOptions) (roomtimedomain.CompensationResult, error) { + if s == nil || s.repository == nil { + return roomtimedomain.CompensationResult{}, xerr.New(xerr.Unavailable, "room time repository is not configured") + } + if options.BatchSize <= 0 { + options.BatchSize = 200 + } + if options.OpenSessionMaxAge <= 0 { + // stale worker 2 分钟内会把断线用户判离房;这里只兜 room-service 节点崩溃或 outbox 丢失的极端场景。 + options.OpenSessionMaxAge = 24 * time.Hour + } + return s.repository.CompensateOpenSessions(ctx, roomtimedomain.CompensationOptions{ + NowMs: time.Now().UnixMilli(), + BatchSize: options.BatchSize, + OpenSessionMaxAgeMs: options.OpenSessionMaxAge.Milliseconds(), + }) +} diff --git a/services/user-service/internal/service/user/admin_ban.go b/services/user-service/internal/service/user/admin_ban.go new file mode 100644 index 00000000..7e169d2b --- /dev/null +++ b/services/user-service/internal/service/user/admin_ban.go @@ -0,0 +1,177 @@ +package user + +import ( + "context" + "strings" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + userdomain "hyapp/services/user-service/internal/domain/user" +) + +const ( + AdminUserBanStatusActive = "active" + AdminUserBanStatusReleased = "released" + AdminUserBanStatusExpired = "expired" + + adminUserBanReasonPrefix = "admin_user_ban:" +) + +// AdminUserBanCommand 是后台管理员永久或限时封禁命令;ExpiresAtMs=0 明确表示永久。 +type AdminUserBanCommand struct { + CommandID string + TargetUserID int64 + ExpiresAtMs int64 + OperatorAdminID int64 + Reason string + RequestID string + NowMs int64 +} + +// ReleaseAdminUserBanCommand 只用于单条管理员封禁的自动到期和底层兼容释放。 +// 后台人工解封必须走 AdminForceUnbanCommand,保证经理封禁和重叠管理员封禁在同一事务内一起释放。 +type ReleaseAdminUserBanCommand struct { + BanID string + TargetUserID int64 + OperatorAdminID int64 + Status string + Reason string + RequestID string + NowMs int64 +} + +// AdminForceUnbanCommand 是后台管理员覆盖全部用户治理封禁来源的命令。 +// RequestID 是跨 gRPC 重试的幂等边界,避免一次超时重试误释放原操作完成后新产生的封禁。 +type AdminForceUnbanCommand struct { + TargetUserID int64 + OperatorAdminID int64 + Reason string + RequestID string + NowMs int64 +} + +// AdminForceUnbanResult 返回本次命令实际释放的事实数量和最终用户主状态。 +// StatusRestored 只描述首次执行时是否发生 banned/disabled -> active,不因幂等重试重复写状态日志。 +type AdminForceUnbanResult struct { + User userdomain.User + ReleasedAdminBanCount int64 + ReleasedManagerBlockCount int64 + StatusRestored bool +} + +// AdminUserBan 是后台管理员封禁事实;TargetOldStatus 用于审计,不作为自动恢复的唯一判断依据。 +type AdminUserBan struct { + BanID string + CommandID string + TargetUserID int64 + TargetOldStatus userdomain.Status + ExpiresAtMs int64 + Status string + Reason string + OperatorAdminID int64 + CreatedAtMs int64 + UpdatedAtMs int64 + ReleasedByAdminID int64 + ReleasedReason string + ReleasedAtMs int64 +} + +// AdminUserBanPersistenceResult 表达创建封禁和账号准入状态在同一事务提交后的事实。 +type AdminUserBanPersistenceResult struct { + Ban AdminUserBan + Status UserStatusPersistenceResult + StatusChanged bool +} + +// AdminUserBanRelease 表达一条管理员封禁释放后的用户状态;仍有其他来源时 Restored=false。 +type AdminUserBanRelease struct { + Ban AdminUserBan + User userdomain.User + Restored bool +} + +// AdminBanUser 创建永久或限时管理员封禁。 +// 已被其他管理员/经理封禁的用户允许叠加新事实,但不会重复吊销 session 或重复驱逐实时连接。 +func (s *Service) AdminBanUser(ctx context.Context, command AdminUserBanCommand) (AdminUserBan, UserStatusResult, error) { + if s.moderationRepository == nil { + return AdminUserBan{}, UserStatusResult{}, xerr.New(xerr.Unavailable, "moderation repository is not configured") + } + command.CommandID = strings.TrimSpace(command.CommandID) + command.Reason = strings.TrimSpace(command.Reason) + if command.CommandID == "" || command.TargetUserID <= 0 || command.OperatorAdminID <= 0 || command.ExpiresAtMs < 0 { + return AdminUserBan{}, UserStatusResult{}, xerr.New(xerr.InvalidArgument, "admin ban command is incomplete") + } + // 封禁期限由 user-service owner 时钟判定;调用方 sent_at_ms 仅是链路元数据,延迟或伪造值不能创建已过期事实。 + command.NowMs = s.now().UTC().UnixMilli() + if command.ExpiresAtMs > 0 && command.ExpiresAtMs <= command.NowMs { + return AdminUserBan{}, UserStatusResult{}, xerr.New(xerr.InvalidArgument, "expires_at_ms must be in the future or zero") + } + if command.Reason == "" { + command.Reason = "admin_user_ban" + } + if command.RequestID == "" { + command.RequestID = command.CommandID + } + + persisted, err := s.moderationRepository.CreateAdminUserBan(ctx, command) + if err != nil { + return AdminUserBan{}, UserStatusResult{}, err + } + statusCommand := UserStatusCommand{ + AppCode: appcode.FromContext(ctx), + TargetUserID: command.TargetUserID, + Status: userdomain.StatusBanned, + OperatorType: OperatorTypeAdmin, + OperatorUserID: command.OperatorAdminID, + Reason: adminUserBanReasonPrefix + persisted.Ban.BanID, + RequestID: command.RequestID, + NowMs: command.NowMs, + } + return persisted.Ban, s.statusResultAfterPersistence(ctx, statusCommand, persisted.Status, persisted.StatusChanged), nil +} + +// AdminUnbanUser 是后台人工强制解封入口。 +// repository 原子释放全部管理员/经理封禁,并覆盖没有独立事实表记录的 legacy banned/disabled 状态。 +func (s *Service) AdminUnbanUser(ctx context.Context, command AdminForceUnbanCommand) (AdminForceUnbanResult, UserStatusResult, error) { + if s.moderationRepository == nil { + return AdminForceUnbanResult{}, UserStatusResult{}, xerr.New(xerr.Unavailable, "moderation repository is not configured") + } + command.Reason = strings.TrimSpace(command.Reason) + if command.TargetUserID <= 0 || command.OperatorAdminID <= 0 { + return AdminForceUnbanResult{}, UserStatusResult{}, xerr.New(xerr.InvalidArgument, "admin unban command is incomplete") + } + // 解封审计和状态恢复统一使用 owner 时钟,避免跨服务时钟偏差污染事实顺序。 + command.NowMs = s.now().UTC().UnixMilli() + command.RequestID = strings.TrimSpace(command.RequestID) + if command.RequestID == "" { + return AdminForceUnbanResult{}, UserStatusResult{}, xerr.New(xerr.InvalidArgument, "admin unban request_id is required") + } + if command.Reason == "" { + command.Reason = "admin_force_unban" + } + // release_reason 还会附带操作类型和管理员 ID,给前缀预留空间,避免多字节原因触发 MySQL 截断。 + if len(command.Reason) > 200 { + return AdminForceUnbanResult{}, UserStatusResult{}, xerr.New(xerr.InvalidArgument, "admin unban reason is too long") + } + + release, err := s.moderationRepository.ForceReleaseUserBans(ctx, command) + if err != nil { + return AdminForceUnbanResult{}, UserStatusResult{}, err + } + return release, UserStatusResult{User: release.User, AccessTokenRevoked: true}, nil +} + +// ExpireAdminUserBans 处理一批已到期的限时管理员封禁;永久封禁的 expires_at_ms=0 永远不会被 claim。 +func (s *Service) ExpireAdminUserBans(ctx context.Context, limit int32) (int, error) { + if s.moderationRepository == nil { + return 0, xerr.New(xerr.Unavailable, "moderation repository is not configured") + } + if limit <= 0 || limit > 500 { + limit = 100 + } + releases, err := s.moderationRepository.ExpireAdminUserBans(ctx, s.now().UTC().UnixMilli(), limit) + if err != nil { + return 0, err + } + return len(releases), nil +} diff --git a/services/user-service/internal/service/user/admin_profile.go b/services/user-service/internal/service/user/admin_profile.go new file mode 100644 index 00000000..5364e8e7 --- /dev/null +++ b/services/user-service/internal/service/user/admin_profile.go @@ -0,0 +1,40 @@ +package user + +import ( + "context" + + "hyapp/pkg/xerr" + userdomain "hyapp/services/user-service/internal/domain/user" +) + +const maxAdminProfileBatchSize = 500 + +// BatchGetUserAdminProfiles 一次读取后台列表所需的 user-service owner 事实。 +// 调用方必须按返回 map 的 key 处理缺失用户,不能用请求 ID 构造占位资料。 +func (s *Service) BatchGetUserAdminProfiles(ctx context.Context, userIDs []int64) (map[int64]userdomain.AdminProfile, error) { + if len(userIDs) == 0 { + return map[int64]userdomain.AdminProfile{}, nil + } + if s.userRepository == nil { + return nil, xerr.New(xerr.Unavailable, "user repository is not configured") + } + + // 去重既限制 SQL IN 参数数量,也保证同一个用户的角色与封禁事实只聚合一次。 + unique := make([]int64, 0, len(userIDs)) + seen := make(map[int64]struct{}, len(userIDs)) + for _, userID := range userIDs { + if userID <= 0 { + return nil, xerr.New(xerr.InvalidArgument, "user_ids contains invalid value") + } + if _, exists := seen[userID]; exists { + continue + } + seen[userID] = struct{}{} + unique = append(unique, userID) + } + if len(unique) > maxAdminProfileBatchSize { + return nil, xerr.New(xerr.InvalidArgument, "user_ids exceeds batch limit") + } + + return s.userRepository.BatchGetUserAdminProfiles(ctx, unique, s.now().UTC().UnixMilli()) +} diff --git a/services/user-service/internal/service/user/country_outbox_test.go b/services/user-service/internal/service/user/country_outbox_test.go index a95a54e4..33eeec6b 100644 --- a/services/user-service/internal/service/user/country_outbox_test.go +++ b/services/user-service/internal/service/user/country_outbox_test.go @@ -135,3 +135,72 @@ func TestAdminChangeUserCountryWritesOutboxWhenRegionUnchanged(t *testing.T) { t.Fatalf("same-region outbox payload mismatch: %v", payload) } } + +func TestAppChangeUserCountryKeepsSameMillisecondSameRegionOutboxEventsDistinct(t *testing.T) { + ctx := appcode.WithContext(context.Background(), appcode.Default) + repository := mysqltest.NewRepository(t) + for _, countryCode := range []string{"US", "CA", "MX"} { + repository.PutCountry(userdomain.Country{CountryCode: countryCode, Enabled: true}) + } + repository.PutRegion(userdomain.Region{ + RegionID: 404, + RegionCode: "NORTH-AMERICA", + Name: "North America", + Countries: []string{"US", "CA", "MX"}, + }) + repository.PutUser(userdomain.User{ + UserID: 91003, + DefaultDisplayUserID: "91003", + CurrentDisplayUserID: "91003", + Country: "US", + RegionID: 404, + ProfileCompleted: true, + ProfileCompletedAtMs: 1, + OnboardingStatus: userdomain.OnboardingStatusCompleted, + Status: userdomain.StatusActive, + }) + // 固定同一毫秒复现高速连续修改;国家码未进入 event_id 时,第二条同区域事件会被 INSERT IGNORE 静默丢弃。 + svc := userservice.New(repository, + userservice.WithCountryRegionRepository(repository), + userservice.WithClock(func() time.Time { return time.UnixMilli(1_700_000_789_000).UTC() }), + ) + + if _, _, err := svc.ChangeUserCountry(ctx, 91003, "CA", "req-app-country-ca"); err != nil { + t.Fatalf("first app country change failed: %v", err) + } + if _, _, err := svc.ChangeUserCountry(ctx, 91003, "MX", "req-app-country-mx"); err != nil { + t.Fatalf("second app country change failed: %v", err) + } + + rows, err := repository.RawDB().QueryContext(ctx, ` + SELECT event_id, CAST(payload_json AS CHAR) + FROM user_outbox + WHERE app_code = ? AND event_type = 'UserRegionChanged' AND aggregate_id = ? + `, appcode.Default, int64(91003)) + if err != nil { + t.Fatalf("query app UserRegionChanged outbox failed: %v", err) + } + defer rows.Close() + found := make(map[string]string) + for rows.Next() { + var eventID string + var payloadJSON string + if err := rows.Scan(&eventID, &payloadJSON); err != nil { + t.Fatalf("scan app UserRegionChanged outbox failed: %v", err) + } + var payload map[string]any + if err := json.Unmarshal([]byte(payloadJSON), &payload); err != nil { + t.Fatalf("decode app UserRegionChanged payload failed: %v", err) + } + if payload["source"] != "app" || int64(payload["old_region_id"].(float64)) != 404 || int64(payload["new_region_id"].(float64)) != 404 { + t.Fatalf("app UserRegionChanged payload mismatch: %v", payload) + } + found[payload["new_country"].(string)] = eventID + } + if err := rows.Err(); err != nil { + t.Fatalf("iterate app UserRegionChanged outbox failed: %v", err) + } + if len(found) != 2 || found["CA"] == "" || found["MX"] == "" || found["CA"] == found["MX"] { + t.Fatalf("same-millisecond app country changes must keep two distinct events: %+v", found) + } +} diff --git a/services/user-service/internal/service/user/moderation.go b/services/user-service/internal/service/user/moderation.go index 667b4bd3..07c2d08b 100644 --- a/services/user-service/internal/service/user/moderation.go +++ b/services/user-service/internal/service/user/moderation.go @@ -10,6 +10,7 @@ import ( "hyapp/pkg/idgen" "hyapp/pkg/xerr" userdomain "hyapp/services/user-service/internal/domain/user" + hostservice "hyapp/services/user-service/internal/service/host" ) const ( @@ -106,6 +107,7 @@ type ManagerUserBlock struct { // ManagerUserBlockRelease 带上是否需要恢复 users.status,避免释放记录时误放仍被其他经理封禁的用户。 type ManagerUserBlockRelease struct { Block ManagerUserBlock + User userdomain.User ShouldRestore bool } @@ -136,24 +138,30 @@ func (s *Service) SetUserStatus(ctx context.Context, command UserStatusCommand) if err != nil { return UserStatusResult{}, err } + return s.statusResultAfterPersistence(ctx, command, persisted, true), nil +} + +// statusResultAfterPersistence 统一把已经提交的用户状态事实转换为外部副作用结果。 +// runBanSideEffects=false 用于“用户原本已 banned、只新增重叠封禁事实”的场景,避免重复踢 IM/房间。 +func (s *Service) statusResultAfterPersistence(ctx context.Context, command UserStatusCommand, persisted UserStatusPersistenceResult, runBanSideEffects bool) UserStatusResult { result := UserStatusResult{ User: persisted.User, RevokedSessionCount: int64(len(persisted.RevokedSessionIDs)), AccessTokenRevoked: true, } - if command.Status == userdomain.StatusActive { + if command.Status == userdomain.StatusActive || !runBanSideEffects { // 恢复 active 只改变准入状态,不重建旧 session、IM 登录或房间 presence。 - return result, nil + return result } result.AccessTokenRevoked, result.AccessTokenError = s.writeSessionDenylist(ctx, command, persisted.RevokedSessionIDs, userStatusRevokeReason) result.IMKicked, result.IMKickError = s.kickIMLogin(ctx, command.TargetUserID) result.RoomEvicted, result.RoomID, result.RTCKicked, result.RTCKickError, result.RoomEvictError = s.evictCurrentRoom(ctx, command) - return result, nil + return result } // CreateManagerUserBlock 创建经理封禁记录并把目标用户状态置为 banned。 -// 国家和 active 状态在 service 层校验,保证任何 gateway/H5 入口都不能跨国家或重复封禁非 active 用户。 +// 目标范围由 App 策略与经理通用跨区开关共同决定,user-service 必须复验,不能只依赖 gateway 早拒绝。 func (s *Service) CreateManagerUserBlock(ctx context.Context, command ManagerUserBlockCommand) (ManagerUserBlock, UserStatusResult, error) { if s.userRepository == nil || s.moderationRepository == nil { return ManagerUserBlock{}, UserStatusResult{}, xerr.New(xerr.Unavailable, "user repository is not configured") @@ -180,8 +188,20 @@ func (s *Service) CreateManagerUserBlock(ctx context.Context, command ManagerUse if err != nil { return ManagerUserBlock{}, UserStatusResult{}, err } - if manager.Country == "" || target.Country == "" || !strings.EqualFold(manager.Country, target.Country) { - return ManagerUserBlock{}, UserStatusResult{}, xerr.New(xerr.PermissionDenied, "target user is outside manager country") + policy, err := hostservice.ResolveRoleScopePolicy(appcode.FromContext(ctx), hostservice.RoleScopeSceneManagerOperations) + if err != nil { + return ManagerUserBlock{}, UserStatusResult{}, err + } + effectiveScope := policy.BaseScope + if policy.RegionExpansionConfigurable { + crossRegionEnabled, err := s.moderationRepository.ManagerCrossRegionEnabled(ctx, command.ManagerUserID) + if err != nil { + return ManagerUserBlock{}, UserStatusResult{}, err + } + effectiveScope = policy.EffectiveScope(crossRegionEnabled) + } + if !managerModerationTargetAllowed(effectiveScope, manager, target) { + return ManagerUserBlock{}, UserStatusResult{}, xerr.New(xerr.PermissionDenied, "target user is outside manager scope") } if target.Status != userdomain.StatusActive { return ManagerUserBlock{}, UserStatusResult{}, xerr.New(xerr.InvalidArgument, "target user is not active") @@ -206,6 +226,19 @@ func (s *Service) CreateManagerUserBlock(ctx context.Context, command ManagerUse return block, status, nil } +func managerModerationTargetAllowed(scope hostservice.RoleScope, manager userdomain.User, target userdomain.User) bool { + switch scope { + case hostservice.RoleScopeGlobal: + return true + case hostservice.RoleScopeRegion: + return manager.RegionID > 0 && manager.RegionID == target.RegionID + case hostservice.RoleScopeCountry: + return manager.Country != "" && target.Country != "" && strings.EqualFold(manager.Country, target.Country) + default: + return false + } +} + func (s *Service) ListManagerUserBlocks(ctx context.Context, managerUserID int64, status string, pageSize int32) ([]ManagerUserBlock, error) { if s.moderationRepository == nil { return nil, xerr.New(xerr.Unavailable, "moderation repository is not configured") @@ -225,7 +258,7 @@ func (s *Service) ListManagerUserBlocks(ctx context.Context, managerUserID int64 func (s *Service) UnblockManagerUser(ctx context.Context, command ReleaseManagerUserBlockCommand) (ManagerUserBlock, UserStatusResult, error) { command.Status = ManagerUserBlockStatusReleased - return s.releaseManagerUserBlock(ctx, command, OperatorTypeAppUser) + return s.releaseManagerUserBlock(ctx, command) } func (s *Service) ExpireManagerUserBlocks(ctx context.Context, limit int32) (int, error) { @@ -240,28 +273,12 @@ func (s *Service) ExpireManagerUserBlocks(ctx context.Context, limit int32) (int if err != nil { return 0, err } - for _, release := range releases { - if !release.ShouldRestore { - // 释放封禁记录不等于一定恢复用户;仍有其他经理封禁或最后一次 ban 不是经理封禁时必须保持 banned。 - continue - } - if _, err := s.SetUserStatus(ctx, UserStatusCommand{ - AppCode: appcode.FromContext(ctx), - TargetUserID: release.Block.TargetUserID, - Status: userdomain.StatusActive, - OperatorType: OperatorTypeSystem, - OperatorUserID: 0, - Reason: "manager_center_block_expired:" + release.Block.BlockID, - RequestID: "manager_block_expire:" + release.Block.BlockID, - NowMs: nowMS, - }); err != nil { - return len(releases), err - } - } + // repository 已在每条释放事务内同时检查 admin/manager 两类 active 事实并按需恢复 users.status; + // service 不再二次 SetUserStatus,避免检查与写入之间插入新的封禁事实。 return len(releases), nil } -func (s *Service) releaseManagerUserBlock(ctx context.Context, command ReleaseManagerUserBlockCommand, operatorType string) (ManagerUserBlock, UserStatusResult, error) { +func (s *Service) releaseManagerUserBlock(ctx context.Context, command ReleaseManagerUserBlockCommand) (ManagerUserBlock, UserStatusResult, error) { if s.moderationRepository == nil { return ManagerUserBlock{}, UserStatusResult{}, xerr.New(xerr.Unavailable, "moderation repository is not configured") } @@ -282,22 +299,9 @@ func (s *Service) releaseManagerUserBlock(ctx context.Context, command ReleaseMa } if !release.ShouldRestore { // repository 已判断能否安全恢复;这里不能盲目 SetUserStatus(active),否则会覆盖后台封禁或并发经理封禁。 - return release.Block, UserStatusResult{}, nil + return release.Block, UserStatusResult{User: release.User, AccessTokenRevoked: true}, nil } - status, err := s.SetUserStatus(ctx, UserStatusCommand{ - AppCode: appcode.FromContext(ctx), - TargetUserID: release.Block.TargetUserID, - Status: userdomain.StatusActive, - OperatorType: operatorType, - OperatorUserID: command.ManagerUserID, - Reason: "manager_center_unblock:" + release.Block.BlockID, - RequestID: command.RequestID, - NowMs: command.NowMS, - }) - if err != nil { - return ManagerUserBlock{}, UserStatusResult{}, err - } - return release.Block, status, nil + return release.Block, UserStatusResult{User: release.User, AccessTokenRevoked: true}, nil } func (s *Service) writeSessionDenylist(ctx context.Context, command UserStatusCommand, sessionIDs []string, reason string) (bool, string) { diff --git a/services/user-service/internal/service/user/moderation_test.go b/services/user-service/internal/service/user/moderation_test.go index b2f6d540..e74281b2 100644 --- a/services/user-service/internal/service/user/moderation_test.go +++ b/services/user-service/internal/service/user/moderation_test.go @@ -8,6 +8,7 @@ import ( roomv1 "hyapp.local/api/proto/room/v1" "hyapp/pkg/appcode" + "hyapp/pkg/xerr" invitedomain "hyapp/services/user-service/internal/domain/invite" userdomain "hyapp/services/user-service/internal/domain/user" ) @@ -102,12 +103,184 @@ func TestSetUserStatusSurfacesAccessDenylistError(t *testing.T) { } } +func TestCreateManagerUserBlockUsesGeneralCrossRegionScope(t *testing.T) { + for _, testCase := range []struct { + name string + appCode string + crossRegion bool + targetRegion int64 + wantAllowed bool + }{ + {name: "default manager denied", appCode: "lalu", targetRegion: 1, wantAllowed: false}, + {name: "cross-region manager allowed inside region", appCode: "lalu", crossRegion: true, targetRegion: 1, wantAllowed: true}, + {name: "cross-region manager denied outside region", appCode: "lalu", crossRegion: true, targetRegion: 2, wantAllowed: false}, + {name: "huwaa globally allowed", appCode: "huwaa", targetRegion: 2, wantAllowed: true}, + } { + t.Run(testCase.name, func(t *testing.T) { + repository := &fakeModerationRepository{ + managerCrossRegionEnabled: testCase.crossRegion, + users: map[int64]userdomain.User{ + 100: {UserID: 100, Country: "US", RegionID: 1, Status: userdomain.StatusActive}, + 200: {UserID: 200, Country: "CA", RegionID: testCase.targetRegion, Status: userdomain.StatusActive}, + }, + } + svc := New(repository, WithModerationRepository(repository), WithClock(func() time.Time { + return time.UnixMilli(1_000).UTC() + })) + _, _, err := svc.CreateManagerUserBlock(appcode.WithContext(context.Background(), testCase.appCode), ManagerUserBlockCommand{ + CommandID: "manager-block", ManagerUserID: 100, TargetUserID: 200, BlockedUntilMS: 2_000, + }) + if testCase.wantAllowed && err != nil { + t.Fatalf("cross-region block should be allowed: %v", err) + } + if !testCase.wantAllowed && !xerr.IsCode(err, xerr.PermissionDenied) { + t.Fatalf("out-of-scope block should be denied: %v", err) + } + }) + } +} + +func TestAdminBanUserAcceptsPermanentBanAndRunsRealtimeSideEffectsOnce(t *testing.T) { + repository := &fakeModerationRepository{ + user: userdomain.User{AppCode: "lalu", UserID: 10001, Status: userdomain.StatusBanned}, + sessionIDs: []string{"sess_admin"}, + } + denylist := &fakeSessionDenylist{} + im := &fakeIMLoginKicker{} + room := &fakeRoomEvictor{resp: &roomv1.SystemEvictUserResponse{Result: &roomv1.CommandResult{Applied: true}}} + svc := New(repository, + WithModerationRepository(repository), + WithSessionDenylist(denylist, time.Minute), + WithIMLoginKicker(im), + WithRoomEvictor(room), + WithClock(func() time.Time { return time.UnixMilli(1_700_000_000_000).UTC() }), + ) + + ban, result, err := svc.AdminBanUser(appcode.WithContext(context.Background(), "lalu"), AdminUserBanCommand{ + CommandID: "admin-ban-command", + TargetUserID: 10001, + ExpiresAtMs: 0, + OperatorAdminID: 9001, + RequestID: "req-admin-ban", + NowMs: 1, + }) + if err != nil { + t.Fatalf("AdminBanUser failed: %v", err) + } + if ban.ExpiresAtMs != 0 || ban.Status != AdminUserBanStatusActive { + t.Fatalf("permanent admin ban mismatch: %+v", ban) + } + if len(denylist.sessions) != 1 || denylist.sessions[0] != "sess_admin" || im.userID != 10001 || room.req.GetTargetUserId() != 10001 { + t.Fatalf("admin ban side effects mismatch: denylist=%+v im=%d room=%+v", denylist.sessions, im.userID, room.req) + } + if !result.AccessTokenRevoked || result.RevokedSessionCount != 1 { + t.Fatalf("admin ban status result mismatch: %+v", result) + } + if repository.lastAdminBanCommand.NowMs != 1_700_000_000_000 { + t.Fatalf("user-service owner clock must override caller sent_at_ms: %+v", repository.lastAdminBanCommand) + } +} + +func TestAdminUnbanUserUsesOwnerClock(t *testing.T) { + repository := &fakeModerationRepository{adminForceUnbanResult: AdminForceUnbanResult{ + User: userdomain.User{AppCode: "lalu", UserID: 10001, Status: userdomain.StatusActive}, + ReleasedManagerBlockCount: 1, + StatusRestored: true, + }} + svc := New(repository, + WithModerationRepository(repository), + WithClock(func() time.Time { return time.UnixMilli(1_700_000_000_000).UTC() }), + ) + release, _, err := svc.AdminUnbanUser(context.Background(), AdminForceUnbanCommand{ + TargetUserID: 10001, OperatorAdminID: 9001, Reason: "manual", + RequestID: "unban-owner-clock", NowMs: 1, + }) + if err != nil { + t.Fatalf("AdminUnbanUser failed: %v", err) + } + if repository.lastAdminForceUnbanCommand.NowMs != 1_700_000_000_000 { + t.Fatalf("unban must use user-service owner clock: %+v", repository.lastAdminForceUnbanCommand) + } + if release.ReleasedManagerBlockCount != 1 || !release.StatusRestored { + t.Fatalf("force unban result mismatch: %+v", release) + } +} + +func TestAdminBanUserDoesNotRepeatSideEffectsForOverlappingBan(t *testing.T) { + repository := &fakeModerationRepository{ + user: userdomain.User{AppCode: "lalu", UserID: 10001, Status: userdomain.StatusBanned}, + adminBanPersistence: AdminUserBanPersistenceResult{ + Ban: AdminUserBan{BanID: "admin-ban-2", TargetUserID: 10001, Status: AdminUserBanStatusActive}, + Status: UserStatusPersistenceResult{User: userdomain.User{AppCode: "lalu", UserID: 10001, Status: userdomain.StatusBanned}}, + StatusChanged: false, + }, + } + im := &fakeIMLoginKicker{} + room := &fakeRoomEvictor{} + svc := New(repository, WithModerationRepository(repository), WithIMLoginKicker(im), WithRoomEvictor(room)) + + _, result, err := svc.AdminBanUser(appcode.WithContext(context.Background(), "lalu"), AdminUserBanCommand{ + CommandID: "overlap", + TargetUserID: 10001, + OperatorAdminID: 9002, + }) + if err != nil { + t.Fatalf("AdminBanUser overlap failed: %v", err) + } + if im.userID != 0 || room.req != nil { + t.Fatalf("overlapping ban must not repeat realtime side effects: im=%d room=%+v", im.userID, room.req) + } + if result.User.Status != userdomain.StatusBanned { + t.Fatalf("overlapping ban must preserve banned projection: %+v", result) + } +} + +func TestAdminBanUserRejectsExpiredDeadline(t *testing.T) { + repository := &fakeModerationRepository{} + svc := New(repository, WithModerationRepository(repository), WithClock(func() time.Time { + return time.UnixMilli(1_700_000_000_000).UTC() + })) + + _, _, err := svc.AdminBanUser(context.Background(), AdminUserBanCommand{ + CommandID: "expired", + TargetUserID: 10001, + OperatorAdminID: 9001, + ExpiresAtMs: 1_700_000_000_000, + }) + if xerr.CodeOf(err) != xerr.InvalidArgument { + t.Fatalf("expired deadline must be rejected, err=%v", err) + } +} + +func TestBatchGetUserAdminProfilesDeduplicatesAndLimitsInput(t *testing.T) { + repository := &fakeModerationRepository{adminProfiles: map[int64]userdomain.AdminProfile{ + 10001: {User: userdomain.User{UserID: 10001}}, + }} + svc := New(repository) + + profiles, err := svc.BatchGetUserAdminProfiles(context.Background(), []int64{10001, 10001}) + if err != nil || profiles[10001].User.UserID != 10001 { + t.Fatalf("batch admin profile failed: profiles=%+v err=%v", profiles, err) + } + tooMany := make([]int64, maxAdminProfileBatchSize+1) + for index := range tooMany { + tooMany[index] = int64(index + 1) + } + if _, err := svc.BatchGetUserAdminProfiles(context.Background(), tooMany); xerr.CodeOf(err) != xerr.InvalidArgument { + t.Fatalf("oversized admin profile batch must be rejected, err=%v", err) + } +} + type fakeModerationRepository struct { user userdomain.User + users map[int64]userdomain.User inviteReferrer userdomain.User sessionIDs []string countrySessionIDs []string lastCommand UserStatusCommand + lastAdminBanCommand AdminUserBanCommand + lastAdminBanReleaseCommand ReleaseAdminUserBanCommand + lastAdminForceUnbanCommand AdminForceUnbanCommand lastProfileCommand userdomain.ProfileUpdateCommand lastWithdrawAddressCommand userdomain.WithdrawAddressUpdateCommand lastCountryCommand userdomain.CountryChangeCommand @@ -116,6 +289,12 @@ type fakeModerationRepository struct { lastBindInvite userdomain.BindInviteReferrerCommand countryChangeCalls int lastReport userdomain.Report + adminProfiles map[int64]userdomain.AdminProfile + adminBanPersistence AdminUserBanPersistenceResult + adminBanRelease AdminUserBanRelease + adminForceUnbanResult AdminForceUnbanResult + adminBanExpirations []AdminUserBanRelease + managerCrossRegionEnabled bool } func (r *fakeModerationRepository) SetUserStatus(_ context.Context, command UserStatusCommand) (UserStatusPersistenceResult, error) { @@ -124,6 +303,10 @@ func (r *fakeModerationRepository) SetUserStatus(_ context.Context, command User return UserStatusPersistenceResult{User: r.user, RevokedSessionIDs: append([]string{}, r.sessionIDs...)}, nil } +func (r *fakeModerationRepository) ManagerCrossRegionEnabled(context.Context, int64) (bool, error) { + return r.managerCrossRegionEnabled, nil +} + func (r *fakeModerationRepository) GetInviteAttribution(context.Context, int64) (invitedomain.Attribution, error) { return invitedomain.Attribution{}, nil } @@ -155,7 +338,43 @@ func (r *fakeModerationRepository) ExpireManagerUserBlocks(context.Context, int6 return nil, nil } -func (r *fakeModerationRepository) GetUser(context.Context, int64) (userdomain.User, error) { +func (r *fakeModerationRepository) CreateAdminUserBan(_ context.Context, command AdminUserBanCommand) (AdminUserBanPersistenceResult, error) { + r.lastAdminBanCommand = command + if r.adminBanPersistence.Ban.BanID == "" { + r.adminBanPersistence = AdminUserBanPersistenceResult{ + Ban: AdminUserBan{ + BanID: "admin-ban-1", + CommandID: command.CommandID, + TargetUserID: command.TargetUserID, + ExpiresAtMs: command.ExpiresAtMs, + Status: AdminUserBanStatusActive, + OperatorAdminID: command.OperatorAdminID, + }, + Status: UserStatusPersistenceResult{User: r.user, RevokedSessionIDs: append([]string{}, r.sessionIDs...)}, + StatusChanged: true, + } + } + return r.adminBanPersistence, nil +} + +func (r *fakeModerationRepository) ReleaseAdminUserBan(_ context.Context, command ReleaseAdminUserBanCommand) (AdminUserBanRelease, error) { + r.lastAdminBanReleaseCommand = command + return r.adminBanRelease, nil +} + +func (r *fakeModerationRepository) ForceReleaseUserBans(_ context.Context, command AdminForceUnbanCommand) (AdminForceUnbanResult, error) { + r.lastAdminForceUnbanCommand = command + return r.adminForceUnbanResult, nil +} + +func (r *fakeModerationRepository) ExpireAdminUserBans(context.Context, int64, int32) ([]AdminUserBanRelease, error) { + return r.adminBanExpirations, nil +} + +func (r *fakeModerationRepository) GetUser(_ context.Context, userID int64) (userdomain.User, error) { + if user, ok := r.users[userID]; ok { + return user, nil + } return r.user, nil } @@ -216,6 +435,13 @@ func (r *fakeModerationRepository) BatchGetUsers(context.Context, []int64) (map[ return nil, nil } +func (r *fakeModerationRepository) BatchGetUserAdminProfiles(context.Context, []int64, int64) (map[int64]userdomain.AdminProfile, error) { + if r.adminProfiles == nil { + return map[int64]userdomain.AdminProfile{}, nil + } + return r.adminProfiles, nil +} + func (r *fakeModerationRepository) BatchGetRoomBasicUsers(context.Context, []int64) (map[int64]userdomain.RoomBasicUser, error) { return nil, nil } diff --git a/services/user-service/internal/service/user/service.go b/services/user-service/internal/service/user/service.go index 8d7a3b3e..45eafbdb 100644 --- a/services/user-service/internal/service/user/service.go +++ b/services/user-service/internal/service/user/service.go @@ -45,6 +45,8 @@ type UserRepository interface { BusinessUserLookup(ctx context.Context, keyword string, numericKeyword bool, pageSize int32) ([]userdomain.BusinessUserLookupItem, error) // BatchGetUsers 批量查询用户主状态,缺失用户不应返回占位对象。 BatchGetUsers(ctx context.Context, userIDs []int64) (map[int64]userdomain.User, error) + // BatchGetUserAdminProfiles 批量读取后台列表需要的用户、身份、登录和封禁 owner 事实。 + BatchGetUserAdminProfiles(ctx context.Context, userIDs []int64, nowMs int64) (map[int64]userdomain.AdminProfile, error) // BatchGetRoomBasicUsers 批量查询房间首屏最小用户资料,不触发完整用户主状态投影。 BatchGetRoomBasicUsers(ctx context.Context, userIDs []int64) (map[int64]userdomain.RoomBasicUser, error) // GetInviteAttribution 按被邀请用户读取邀请归因快照;无关系时返回 Found=false。 @@ -167,10 +169,16 @@ type DeviceRepository interface { // ModerationRepository 持有用户状态和认证 session 的同库事务。 type ModerationRepository interface { SetUserStatus(ctx context.Context, command UserStatusCommand) (UserStatusPersistenceResult, error) + ManagerCrossRegionEnabled(ctx context.Context, managerUserID int64) (bool, error) CreateManagerUserBlock(ctx context.Context, command ManagerUserBlockCommand, target userdomain.User) (ManagerUserBlock, error) ListManagerUserBlocks(ctx context.Context, managerUserID int64, status string, pageSize int32) ([]ManagerUserBlock, error) ReleaseManagerUserBlock(ctx context.Context, command ReleaseManagerUserBlockCommand) (ManagerUserBlockRelease, error) ExpireManagerUserBlocks(ctx context.Context, nowMS int64, limit int32) ([]ManagerUserBlockRelease, error) + CreateAdminUserBan(ctx context.Context, command AdminUserBanCommand) (AdminUserBanPersistenceResult, error) + ReleaseAdminUserBan(ctx context.Context, command ReleaseAdminUserBanCommand) (AdminUserBanRelease, error) + // ForceReleaseUserBans 是后台管理员的最高治理入口,必须在一个事务内释放管理员和经理两类封禁事实。 + ForceReleaseUserBans(ctx context.Context, command AdminForceUnbanCommand) (AdminForceUnbanResult, error) + ExpireAdminUserBans(ctx context.Context, nowMS int64, limit int32) ([]AdminUserBanRelease, error) } // SessionDenylist 写入 gateway access token 快速拒绝缓存。 diff --git a/services/user-service/internal/storage/mysql/auth/audit.go b/services/user-service/internal/storage/mysql/auth/audit.go index bdb9ec73..eb270627 100644 --- a/services/user-service/internal/storage/mysql/auth/audit.go +++ b/services/user-service/internal/storage/mysql/auth/audit.go @@ -12,9 +12,9 @@ import ( func (r *Repository) RecordLoginAudit(ctx context.Context, audit authdomain.LoginAudit) error { // 审计表不能包含密码、三方 credential 或 refresh token 原文。 _, err := r.db.ExecContext(ctx, ` - INSERT INTO login_audit (app_code, request_id, user_id, login_type, provider, channel, platform, result, failure_code, client_ip, ip_country_code, blocked, block_reason, user_agent, created_at_ms) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `, appcode.Normalize(audit.AppCode), audit.RequestID, nullableUserID(audit.UserID), audit.LoginType, shared.NullableString(audit.Provider), audit.Channel, audit.Platform, audit.Result, shared.NullableString(audit.FailureCode), shared.NullableString(audit.ClientIP), audit.IPCountryCode, audit.Blocked, audit.BlockReason, shared.NullableString(audit.UserAgent), audit.CreatedAtMs) + INSERT INTO login_audit (app_code, request_id, user_id, login_type, provider, channel, platform, app_version, build_number, result, failure_code, client_ip, ip_country_code, blocked, block_reason, user_agent, created_at_ms) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, appcode.Normalize(audit.AppCode), audit.RequestID, nullableUserID(audit.UserID), audit.LoginType, shared.NullableString(audit.Provider), audit.Channel, audit.Platform, audit.AppVersion, audit.BuildNumber, audit.Result, shared.NullableString(audit.FailureCode), shared.NullableString(audit.ClientIP), audit.IPCountryCode, audit.Blocked, audit.BlockReason, shared.NullableString(audit.UserAgent), audit.CreatedAtMs) return err } diff --git a/services/user-service/internal/storage/mysql/auth/sessions.go b/services/user-service/internal/storage/mysql/auth/sessions.go index d7b0e8fc..362cbfe5 100644 --- a/services/user-service/internal/storage/mysql/auth/sessions.go +++ b/services/user-service/internal/storage/mysql/auth/sessions.go @@ -63,6 +63,35 @@ func (r *Repository) FindActiveSessionByID(ctx context.Context, sessionID string return session, nil } +// FindLatestActiveSessionByUser 返回用户当前最新的未过期未吊销 session,供后台按用户重签 access token。 +// 与 FindActiveSessionByID 不同:这里没有可信 sid 输入,用“最近创建的 active session”近似客户端当前会话; +// refresh 轮换后旧 session 已吊销,最新 created_at_ms 即客户端实际持有的会话。 + +func (r *Repository) FindLatestActiveSessionByUser(ctx context.Context, userID int64, nowMs int64) (authdomain.Session, error) { + // idx_auth_sessions_user_id(app_code, user_id) 先收敛到单用户,再在少量行上过滤排序即可。 + var session authdomain.Session + var revokedAt sql.Null[int64] + err := r.db.QueryRowContext(ctx, ` + SELECT app_code, session_id, user_id, refresh_token_hash, device_id, expires_at_ms, last_heartbeat_at_ms, last_heartbeat_request_id, revoked_at_ms, revoked_reason, revoked_request_id, revoked_by, created_at_ms, updated_at_ms + FROM auth_sessions + WHERE app_code = ? AND user_id = ? AND revoked_at_ms IS NULL AND expires_at_ms > ? + ORDER BY created_at_ms DESC, session_id DESC + LIMIT 1 + `, appcode.FromContext(ctx), userID, nowMs).Scan(&session.AppCode, &session.SessionID, &session.UserID, &session.RefreshTokenHash, &session.DeviceID, &session.ExpiresAtMs, &session.LastHeartbeatAtMs, &session.LastHeartbeatRequestID, &revokedAt, &session.RevokedReason, &session.RevokedRequestID, &session.RevokedBy, &session.CreatedAtMs, &session.UpdatedAtMs) + if err == sql.ErrNoRows { + // “无活跃会话”是后台要明确展示的业务事实,用 NotFound 与其他存储错误区分。 + return authdomain.Session{}, xerr.New(xerr.NotFound, "active session not found") + } + if err != nil { + return authdomain.Session{}, err + } + if revokedAt.Valid { + session.RevokedAtMs = revokedAt.V + } + + return session, nil +} + // ReplaceSession 原子吊销旧 session 并创建新 session。 func (r *Repository) ReplaceSession(ctx context.Context, oldSessionID string, newSession authdomain.Session, revokedAtMs int64) error { diff --git a/services/user-service/internal/storage/mysql/auth/user_devices.go b/services/user-service/internal/storage/mysql/auth/user_devices.go new file mode 100644 index 00000000..7c14e212 --- /dev/null +++ b/services/user-service/internal/storage/mysql/auth/user_devices.go @@ -0,0 +1,45 @@ +package auth + +import ( + "context" + + "hyapp/pkg/appcode" + authdomain "hyapp/services/user-service/internal/domain/auth" +) + +// UpsertUserDevice 以 (app_code, user_id, device_id) 为主键合并设备档案。 +// 合并语义是“非空覆盖”:客户端可能只在部分请求携带设备头(例如 refresh 只带 device_id), +// 空值不能抹掉此前已经上报过的型号/厂商/系统版本;last_seen/updated 每次认证成功都必须前移, +// 让后台设备列表能按最近活跃排序。写入失败由调用方决定是否忽略,认证主链路不因它失败。 +func (r *Repository) UpsertUserDevice(ctx context.Context, device authdomain.UserDevice) error { + _, err := r.db.ExecContext(ctx, ` + INSERT INTO user_devices (app_code, user_id, device_id, platform, device_model, manufacturer, os_version, imei, app_version, build_number, first_seen_at_ms, last_seen_at_ms, created_at_ms, updated_at_ms) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + platform = IF(VALUES(platform) = '', platform, VALUES(platform)), + device_model = IF(VALUES(device_model) = '', device_model, VALUES(device_model)), + manufacturer = IF(VALUES(manufacturer) = '', manufacturer, VALUES(manufacturer)), + os_version = IF(VALUES(os_version) = '', os_version, VALUES(os_version)), + imei = IF(VALUES(imei) = '', imei, VALUES(imei)), + app_version = IF(VALUES(app_version) = '', app_version, VALUES(app_version)), + build_number = IF(VALUES(build_number) = '', build_number, VALUES(build_number)), + last_seen_at_ms = VALUES(last_seen_at_ms), + updated_at_ms = VALUES(updated_at_ms) + `, + appcode.Normalize(device.AppCode), + device.UserID, + device.DeviceID, + device.Platform, + device.DeviceModel, + device.Manufacturer, + device.OSVersion, + device.IMEI, + device.AppVersion, + device.BuildNumber, + device.FirstSeenAtMs, + device.LastSeenAtMs, + device.CreatedAtMs, + device.UpdatedAtMs, + ) + return err +} diff --git a/services/user-service/internal/storage/mysql/db.go b/services/user-service/internal/storage/mysql/db.go index 49c9a247..5f2550fc 100644 --- a/services/user-service/internal/storage/mysql/db.go +++ b/services/user-service/internal/storage/mysql/db.go @@ -45,6 +45,95 @@ func Open(ctx context.Context, dsn string) (*Repository, error) { return &Repository{db: db}, nil } +// Migrate 补齐允许服务启动时在线迁移的向后兼容字段和索引。 +func (r *Repository) Migrate(ctx context.Context) error { + if r == nil || r.db == nil { + return xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + // app_version/build_number 必须先于新版登录审计 INSERT 落地;逐列探测使旧库可滚动升级, + // 同时避免对已经执行 initdb 或离线 migration 的数据库重复 ALTER。 + if err := r.ensureColumn(ctx, "login_audit", "app_version", "app_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '本次认证客户端版本' AFTER platform"); err != nil { + return err + } + if err := r.ensureColumn(ctx, "login_audit", "build_number", "build_number VARCHAR(64) NOT NULL DEFAULT '' COMMENT '本次认证客户端构建号' AFTER app_version"); err != nil { + return err + } + // Dashboard 必须先按用户分区,再按 created_at_ms/id 取最近成功登录。校验完整列序而不只检查索引名, + // 防止历史环境中同名但列定义不完整的索引让查询退化成全表排序。 + return r.ensureIndexDefinition(ctx, + "login_audit", + "idx_login_audit_latest_success", + []string{"app_code", "user_id", "result", "blocked", "login_type", "created_at_ms", "id"}, + "ALTER TABLE login_audit ADD INDEX idx_login_audit_latest_success (app_code, user_id, result, blocked, login_type, created_at_ms, id)", + ) +} + +func (r *Repository) ensureColumn(ctx context.Context, tableName string, columnName string, columnDDL string) error { + var count int + if err := r.db.QueryRowContext(ctx, + `SELECT COUNT(*) + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?`, + tableName, columnName, + ).Scan(&count); err != nil { + return err + } + if count > 0 { + return nil + } + // tableName/columnDDL 只来自上方固定常量,不接受外部输入;这里不能用占位符绑定 DDL 标识符。 + _, err := r.db.ExecContext(ctx, `ALTER TABLE `+tableName+` ADD COLUMN `+columnDDL) + return err +} + +func (r *Repository) ensureIndexDefinition(ctx context.Context, tableName string, indexName string, columns []string, createDDL string) error { + rows, err := r.db.QueryContext(ctx, + `SELECT COLUMN_NAME + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND INDEX_NAME = ? + ORDER BY SEQ_IN_INDEX`, + tableName, indexName, + ) + if err != nil { + return err + } + defer rows.Close() + existing := make([]string, 0, len(columns)) + for rows.Next() { + var column string + if err := rows.Scan(&column); err != nil { + return err + } + existing = append(existing, column) + } + if err := rows.Err(); err != nil { + return err + } + if equalStringSlices(existing, columns) { + return nil + } + if len(existing) > 0 { + // 同名索引定义不一致时必须先移除,否则 ADD INDEX 会以 duplicate key name 失败。 + if _, err := r.db.ExecContext(ctx, `ALTER TABLE `+tableName+` DROP INDEX `+indexName); err != nil { + return err + } + } + _, err = r.db.ExecContext(ctx, createDDL) + return err +} + +func equalStringSlices(left []string, right []string) bool { + if len(left) != len(right) { + return false + } + for index := range left { + if left[index] != right[index] { + return false + } + } + return true +} + // Close 释放 MySQL 连接池。 func (r *Repository) Close() error { if r == nil || r.db == nil { diff --git a/services/user-service/internal/storage/mysql/db_test.go b/services/user-service/internal/storage/mysql/db_test.go new file mode 100644 index 00000000..b2b5402c --- /dev/null +++ b/services/user-service/internal/storage/mysql/db_test.go @@ -0,0 +1,42 @@ +package mysql + +import ( + "context" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestMigrateAddsLoginAuditClientVersionColumnsAndIndex(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + repository := &Repository{db: db} + + columnProbe := `(?s)SELECT COUNT\(\*\).*FROM information_schema\.COLUMNS.*TABLE_SCHEMA = DATABASE\(\).*TABLE_NAME = \?.*COLUMN_NAME = \?` + mock.ExpectQuery(columnProbe). + WithArgs("login_audit", "app_version"). + WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) + mock.ExpectExec(regexp.QuoteMeta("ALTER TABLE login_audit ADD COLUMN app_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '本次认证客户端版本' AFTER platform")). + WillReturnResult(sqlmock.NewResult(0, 0)) + mock.ExpectQuery(columnProbe). + WithArgs("login_audit", "build_number"). + WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) + mock.ExpectExec(regexp.QuoteMeta("ALTER TABLE login_audit ADD COLUMN build_number VARCHAR(64) NOT NULL DEFAULT '' COMMENT '本次认证客户端构建号' AFTER app_version")). + WillReturnResult(sqlmock.NewResult(0, 0)) + mock.ExpectQuery(`(?s)SELECT COLUMN_NAME.*FROM information_schema\.STATISTICS.*TABLE_SCHEMA = DATABASE\(\).*TABLE_NAME = \?.*INDEX_NAME = \?.*ORDER BY SEQ_IN_INDEX`). + WithArgs("login_audit", "idx_login_audit_latest_success"). + WillReturnRows(sqlmock.NewRows([]string{"COLUMN_NAME"})) + mock.ExpectExec(regexp.QuoteMeta("ALTER TABLE login_audit ADD INDEX idx_login_audit_latest_success (app_code, user_id, result, blocked, login_type, created_at_ms, id)")). + WillReturnResult(sqlmock.NewResult(0, 0)) + + if err := repository.Migrate(context.Background()); err != nil { + t.Fatalf("migrate login audit client version schema: %v", err) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("migration expectations mismatch: %v", err) + } +} diff --git a/services/user-service/internal/storage/mysql/host/applications.go b/services/user-service/internal/storage/mysql/host/applications.go index 44303066..f21118aa 100644 --- a/services/user-service/internal/storage/mysql/host/applications.go +++ b/services/user-service/internal/storage/mysql/host/applications.go @@ -28,7 +28,7 @@ func (r *Repository) ApplyToAgency(ctx context.Context, command hostservice.Appl } // 申请人此时可能还没有 host_profiles 行,因此以 users 行作为“按用户串行化”的主锁。 - // 区域也从用户表读取,保证申请创建时的区域是 user-service 的当前事实。 + // 区域从用户表读取,作为后续 Host/成员关系的当前投影;Huwaa 允许它与 Agency owner 区域不同,但不会迁移申请人区域。 userRegionID, err := r.userRegion(ctx, tx, command.UserID, "FOR UPDATE") if err != nil { return hostdomain.AgencyApplication{}, err @@ -46,7 +46,7 @@ func (r *Repository) ApplyToAgency(ctx context.Context, command hostservice.Appl if agency.Status != hostdomain.AgencyStatusActive || !agency.JoinEnabled { return hostdomain.AgencyApplication{}, xerr.New(xerr.Conflict, "agency is not joinable") } - if agency.RegionID != userRegionID { + if roleRegionMatchRequired(ctx) && agency.RegionID != userRegionID { return hostdomain.AgencyApplication{}, xerr.New(xerr.PermissionDenied, "agency region does not match user region") } // 第一阶段规则是一个 Host 同时只能归属一个有效 Agency; @@ -129,13 +129,13 @@ func (r *Repository) ReviewAgencyApplication(ctx context.Context, command hostse return hostdomain.ReviewAgencyApplicationResult{}, xerr.New(xerr.Conflict, "agency is not active") } - // 申请创建后申请人或 Agency 拥有者区域都可能被后台调整;通过前重新锁申请人 users 行, - // 并用 Agency 当前拥有者区域校验,申请表不再保存区域快照。 + // 申请创建后申请人或 Agency 拥有者区域都可能被后台调整;通过前重新锁申请人 users 行。 + // 其他 App 继续按 Agency 当前拥有者区域复验,Huwaa 只跳过匹配,不在申请表恢复区域快照或修改 users。 regionID, err := r.userRegion(ctx, tx, application.ApplicantUserID, "FOR UPDATE") if err != nil { return hostdomain.ReviewAgencyApplicationResult{}, err } - if regionID != agency.RegionID { + if roleRegionMatchRequired(ctx) && regionID != agency.RegionID { return hostdomain.ReviewAgencyApplicationResult{}, xerr.New(xerr.PermissionDenied, "application region no longer matches") } diff --git a/services/user-service/internal/storage/mysql/host/invitations.go b/services/user-service/internal/storage/mysql/host/invitations.go index 2dfe5be4..73a26ad1 100644 --- a/services/user-service/internal/storage/mysql/host/invitations.go +++ b/services/user-service/internal/storage/mysql/host/invitations.go @@ -14,7 +14,7 @@ import ( const userOutboxEventRoleInvitationCreated = "RoleInvitationCreated" -// InviteAgency 创建 Agency 角色邀请,校验 inviter BD 身份和目标用户区域。 +// InviteAgency 创建 Agency 角色邀请;除 Huwaa 外校验 inviter BD 和目标用户区域一致。 func (r *Repository) InviteAgency(ctx context.Context, command hostservice.InviteAgencyCommand) (hostdomain.RoleInvitation, error) { tx, err := r.db.BeginTx(ctx, nil) if err != nil { @@ -44,7 +44,7 @@ func (r *Repository) InviteAgency(ctx context.Context, command hostservice.Invit if err != nil { return hostdomain.RoleInvitation{}, err } - if targetRegionID != inviter.RegionID { + if roleRegionMatchRequired(ctx) && targetRegionID != inviter.RegionID { return hostdomain.RoleInvitation{}, xerr.New(xerr.RegionMismatch, "target region does not match inviter region") } // Agency owner 和 Host 成员归属是两套身份;目标已有 active Host 绑定时仍允许成为 Agency owner, @@ -125,12 +125,12 @@ func (r *Repository) InviteBD(ctx context.Context, command hostservice.InviteBDC if inviter.Status != hostdomain.BDStatusActive { return hostdomain.RoleInvitation{}, xerr.New(xerr.PermissionDenied, "inviter is not active bd leader") } - // BD 从属于负责人的区域,邀请创建时先锁目标用户当前区域。 + // 邀请创建时锁目标用户当前区域;其他 App 要求和 Leader 同区,Huwaa 允许跨区但 BD 投影仍来自目标 users 当前区域。 targetRegionID, err := r.userRegion(ctx, tx, command.TargetUserID, "FOR UPDATE") if err != nil { return hostdomain.RoleInvitation{}, err } - if targetRegionID != inviter.RegionID { + if roleRegionMatchRequired(ctx) && targetRegionID != inviter.RegionID { return hostdomain.RoleInvitation{}, xerr.New(xerr.RegionMismatch, "target region does not match leader region") } // 只有 active 普通 BD 才算已有权限;disabled 行可以重新发邀请, @@ -484,8 +484,8 @@ func (r *Repository) ProcessRoleInvitation(ctx context.Context, command hostserv } func (r *Repository) acceptAgencyInvitation(ctx context.Context, tx *sql.Tx, command hostservice.ProcessRoleInvitationCommand, invitation *hostdomain.RoleInvitation, result *hostdomain.ProcessRoleInvitationResult) (bool, bool, error) { - // 邀请可能放置一段时间后才被接受,所以目标用户和邀请 BD 都用 users 当前区域重新对齐。 - // 邀请表不再保存区域快照,旧库残留的 region_id 也不能参与是否可接受的业务判断。 + // 邀请可能放置一段时间后才被接受,所以目标用户和邀请 BD 都读取 users 当前区域。 + // 其他 App 用它们重新对齐;Huwaa 允许跨区,且邀请表仍不保存区域快照、不迁移目标用户。 targetRegionID, err := r.userRegion(ctx, tx, invitation.TargetUserID, "FOR UPDATE") if err != nil { return false, false, err @@ -494,7 +494,7 @@ func (r *Repository) acceptAgencyInvitation(ctx context.Context, tx *sql.Tx, com if err != nil { return false, false, err } - if targetRegionID != inviterRegionID { + if roleRegionMatchRequired(ctx) && targetRegionID != inviterRegionID { return false, false, xerr.New(xerr.RegionMismatch, "target region no longer matches invitation") } // 目标已有 Host 绑定时保留绑定,不再把 Host 当前归属切到自己新建的 Agency。 @@ -624,7 +624,7 @@ func (r *Repository) acceptHostInvitation(ctx context.Context, tx *sql.Tx, comma } func (r *Repository) acceptBDInvitation(ctx context.Context, tx *sql.Tx, command hostservice.ProcessRoleInvitationCommand, invitation *hostdomain.RoleInvitation, result *hostdomain.ProcessRoleInvitationResult) error { - // BD 邀请同样以目标用户和发起 Leader 的 users 当前区域为准;旧邀请快照不能继续授权跨区接受。 + // BD 邀请同样读取目标用户和发起 Leader 的 users 当前区域;其他 App 阻止漂移/跨区,Huwaa 只取消匹配校验。 targetRegionID, err := r.userRegion(ctx, tx, invitation.TargetUserID, "FOR UPDATE") if err != nil { return err @@ -633,7 +633,7 @@ func (r *Repository) acceptBDInvitation(ctx context.Context, tx *sql.Tx, command if err != nil { return err } - if targetRegionID != leaderRegionID { + if roleRegionMatchRequired(ctx) && targetRegionID != leaderRegionID { return xerr.New(xerr.RegionMismatch, "target region no longer matches invitation") } // BD 身份是独立角色事实,不依赖 host_profile;active BD 不能被覆盖, diff --git a/services/user-service/internal/storage/mysql/host/queries.go b/services/user-service/internal/storage/mysql/host/queries.go index 597282f6..657272ff 100644 --- a/services/user-service/internal/storage/mysql/host/queries.go +++ b/services/user-service/internal/storage/mysql/host/queries.go @@ -13,13 +13,13 @@ import ( hostservice "hyapp/services/user-service/internal/service/host" ) -// SearchAgencies 返回用户当前国家内可加入的有效 Agency。 +// SearchAgencies 返回当前 App 内可加入的有效 Agency;除 Huwaa 外继续按用户当前国家收敛候选集。 func (r *Repository) SearchAgencies(ctx context.Context, command hostservice.SearchAgenciesCommand) ([]hostdomain.Agency, error) { country, err := r.userCountry(ctx, r.db, command.UserID, "") if err != nil { return nil, err } - if country == "" { + if roleRegionMatchRequired(ctx) && country == "" { // 国家是 App 推荐列表的硬边界,缺失时不能退化成跨国或全局列表。 return nil, xerr.New(xerr.InvalidArgument, "user country is required") } @@ -47,12 +47,16 @@ func (r *Repository) SearchAgencies(ctx context.Context, command hostservice.Sea AND active_member.agency_id = a.agency_id AND active_member.status = ? WHERE a.app_code = ? - AND owner.country = ? AND owner.status = 'active' AND a.status = ? AND a.join_enabled = TRUE ` - args := []any{hostdomain.MembershipStatusActive, appcode.FromContext(ctx), country, hostdomain.AgencyStatusActive} + args := []any{hostdomain.MembershipStatusActive, appcode.FromContext(ctx), hostdomain.AgencyStatusActive} + if roleRegionMatchRequired(ctx) { + // 其他 App 保留历史国家边界;条件由服务端用户资料派生,客户端不能伪造国家扩大结果集。 + query += ` AND owner.country = ?` + args = append(args, country) + } if strings.TrimSpace(command.Keyword) != "" { keyword := strings.TrimSpace(command.Keyword) query += ` AND (a.name LIKE ? OR a.agency_id = ? OR owner.current_display_user_id = ?)` @@ -369,6 +373,8 @@ func (r *Repository) HasActiveManagerCapability(ctx context.Context, userID int6 columnSQL = "can_update_user_level = 1" case hostservice.CapabilityManagerAddBDLeader: columnSQL = "can_add_bd_leader = 1" + case hostservice.CapabilityManagerCrossRegion, hostservice.CapabilityManagerAddBDLeaderInRegion: + columnSQL = "can_manage_cross_region = 1" case hostservice.CapabilityManagerAddAdmin: columnSQL = "can_add_admin = 1" case hostservice.CapabilityManagerAddSuperadmin: diff --git a/services/user-service/internal/storage/mysql/host/region_policy.go b/services/user-service/internal/storage/mysql/host/region_policy.go new file mode 100644 index 00000000..f85ca420 --- /dev/null +++ b/services/user-service/internal/storage/mysql/host/region_policy.go @@ -0,0 +1,15 @@ +package host + +import ( + "context" + + "hyapp/pkg/appcode" + hostservice "hyapp/services/user-service/internal/service/host" +) + +// roleRegionMatchRequired 只控制 Agency 角色邀请、Host 主动申请和 BD 邀请的区域匹配。 +// App 差异由统一策略注册表解析;Agency 主动邀请 Host、币商列表和子币商关系不得复用该例外。 +func roleRegionMatchRequired(ctx context.Context) bool { + policy, err := hostservice.ResolveRoleScopePolicy(appcode.FromContext(ctx), hostservice.RoleScopeSceneOrganizationExpansion) + return err != nil || policy.BaseScope != hostservice.RoleScopeGlobal +} diff --git a/services/user-service/internal/storage/mysql/host/region_policy_test.go b/services/user-service/internal/storage/mysql/host/region_policy_test.go new file mode 100644 index 00000000..f1ea8c96 --- /dev/null +++ b/services/user-service/internal/storage/mysql/host/region_policy_test.go @@ -0,0 +1,28 @@ +package host + +import ( + "context" + "testing" + + "hyapp/pkg/appcode" +) + +func TestRoleRegionMatchRequiredOnlyExemptsNormalizedHuwaa(t *testing.T) { + for _, testCase := range []struct { + name string + app string + required bool + }{ + {name: "default_lalu", app: "", required: true}, + {name: "explicit_lalu", app: "lalu", required: true}, + {name: "fami", app: "fami", required: true}, + {name: "normalized_huwaa", app: " HUWAa ", required: false}, + } { + t.Run(testCase.name, func(t *testing.T) { + ctx := appcode.WithContext(context.Background(), testCase.app) + if got := roleRegionMatchRequired(ctx); got != testCase.required { + t.Fatalf("roleRegionMatchRequired(%q) = %v, want %v", testCase.app, got, testCase.required) + } + }) + } +} diff --git a/services/user-service/internal/storage/mysql/host/stats.go b/services/user-service/internal/storage/mysql/host/stats.go new file mode 100644 index 00000000..92759286 --- /dev/null +++ b/services/user-service/internal/storage/mysql/host/stats.go @@ -0,0 +1,144 @@ +package host + +import ( + "context" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + hostdomain "hyapp/services/user-service/internal/domain/host" +) + +const validMicDayThresholdMS = int64(time.Hour / time.Millisecond) + +// GetHostEngagementStats 聚合 user-service 自己拥有的事实表,不从当前 Agency 关系反推历史归属。 +func (r *Repository) GetHostEngagementStats(ctx context.Context, query hostdomain.EngagementStatsQuery) (hostdomain.EngagementStats, error) { + if r == nil || r.db == nil { + return hostdomain.EngagementStats{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + stats := hostdomain.EngagementStats{} + appCode := appcode.FromContext(ctx) + startDate := time.UnixMilli(query.StartAtMS).UTC().Format("2006-01-02") + endDate := time.UnixMilli(query.EndAtMS - 1).UTC().Format("2006-01-02") + + if err := r.db.QueryRowContext(ctx, ` + SELECT COALESCE(SUM(mic_online_ms), 0), + COALESCE(SUM(CASE WHEN mic_online_ms >= ? THEN 1 ELSE 0 END), 0) + FROM user_mic_daily_stats + WHERE app_code = ? AND user_id = ? AND stat_date BETWEEN ? AND ?`, + validMicDayThresholdMS, appCode, query.HostUserID, startDate, endDate, + ).Scan(&stats.ValidMicDurationMS, &stats.ValidMicDays); err != nil { + return hostdomain.EngagementStats{}, err + } + + if err := r.db.QueryRowContext(ctx, ` + SELECT COUNT(DISTINCT sender_user_id) + FROM user_private_message_events + WHERE app_code = ? AND target_user_id = ? AND occurred_at_ms >= ? AND occurred_at_ms < ?`, + appCode, query.HostUserID, query.StartAtMS, query.EndAtMS, + ).Scan(&stats.PrivateMessageSenders); err != nil { + return hostdomain.EngagementStats{}, err + } + + if err := r.db.QueryRowContext(ctx, ` + SELECT COALESCE(SUM(delta), 0) + FROM user_follow_events + WHERE app_code = ? AND followee_user_id = ? AND occurred_at_ms >= ? AND occurred_at_ms < ?`, + appCode, query.HostUserID, query.StartAtMS, query.EndAtMS, + ).Scan(&stats.NewFollowers); err != nil { + return hostdomain.EngagementStats{}, err + } + + onlineMS, err := r.hostOnlineDuration(ctx, query) + if err != nil { + return hostdomain.EngagementStats{}, err + } + stats.OnlineDurationMS = onlineMS + return stats, nil +} + +// hostOnlineDuration 按每天“最早会话开始到最后一次心跳”累计;多设备/多 session 不重复相加。 +func (r *Repository) hostOnlineDuration(ctx context.Context, query hostdomain.EngagementStatsQuery) (int64, error) { + rows, err := r.db.QueryContext(ctx, ` + SELECT created_at_ms, last_heartbeat_at_ms + FROM auth_sessions + WHERE app_code = ? AND user_id = ? + AND last_heartbeat_at_ms >= ? AND created_at_ms < ?`, + appcode.FromContext(ctx), query.HostUserID, query.StartAtMS, query.EndAtMS, + ) + if err != nil { + return 0, err + } + defer rows.Close() + + type daySpan struct{ first, last int64 } + spans := map[string]daySpan{} + for rows.Next() { + var startedAtMS, heartbeatAtMS int64 + if err := rows.Scan(&startedAtMS, &heartbeatAtMS); err != nil { + return 0, err + } + start := maxInt64(startedAtMS, query.StartAtMS) + end := minInt64(heartbeatAtMS, query.EndAtMS-1) + if end < start { + continue + } + for day := utcDayStart(start); day <= utcDayStart(end); day += int64(24 * time.Hour / time.Millisecond) { + segmentStart := maxInt64(start, day) + segmentEnd := minInt64(end, day+int64(24*time.Hour/time.Millisecond)-1) + key := time.UnixMilli(day).UTC().Format("2006-01-02") + span, exists := spans[key] + if !exists || segmentStart < span.first { + span.first = segmentStart + } + if !exists || segmentEnd > span.last { + span.last = segmentEnd + } + spans[key] = span + } + } + if err := rows.Err(); err != nil { + return 0, err + } + var total int64 + for _, span := range spans { + if span.last >= span.first { + total += span.last - span.first + } + } + return total, nil +} + +// RecordPrivateMessageEvent 用 app_code + event_id 幂等,不保存消息正文或扩展负载。 +func (r *Repository) RecordPrivateMessageEvent(ctx context.Context, event hostdomain.PrivateMessageEvent) (bool, error) { + result, err := r.db.ExecContext(ctx, ` + INSERT IGNORE INTO user_private_message_events ( + app_code, event_id, sender_user_id, target_user_id, occurred_at_ms, created_at_ms + ) VALUES (?, ?, ?, ?, ?, ?)`, + appcode.FromContext(ctx), event.EventID, event.SenderUserID, event.TargetUserID, event.OccurredAtMS, time.Now().UTC().UnixMilli(), + ) + if err != nil { + return false, err + } + rows, err := result.RowsAffected() + return rows > 0, err +} + +func utcDayStart(value int64) int64 { + t := time.UnixMilli(value).UTC() + return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC).UnixMilli() +} + +func minInt64(left, right int64) int64 { + if left < right { + return left + } + return right +} + +func maxInt64(left, right int64) int64 { + if left > right { + return left + } + return right +} diff --git a/services/user-service/internal/storage/mysql/host/stats_test.go b/services/user-service/internal/storage/mysql/host/stats_test.go new file mode 100644 index 00000000..bffa69e1 --- /dev/null +++ b/services/user-service/internal/storage/mysql/host/stats_test.go @@ -0,0 +1,55 @@ +package host + +import ( + "context" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "hyapp/pkg/appcode" + hostdomain "hyapp/services/user-service/internal/domain/host" +) + +func TestGetHostEngagementStatsAggregatesOwnedFacts(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + + mock.ExpectQuery("FROM user_mic_daily_stats"). + WithArgs(validMicDayThresholdMS, "fami", int64(42), "2026-07-06", "2026-07-12"). + WillReturnRows(sqlmock.NewRows([]string{"mic_ms", "valid_days"}).AddRow(int64(5400000), int64(1))) + mock.ExpectQuery("FROM user_private_message_events"). + WithArgs("fami", int64(42), int64(1783296000000), int64(1783900800000)). + WillReturnRows(sqlmock.NewRows([]string{"senders"}).AddRow(int64(6))) + mock.ExpectQuery("FROM user_follow_events"). + WithArgs("fami", int64(42), int64(1783296000000), int64(1783900800000)). + WillReturnRows(sqlmock.NewRows([]string{"followers"}).AddRow(int64(4))) + mock.ExpectQuery(regexp.QuoteMeta(` + SELECT created_at_ms, last_heartbeat_at_ms + FROM auth_sessions + WHERE app_code = ? AND user_id = ? + AND last_heartbeat_at_ms >= ? AND created_at_ms < ?`)). + WithArgs("fami", int64(42), int64(1783296000000), int64(1783900800000)). + WillReturnRows(sqlmock.NewRows([]string{"created_at_ms", "last_heartbeat_at_ms"}). + AddRow(int64(1783299600000), int64(1783303200000)). + AddRow(int64(1783301400000), int64(1783306800000))) + + repo := New(db) + stats, err := repo.GetHostEngagementStats(appcode.WithContext(context.Background(), "fami"), hostdomain.EngagementStatsQuery{ + HostUserID: 42, + StartAtMS: 1783296000000, + EndAtMS: 1783900800000, + }) + if err != nil { + t.Fatalf("GetHostEngagementStats failed: %v", err) + } + // 两个重叠 session 按当天最早开始到最晚心跳合并,不能相加成重复在线时长。 + if stats.OnlineDurationMS != 7200000 || stats.ValidMicDurationMS != 5400000 || stats.ValidMicDays != 1 || stats.PrivateMessageSenders != 6 || stats.NewFollowers != 4 { + t.Fatalf("unexpected engagement stats: %+v", stats) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("unmet SQL expectations: %v", err) + } +} diff --git a/services/user-service/internal/storage/mysql/identity/pretty_test.go b/services/user-service/internal/storage/mysql/identity/pretty_test.go index 2e8c35ca..67c4ab94 100644 --- a/services/user-service/internal/storage/mysql/identity/pretty_test.go +++ b/services/user-service/internal/storage/mysql/identity/pretty_test.go @@ -80,7 +80,7 @@ func TestGeneratePrettyDisplayIDCandidatesWithConfig(t *testing.T) { if err != nil { t.Fatalf("generate custom candidates failed: %v", err) } - if strings.Join(custom, ",") != "VIP2026,ملك123" { + if strings.Join(custom, ",") != "VIP2026,ملك123,VIP!" { t.Fatalf("custom candidates = %#v", custom) } } diff --git a/services/user-service/internal/storage/mysql/repositories.go b/services/user-service/internal/storage/mysql/repositories.go index 4a68b9e8..d8c20ccb 100644 --- a/services/user-service/internal/storage/mysql/repositories.go +++ b/services/user-service/internal/storage/mysql/repositories.go @@ -10,6 +10,7 @@ import ( invitestorage "hyapp/services/user-service/internal/storage/mysql/invite" mictimestorage "hyapp/services/user-service/internal/storage/mysql/mictime" regionstorage "hyapp/services/user-service/internal/storage/mysql/region" + roomtimestorage "hyapp/services/user-service/internal/storage/mysql/roomtime" userstorage "hyapp/services/user-service/internal/storage/mysql/user" ) @@ -85,6 +86,14 @@ func (r *Repository) MicTimeRepository() *mictimestorage.Repository { return mictimestorage.New(r.db) } +// 基于 user-service 共享 MySQL 连接池返回用户在房时长存储对象。 +func (r *Repository) RoomTimeRepository() *roomtimestorage.Repository { + if r == nil { + return nil + } + return roomtimestorage.New(r.db) +} + // 基于 user-service 共享 MySQL 连接池返回 CP/兄弟/姐妹关系存储对象。 func (r *Repository) CPRepository() *cpstorage.Repository { if r == nil { diff --git a/services/user-service/internal/storage/mysql/roomtime/repository.go b/services/user-service/internal/storage/mysql/roomtime/repository.go new file mode 100644 index 00000000..3bfb27bb --- /dev/null +++ b/services/user-service/internal/storage/mysql/roomtime/repository.go @@ -0,0 +1,543 @@ +// Package roomtime stores user-scoped in-room duration read models in MySQL. +package roomtime + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + mysqldriver "github.com/go-sql-driver/mysql" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + roomtimedomain "hyapp/services/user-service/internal/domain/roomtime" +) + +// Repository owns in-room session state and user duration aggregates inside user-service MySQL. +type Repository struct { + db *sql.DB +} + +const ( + defaultCompensationBatchSize = 200 + defaultOpenSessionCompensationMs = int64(24 * 60 * 60 * 1000) + userOutboxStatusPending = "pending" +) + +// New creates an in-room duration repository backed by the shared user-service connection pool. +func New(db *sql.DB) *Repository { + return &Repository{db: db} +} + +// ProcessRoomPresenceEvent idempotently applies one RoomUserJoined/RoomUserLeft/RoomUserKicked fact. +func (r *Repository) ProcessRoomPresenceEvent(ctx context.Context, event roomtimedomain.RoomPresenceEvent) (roomtimedomain.ApplyResult, error) { + if r == nil || r.db == nil { + return roomtimedomain.ApplyResult{}, xerr.New(xerr.Unavailable, "room time repository is not configured") + } + event = normalizeRoomPresenceEvent(event) + if event.EventID == "" || event.EventType == "" { + return roomtimedomain.ApplyResult{}, xerr.New(xerr.InvalidArgument, "room presence event identity is required") + } + if event.OccurredAtMs <= 0 { + event.OccurredAtMs = time.Now().UnixMilli() + } + + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return roomtimedomain.ApplyResult{}, err + } + defer tx.Rollback() + + if err := insertEventConsumption(ctx, tx, event); err != nil { + if isDuplicate(err) { + return roomtimedomain.ApplyResult{Skipped: true, Reason: "already_consumed"}, nil + } + return roomtimedomain.ApplyResult{}, err + } + + var result roomtimedomain.ApplyResult + switch { + case event.RoomID == "" || event.UserID <= 0: + result = roomtimedomain.ApplyResult{Skipped: true, Reason: "incomplete_presence_event"} + case event.EventType == roomtimedomain.RoomUserJoinedEventType: + result, err = applyJoined(ctx, tx, event) + case event.EventType == roomtimedomain.RoomUserLeftEventType: + result, err = applyLeft(ctx, tx, event, roomtimedomain.EndReasonLeft) + case event.EventType == roomtimedomain.RoomUserKickedEventType: + result, err = applyLeft(ctx, tx, event, roomtimedomain.EndReasonKicked) + default: + result = roomtimedomain.ApplyResult{Skipped: true, Reason: "unsupported_presence_event"} + } + if err != nil { + return roomtimedomain.ApplyResult{}, err + } + if result.Reason == "" && result.Skipped { + result.Reason = "skipped" + } + status := "consumed" + if result.Skipped { + status = "skipped" + } + if err := finishEventConsumption(ctx, tx, event, status, result.Reason); err != nil { + return roomtimedomain.ApplyResult{}, err + } + if err := tx.Commit(); err != nil { + return roomtimedomain.ApplyResult{}, err + } + if !result.Skipped { + result.Consumed = true + } + return result, nil +} + +// CompensateOpenSessions closes abnormal sessions whose authoritative leave event never reached user-service. +func (r *Repository) CompensateOpenSessions(ctx context.Context, options roomtimedomain.CompensationOptions) (roomtimedomain.CompensationResult, error) { + if r == nil || r.db == nil { + return roomtimedomain.CompensationResult{}, xerr.New(xerr.Unavailable, "room time repository is not configured") + } + options = normalizeCompensationOptions(options) + + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return roomtimedomain.CompensationResult{}, err + } + defer tx.Rollback() + + cutoff := options.NowMs - options.OpenSessionMaxAgeMs + app := appcode.FromContext(ctx) + rows, err := tx.QueryContext(ctx, ` + SELECT app_code, room_session_id, user_id, room_id, status, is_robot, joined_at_ms, last_room_version + FROM user_room_sessions + WHERE app_code = ? AND status = ? AND joined_at_ms <= ? + ORDER BY joined_at_ms ASC, room_session_id ASC + LIMIT ? + FOR UPDATE SKIP LOCKED + `, app, roomtimedomain.SessionStatusOpen, cutoff, options.BatchSize) + if err != nil { + return roomtimedomain.CompensationResult{}, err + } + + candidates := make([]roomSessionRow, 0, options.BatchSize) + for rows.Next() { + var session roomSessionRow + if err := rows.Scan(&session.AppCode, &session.RoomSessionID, &session.UserID, &session.RoomID, &session.Status, &session.IsRobot, &session.JoinedAtMs, &session.LastRoomVersion); err != nil { + _ = rows.Close() + return roomtimedomain.CompensationResult{}, err + } + candidates = append(candidates, session) + } + if err := rows.Close(); err != nil { + return roomtimedomain.CompensationResult{}, err + } + if err := rows.Err(); err != nil { + return roomtimedomain.CompensationResult{}, err + } + + var result roomtimedomain.CompensationResult + for _, session := range candidates { + if err := closeRoomSession(ctx, tx, session, closeRoomSessionInput{ + ClosedEventID: fmt.Sprintf("compensated:%s", session.RoomSessionID), + EndAtMs: minInt64(options.NowMs, session.JoinedAtMs+options.OpenSessionMaxAgeMs), + EndReason: roomtimedomain.CompensationReasonOpenSessionMaxWindow, + UpdatedAtMs: options.NowMs, + RoomVersion: session.LastRoomVersion, + }); err != nil { + return roomtimedomain.CompensationResult{}, err + } + result.ClosedCount++ + } + + // 孤儿 Left 只用于修正乱序到达;超过补偿窗口后已不可能再匹配任何进房事实,按批清理。 + deleted, err := tx.ExecContext(ctx, ` + DELETE FROM user_room_orphan_leaves + WHERE app_code = ? AND left_at_ms <= ? + LIMIT ? + `, app, cutoff, options.BatchSize) + if err != nil { + return roomtimedomain.CompensationResult{}, err + } + if affected, err := deleted.RowsAffected(); err == nil { + result.OrphanLeavesDeleted = int(affected) + } + + if err := tx.Commit(); err != nil { + return roomtimedomain.CompensationResult{}, err + } + return result, nil +} + +func applyJoined(ctx context.Context, tx *sql.Tx, event roomtimedomain.RoomPresenceEvent) (roomtimedomain.ApplyResult, error) { + openSessions, err := openSessionsForUpdate(ctx, tx, event.AppCode, event.UserID) + if err != nil { + return roomtimedomain.ApplyResult{}, err + } + for _, session := range openSessions { + // 一个用户同一时刻只保留一个在房会话:房间侧一个在线周期只发一条 Joined, + // 这里还有 open 会话说明它的 Left/Kicked 丢失或尚未到达,用新进房时刻闭合避免双计。 + reason := roomtimedomain.EndReasonSupersededByJoin + if session.RoomID == event.RoomID { + reason = roomtimedomain.EndReasonSupersededByRejoin + } + if err := closeRoomSession(ctx, tx, session, closeRoomSessionInput{ + ClosedEventID: event.EventID, + EndAtMs: event.OccurredAtMs, + EndReason: reason, + UpdatedAtMs: event.OccurredAtMs, + RoomVersion: event.RoomVersion, + }); err != nil { + return roomtimedomain.ApplyResult{}, err + } + } + + orphan, matched, err := matchOrphanLeave(ctx, tx, event) + if err != nil { + return roomtimedomain.ApplyResult{}, err + } + joinedSession := roomSessionRow{ + AppCode: event.AppCode, + RoomSessionID: event.EventID, + UserID: event.UserID, + RoomID: event.RoomID, + Status: roomtimedomain.SessionStatusOpen, + IsRobot: event.IsRobot, + JoinedAtMs: event.OccurredAtMs, + LastRoomVersion: event.RoomVersion, + } + if matched { + // Left 先到、Joined 后到:直接落一条已闭合会话,时长取两个事实的间隔。 + if err := insertRoomSession(ctx, tx, joinedSession, event, roomtimedomain.SessionStatusOpen); err != nil { + return roomtimedomain.ApplyResult{}, err + } + if err := closeRoomSession(ctx, tx, joinedSession, closeRoomSessionInput{ + ClosedEventID: orphan.EventID, + EndAtMs: orphan.LeftAtMs, + EndReason: roomtimedomain.EndReasonOrphanLeft, + UpdatedAtMs: event.OccurredAtMs, + RoomVersion: event.RoomVersion, + }); err != nil { + return roomtimedomain.ApplyResult{}, err + } + if _, err := tx.ExecContext(ctx, ` + DELETE FROM user_room_orphan_leaves WHERE app_code = ? AND event_id = ? + `, event.AppCode, orphan.EventID); err != nil { + return roomtimedomain.ApplyResult{}, err + } + return roomtimedomain.ApplyResult{Consumed: true, Opened: true, Closed: true, Reason: "matched_orphan_left"}, nil + } + + if err := insertRoomSession(ctx, tx, joinedSession, event, roomtimedomain.SessionStatusOpen); err != nil { + return roomtimedomain.ApplyResult{}, err + } + return roomtimedomain.ApplyResult{Consumed: true, Opened: true}, nil +} + +func applyLeft(ctx context.Context, tx *sql.Tx, event roomtimedomain.RoomPresenceEvent, endReason string) (roomtimedomain.ApplyResult, error) { + openSessions, err := openRoomSessionsForUpdate(ctx, tx, event.AppCode, event.RoomID, event.UserID) + if err != nil { + return roomtimedomain.ApplyResult{}, err + } + closed := 0 + sawNewerSession := false + for _, session := range openSessions { + if session.JoinedAtMs > event.OccurredAtMs { + // Left 早于当前 open 会话的进房时刻,说明它属于更早的在线周期;那个周期已被 supersede 闭合,直接丢弃。 + sawNewerSession = true + continue + } + if err := closeRoomSession(ctx, tx, session, closeRoomSessionInput{ + ClosedEventID: event.EventID, + EndAtMs: event.OccurredAtMs, + EndReason: endReason, + UpdatedAtMs: event.OccurredAtMs, + RoomVersion: event.RoomVersion, + }); err != nil { + return roomtimedomain.ApplyResult{}, err + } + closed++ + } + if closed > 0 { + return roomtimedomain.ApplyResult{Consumed: true, Closed: true}, nil + } + if sawNewerSession { + return roomtimedomain.ApplyResult{Skipped: true, Reason: "stale_left_event"}, nil + } + // 没有可闭合的会话:Left 可能先于 Joined 到达,暂存等 Joined 到达时直接生成闭合会话。 + // 踢人对已离房用户也会发 RoomUserKicked,这类孤儿记录不会匹配任何更晚的进房事实,由补偿任务清理。 + if _, err := tx.ExecContext(ctx, ` + INSERT IGNORE INTO user_room_orphan_leaves (app_code, event_id, room_id, user_id, left_at_ms, reason, created_at_ms) + VALUES (?, ?, ?, ?, ?, ?, ?) + `, event.AppCode, event.EventID, event.RoomID, event.UserID, event.OccurredAtMs, endReason, event.OccurredAtMs); err != nil { + return roomtimedomain.ApplyResult{}, err + } + return roomtimedomain.ApplyResult{Skipped: true, Reason: "left_before_join_buffered"}, nil +} + +type closeRoomSessionInput struct { + ClosedEventID string + EndAtMs int64 + EndReason string + UpdatedAtMs int64 + RoomVersion int64 +} + +func closeRoomSession(ctx context.Context, tx *sql.Tx, session roomSessionRow, input closeRoomSessionInput) error { + input.ClosedEventID = strings.TrimSpace(input.ClosedEventID) + if input.ClosedEventID == "" { + input.ClosedEventID = "closed:" + session.RoomSessionID + } + input.EndReason = strings.TrimSpace(input.EndReason) + if input.EndReason == "" { + input.EndReason = roomtimedomain.EndReasonLeft + } + if input.EndAtMs < session.JoinedAtMs { + input.EndAtMs = session.JoinedAtMs + } + if input.UpdatedAtMs <= 0 { + input.UpdatedAtMs = input.EndAtMs + } + roomOnlineMs := maxInt64(0, input.EndAtMs-session.JoinedAtMs) + + _, err := tx.ExecContext(ctx, ` + UPDATE user_room_sessions + SET status = ?, ended_at_ms = ?, end_reason = ?, room_online_ms = ?, + closed_event_id = ?, last_event_id = ?, last_room_version = GREATEST(last_room_version, ?), updated_at_ms = ? + WHERE app_code = ? AND room_session_id = ? AND status <> ? + `, roomtimedomain.SessionStatusEnded, input.EndAtMs, input.EndReason, roomOnlineMs, + input.ClosedEventID, input.ClosedEventID, input.RoomVersion, input.UpdatedAtMs, + session.AppCode, session.RoomSessionID, roomtimedomain.SessionStatusEnded) + if err != nil { + return err + } + if session.IsRobot { + // 机器人在房不产生活跃/时长统计事实;会话仍闭合留档,方便排查调度行为。 + return nil + } + return incrementDaily(ctx, tx, session, input) +} + +func incrementDaily(ctx context.Context, tx *sql.Tx, session roomSessionRow, input closeRoomSessionInput) error { + for date, part := range splitByUTCDay(session.JoinedAtMs, input.EndAtMs) { + _, err := tx.ExecContext(ctx, ` + INSERT INTO user_room_daily_stats ( + app_code, user_id, stat_date, room_online_ms, session_count, first_room_at_ms, last_room_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, 1, ?, ?, ?) + ON DUPLICATE KEY UPDATE + room_online_ms = room_online_ms + VALUES(room_online_ms), + session_count = session_count + 1, + first_room_at_ms = CASE WHEN first_room_at_ms = 0 OR first_room_at_ms > VALUES(first_room_at_ms) THEN VALUES(first_room_at_ms) ELSE first_room_at_ms END, + last_room_at_ms = GREATEST(last_room_at_ms, VALUES(last_room_at_ms)), + updated_at_ms = VALUES(updated_at_ms) + `, session.AppCode, session.UserID, date, part.DurationMs, part.FirstAtMs, part.LastAtMs, input.UpdatedAtMs) + if err != nil { + return err + } + if err := insertUserOutbox(ctx, tx, session.AppCode, + roomtimedomain.EventTypeUserRoomDailyStatsUpdated+":"+session.RoomSessionID+":"+date, + roomtimedomain.EventTypeUserRoomDailyStatsUpdated, "user_room_daily_stats", session.UserID, input.UpdatedAtMs, map[string]any{ + "app_code": session.AppCode, + "user_id": session.UserID, + "room_id": session.RoomID, + "stat_date": date, + "room_session_id": session.RoomSessionID, + "room_online_delta_ms": part.DurationMs, + "session_count_delta": 1, + "end_reason": input.EndReason, + "updated_at_ms": input.UpdatedAtMs, + }); err != nil { + return err + } + } + return nil +} + +type roomSessionRow struct { + AppCode string + RoomSessionID string + UserID int64 + RoomID string + Status string + IsRobot bool + JoinedAtMs int64 + LastRoomVersion int64 +} + +type orphanLeaveRow struct { + EventID string + LeftAtMs int64 +} + +func openSessionsForUpdate(ctx context.Context, tx *sql.Tx, appCode string, userID int64) ([]roomSessionRow, error) { + return scanSessions(tx.QueryContext(ctx, ` + SELECT app_code, room_session_id, user_id, room_id, status, is_robot, joined_at_ms, last_room_version + FROM user_room_sessions + WHERE app_code = ? AND user_id = ? AND status = ? + ORDER BY joined_at_ms ASC, room_session_id ASC + FOR UPDATE + `, appCode, userID, roomtimedomain.SessionStatusOpen)) +} + +func openRoomSessionsForUpdate(ctx context.Context, tx *sql.Tx, appCode string, roomID string, userID int64) ([]roomSessionRow, error) { + return scanSessions(tx.QueryContext(ctx, ` + SELECT app_code, room_session_id, user_id, room_id, status, is_robot, joined_at_ms, last_room_version + FROM user_room_sessions + WHERE app_code = ? AND user_id = ? AND room_id = ? AND status = ? + ORDER BY joined_at_ms ASC, room_session_id ASC + FOR UPDATE + `, appCode, userID, roomID, roomtimedomain.SessionStatusOpen)) +} + +func scanSessions(rows *sql.Rows, err error) ([]roomSessionRow, error) { + if err != nil { + return nil, err + } + defer rows.Close() + sessions := []roomSessionRow{} + for rows.Next() { + var session roomSessionRow + if err := rows.Scan(&session.AppCode, &session.RoomSessionID, &session.UserID, &session.RoomID, &session.Status, &session.IsRobot, &session.JoinedAtMs, &session.LastRoomVersion); err != nil { + return nil, err + } + sessions = append(sessions, session) + } + return sessions, rows.Err() +} + +func matchOrphanLeave(ctx context.Context, tx *sql.Tx, event roomtimedomain.RoomPresenceEvent) (orphanLeaveRow, bool, error) { + var orphan orphanLeaveRow + err := tx.QueryRowContext(ctx, ` + SELECT event_id, left_at_ms + FROM user_room_orphan_leaves + WHERE app_code = ? AND room_id = ? AND user_id = ? AND left_at_ms >= ? + ORDER BY left_at_ms ASC + LIMIT 1 + FOR UPDATE + `, event.AppCode, event.RoomID, event.UserID, event.OccurredAtMs).Scan(&orphan.EventID, &orphan.LeftAtMs) + if err == sql.ErrNoRows { + return orphanLeaveRow{}, false, nil + } + if err != nil { + return orphanLeaveRow{}, false, err + } + return orphan, true, nil +} + +func insertRoomSession(ctx context.Context, tx *sql.Tx, session roomSessionRow, event roomtimedomain.RoomPresenceEvent, status string) error { + _, err := tx.ExecContext(ctx, ` + INSERT INTO user_room_sessions ( + app_code, room_session_id, user_id, room_id, status, is_robot, + joined_at_ms, ended_at_ms, end_reason, room_online_ms, + opened_event_id, closed_event_id, last_event_id, last_room_version, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, NULL, '', 0, ?, '', ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + last_event_id = VALUES(last_event_id), + last_room_version = GREATEST(last_room_version, VALUES(last_room_version)), + updated_at_ms = VALUES(updated_at_ms) + `, session.AppCode, session.RoomSessionID, session.UserID, session.RoomID, status, session.IsRobot, + session.JoinedAtMs, event.EventID, event.EventID, event.RoomVersion, event.OccurredAtMs, event.OccurredAtMs) + return err +} + +func insertEventConsumption(ctx context.Context, tx *sql.Tx, event roomtimedomain.RoomPresenceEvent) error { + _, err := tx.ExecContext(ctx, ` + INSERT INTO user_room_event_consumption ( + app_code, event_id, event_type, room_id, user_id, status, skip_reason, + created_at_ms, consumed_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, 'processing', '', ?, NULL, ?) + `, event.AppCode, event.EventID, event.EventType, event.RoomID, event.UserID, event.OccurredAtMs, event.OccurredAtMs) + return err +} + +func finishEventConsumption(ctx context.Context, tx *sql.Tx, event roomtimedomain.RoomPresenceEvent, status string, reason string) error { + _, err := tx.ExecContext(ctx, ` + UPDATE user_room_event_consumption + SET status = ?, skip_reason = ?, consumed_at_ms = ?, updated_at_ms = ? + WHERE app_code = ? AND event_id = ? + `, status, strings.TrimSpace(reason), event.OccurredAtMs, event.OccurredAtMs, event.AppCode, event.EventID) + return err +} + +func insertUserOutbox(ctx context.Context, tx *sql.Tx, appCode string, eventID string, eventType string, aggregateType string, aggregateID int64, createdAtMs int64, payload map[string]any) error { + payloadBytes, err := json.Marshal(payload) + if err != nil { + return err + } + _, err = tx.ExecContext(ctx, ` + INSERT IGNORE INTO user_outbox (app_code, event_id, event_type, aggregate_type, aggregate_id, status, payload_json, created_at_ms, updated_at_ms) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + `, appcode.Normalize(appCode), eventID, eventType, aggregateType, aggregateID, userOutboxStatusPending, string(payloadBytes), createdAtMs, createdAtMs) + return err +} + +func normalizeRoomPresenceEvent(event roomtimedomain.RoomPresenceEvent) roomtimedomain.RoomPresenceEvent { + event.AppCode = appcode.Normalize(event.AppCode) + event.EventID = strings.TrimSpace(event.EventID) + event.EventType = strings.TrimSpace(event.EventType) + event.RoomID = strings.TrimSpace(event.RoomID) + return event +} + +func normalizeCompensationOptions(options roomtimedomain.CompensationOptions) roomtimedomain.CompensationOptions { + if options.NowMs <= 0 { + options.NowMs = time.Now().UnixMilli() + } + if options.BatchSize <= 0 { + options.BatchSize = defaultCompensationBatchSize + } + if options.OpenSessionMaxAgeMs <= 0 { + options.OpenSessionMaxAgeMs = defaultOpenSessionCompensationMs + } + return options +} + +type dayDuration struct { + DurationMs int64 + FirstAtMs int64 + LastAtMs int64 +} + +func splitByUTCDay(startMs int64, endMs int64) map[string]dayDuration { + parts := map[string]dayDuration{} + if endMs <= startMs { + return parts + } + for cursor := startMs; cursor < endMs; { + cursorTime := time.UnixMilli(cursor).UTC() + dayStart := time.Date(cursorTime.Year(), cursorTime.Month(), cursorTime.Day(), 0, 0, 0, 0, time.UTC) + nextDayMs := dayStart.AddDate(0, 0, 1).UnixMilli() + segmentEnd := minInt64(endMs, nextDayMs) + date := dayStart.Format("2006-01-02") + part := parts[date] + if part.FirstAtMs == 0 || cursor < part.FirstAtMs { + part.FirstAtMs = cursor + } + if segmentEnd > part.LastAtMs { + part.LastAtMs = segmentEnd + } + part.DurationMs += segmentEnd - cursor + parts[date] = part + cursor = segmentEnd + } + return parts +} + +func isDuplicate(err error) bool { + var mysqlErr *mysqldriver.MySQLError + return errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 +} + +func minInt64(a int64, b int64) int64 { + if a < b { + return a + } + return b +} + +func maxInt64(a int64, b int64) int64 { + if a > b { + return a + } + return b +} diff --git a/services/user-service/internal/storage/mysql/roomtime/repository_test.go b/services/user-service/internal/storage/mysql/roomtime/repository_test.go new file mode 100644 index 00000000..94081d22 --- /dev/null +++ b/services/user-service/internal/storage/mysql/roomtime/repository_test.go @@ -0,0 +1,224 @@ +package roomtime_test + +import ( + "context" + "database/sql" + "testing" + "time" + + roomtimedomain "hyapp/services/user-service/internal/domain/roomtime" + "hyapp/services/user-service/internal/testutil/mysqltest" +) + +func TestProcessJoinedAndLeftAggregatesDailyAndDeduplicates(t *testing.T) { + wrapped := mysqltest.NewRepository(t) + repo := wrapped.Repository.RoomTimeRepository() + ctx := context.Background() + base := time.Date(2026, 7, 1, 10, 0, 0, 0, time.UTC).UnixMilli() + + mustApply(t, repo, presenceEvent("evt-join", roomtimedomain.RoomUserJoinedEventType, "room-a", 20001, base)) + mustApply(t, repo, presenceEvent("evt-left", roomtimedomain.RoomUserLeftEventType, "room-a", 20001, base+11*60_000)) + + assertDailyStats(t, wrapped.RawDB(), 20001, "2026-07-01", 11*60_000, 1) + assertSessionEnded(t, wrapped.RawDB(), "evt-join", roomtimedomain.EndReasonLeft) + assertUserOutboxEvent(t, wrapped.RawDB(), "UserRoomDailyStatsUpdated:evt-join:2026-07-01", 1) + + duplicate, err := repo.ProcessRoomPresenceEvent(ctx, presenceEvent("evt-left", roomtimedomain.RoomUserLeftEventType, "room-a", 20001, base+11*60_000)) + if err != nil || !duplicate.Skipped || duplicate.Reason != "already_consumed" { + t.Fatalf("duplicate left should skip: result=%+v err=%v", duplicate, err) + } + assertDailyStats(t, wrapped.RawDB(), 20001, "2026-07-01", 11*60_000, 1) +} + +func TestProcessKickedClosesOpenSession(t *testing.T) { + wrapped := mysqltest.NewRepository(t) + repo := wrapped.Repository.RoomTimeRepository() + base := time.Date(2026, 7, 1, 12, 0, 0, 0, time.UTC).UnixMilli() + + mustApply(t, repo, presenceEvent("evt-kick-join", roomtimedomain.RoomUserJoinedEventType, "room-a", 20002, base)) + mustApply(t, repo, presenceEvent("evt-kick", roomtimedomain.RoomUserKickedEventType, "room-a", 20002, base+5*60_000)) + + assertSessionEnded(t, wrapped.RawDB(), "evt-kick-join", roomtimedomain.EndReasonKicked) + assertDailyStats(t, wrapped.RawDB(), 20002, "2026-07-01", 5*60_000, 1) +} + +func TestLeftBeforeJoinedBuffersOrphanAndSettlesOnJoin(t *testing.T) { + wrapped := mysqltest.NewRepository(t) + repo := wrapped.Repository.RoomTimeRepository() + ctx := context.Background() + base := time.Date(2026, 7, 1, 14, 0, 0, 0, time.UTC).UnixMilli() + + // Left 先到:没有 open 会话,暂存为孤儿离房。 + result, err := repo.ProcessRoomPresenceEvent(ctx, presenceEvent("evt-ooo-left", roomtimedomain.RoomUserLeftEventType, "room-a", 20003, base+7*60_000)) + if err != nil || !result.Skipped || result.Reason != "left_before_join_buffered" { + t.Fatalf("orphan left should buffer: result=%+v err=%v", result, err) + } + + // Joined 后到:直接生成闭合会话,时长取两个事实的间隔。 + joined, err := repo.ProcessRoomPresenceEvent(ctx, presenceEvent("evt-ooo-join", roomtimedomain.RoomUserJoinedEventType, "room-a", 20003, base)) + if err != nil || !joined.Closed || joined.Reason != "matched_orphan_left" { + t.Fatalf("joined should settle orphan left: result=%+v err=%v", joined, err) + } + assertSessionEnded(t, wrapped.RawDB(), "evt-ooo-join", roomtimedomain.EndReasonOrphanLeft) + assertDailyStats(t, wrapped.RawDB(), 20003, "2026-07-01", 7*60_000, 1) + + var orphans int + if err := wrapped.RawDB().QueryRowContext(ctx, ` + SELECT COUNT(*) FROM user_room_orphan_leaves WHERE app_code = 'lalu' AND user_id = 20003 + `).Scan(&orphans); err != nil { + t.Fatalf("query orphan leaves failed: %v", err) + } + if orphans != 0 { + t.Fatalf("matched orphan leave should be deleted, got %d rows", orphans) + } +} + +func TestJoinSupersedesLingeringOpenSessionInOtherRoom(t *testing.T) { + wrapped := mysqltest.NewRepository(t) + repo := wrapped.Repository.RoomTimeRepository() + base := time.Date(2026, 7, 1, 16, 0, 0, 0, time.UTC).UnixMilli() + + mustApply(t, repo, presenceEvent("evt-super-join-a", roomtimedomain.RoomUserJoinedEventType, "room-a", 20004, base)) + // room-a 的 Left 丢失,用户直接出现在 room-b:旧会话按新进房时刻闭合,避免双计。 + mustApply(t, repo, presenceEvent("evt-super-join-b", roomtimedomain.RoomUserJoinedEventType, "room-b", 20004, base+3*60_000)) + + assertSessionEnded(t, wrapped.RawDB(), "evt-super-join-a", roomtimedomain.EndReasonSupersededByJoin) + assertDailyStats(t, wrapped.RawDB(), 20004, "2026-07-01", 3*60_000, 1) + + mustApply(t, repo, presenceEvent("evt-super-left-b", roomtimedomain.RoomUserLeftEventType, "room-b", 20004, base+10*60_000)) + assertDailyStats(t, wrapped.RawDB(), 20004, "2026-07-01", 10*60_000, 2) +} + +func TestRobotSessionsCloseWithoutStatsFacts(t *testing.T) { + wrapped := mysqltest.NewRepository(t) + repo := wrapped.Repository.RoomTimeRepository() + ctx := context.Background() + base := time.Date(2026, 7, 1, 18, 0, 0, 0, time.UTC).UnixMilli() + + robotJoin := presenceEvent("evt-robot-join", roomtimedomain.RoomUserJoinedEventType, "room-a", 20005, base) + robotJoin.IsRobot = true + mustApply(t, repo, robotJoin) + mustApply(t, repo, presenceEvent("evt-robot-left", roomtimedomain.RoomUserLeftEventType, "room-a", 20005, base+9*60_000)) + + assertSessionEnded(t, wrapped.RawDB(), "evt-robot-join", roomtimedomain.EndReasonLeft) + var rows int + if err := wrapped.RawDB().QueryRowContext(ctx, ` + SELECT COUNT(*) FROM user_room_daily_stats WHERE app_code = 'lalu' AND user_id = 20005 + `).Scan(&rows); err != nil { + t.Fatalf("query daily stats failed: %v", err) + } + if rows != 0 { + t.Fatalf("robot session must not produce daily stats, got %d rows", rows) + } + assertUserOutboxEvent(t, wrapped.RawDB(), "UserRoomDailyStatsUpdated:evt-robot-join:2026-07-01", 0) +} + +func TestCrossDaySessionSplitsByUTCDay(t *testing.T) { + wrapped := mysqltest.NewRepository(t) + repo := wrapped.Repository.RoomTimeRepository() + base := time.Date(2026, 7, 1, 23, 50, 0, 0, time.UTC).UnixMilli() + + mustApply(t, repo, presenceEvent("evt-cross-join", roomtimedomain.RoomUserJoinedEventType, "room-a", 20006, base)) + mustApply(t, repo, presenceEvent("evt-cross-left", roomtimedomain.RoomUserLeftEventType, "room-a", 20006, base+30*60_000)) + + assertDailyStats(t, wrapped.RawDB(), 20006, "2026-07-01", 10*60_000, 1) + assertDailyStats(t, wrapped.RawDB(), 20006, "2026-07-02", 20*60_000, 1) + assertUserOutboxEvent(t, wrapped.RawDB(), "UserRoomDailyStatsUpdated:evt-cross-join:2026-07-01", 1) + assertUserOutboxEvent(t, wrapped.RawDB(), "UserRoomDailyStatsUpdated:evt-cross-join:2026-07-02", 1) +} + +func TestCompensateOpenSessionsClosesStaleSessionsAndPrunesOrphans(t *testing.T) { + wrapped := mysqltest.NewRepository(t) + repo := wrapped.Repository.RoomTimeRepository() + ctx := context.Background() + base := time.Date(2026, 7, 1, 8, 0, 0, 0, time.UTC).UnixMilli() + + mustApply(t, repo, presenceEvent("evt-comp-join", roomtimedomain.RoomUserJoinedEventType, "room-a", 20007, base)) + if _, err := repo.ProcessRoomPresenceEvent(ctx, presenceEvent("evt-comp-orphan", roomtimedomain.RoomUserLeftEventType, "room-b", 20008, base)); err != nil { + t.Fatalf("buffer orphan left failed: %v", err) + } + + result, err := repo.CompensateOpenSessions(ctx, roomtimedomain.CompensationOptions{ + NowMs: base + 30*60*60*1000, + BatchSize: 10, + OpenSessionMaxAgeMs: 24 * 60 * 60 * 1000, + }) + if err != nil { + t.Fatalf("CompensateOpenSessions failed: %v", err) + } + if result.ClosedCount != 1 || result.OrphanLeavesDeleted != 1 { + t.Fatalf("compensation result mismatch: %+v", result) + } + assertSessionEnded(t, wrapped.RawDB(), "evt-comp-join", roomtimedomain.CompensationReasonOpenSessionMaxWindow) + // 补偿闭合按 joined_at + max_age 封顶记时长,跨 7/1 与 7/2 两个 UTC 日。 + assertDailyStats(t, wrapped.RawDB(), 20007, "2026-07-01", 16*60*60*1000, 1) + assertDailyStats(t, wrapped.RawDB(), 20007, "2026-07-02", 8*60*60*1000, 1) +} + +func mustApply(t *testing.T, repo interface { + ProcessRoomPresenceEvent(context.Context, roomtimedomain.RoomPresenceEvent) (roomtimedomain.ApplyResult, error) +}, event roomtimedomain.RoomPresenceEvent) { + t.Helper() + result, err := repo.ProcessRoomPresenceEvent(context.Background(), event) + if err != nil || result.Skipped { + t.Fatalf("ProcessRoomPresenceEvent failed: event=%+v result=%+v err=%v", event, result, err) + } +} + +func presenceEvent(eventID string, eventType string, roomID string, userID int64, occurredAtMs int64) roomtimedomain.RoomPresenceEvent { + return roomtimedomain.RoomPresenceEvent{ + AppCode: "lalu", + EventID: eventID, + EventType: eventType, + RoomID: roomID, + RoomVersion: occurredAtMs, + OccurredAtMs: occurredAtMs, + UserID: userID, + } +} + +func assertDailyStats(t *testing.T, db *sql.DB, userID int64, statDate string, wantOnlineMs int64, wantSessions int64) { + t.Helper() + var onlineMs, sessions int64 + if err := db.QueryRowContext(context.Background(), ` + SELECT room_online_ms, session_count + FROM user_room_daily_stats + WHERE app_code = 'lalu' AND user_id = ? AND stat_date = ? + `, userID, statDate).Scan(&onlineMs, &sessions); err != nil { + t.Fatalf("query user_room_daily_stats user=%d date=%s failed: %v", userID, statDate, err) + } + if onlineMs != wantOnlineMs || sessions != wantSessions { + t.Fatalf("daily stats mismatch user=%d date=%s: online=%d sessions=%d, want online=%d sessions=%d", userID, statDate, onlineMs, sessions, wantOnlineMs, wantSessions) + } +} + +func assertSessionEnded(t *testing.T, db *sql.DB, roomSessionID string, reason string) { + t.Helper() + var status string + var endReason string + if err := db.QueryRowContext(context.Background(), ` + SELECT status, end_reason + FROM user_room_sessions + WHERE app_code = 'lalu' AND room_session_id = ? + `, roomSessionID).Scan(&status, &endReason); err != nil { + t.Fatalf("query user_room_sessions failed: %v", err) + } + if status != roomtimedomain.SessionStatusEnded || endReason != reason { + t.Fatalf("session %s terminal state mismatch: status=%s reason=%s want=%s", roomSessionID, status, endReason, reason) + } +} + +func assertUserOutboxEvent(t *testing.T, db *sql.DB, eventID string, want int) { + t.Helper() + var count int + if err := db.QueryRowContext(context.Background(), ` + SELECT COUNT(*) + FROM user_outbox + WHERE app_code = 'lalu' AND event_id = ? + `, eventID).Scan(&count); err != nil { + t.Fatalf("query user_outbox failed: %v", err) + } + if count != want { + t.Fatalf("user_outbox event %s count=%d, want %d", eventID, count, want) + } +} diff --git a/services/user-service/internal/storage/mysql/user/admin_ban.go b/services/user-service/internal/storage/mysql/user/admin_ban.go new file mode 100644 index 00000000..447a2dbb --- /dev/null +++ b/services/user-service/internal/storage/mysql/user/admin_ban.go @@ -0,0 +1,479 @@ +package user + +import ( + "context" + "database/sql" + "errors" + "strings" + + "hyapp/pkg/appcode" + "hyapp/pkg/idgen" + "hyapp/pkg/xerr" + userdomain "hyapp/services/user-service/internal/domain/user" + userservice "hyapp/services/user-service/internal/service/user" +) + +const adminBanStatusReasonPrefix = "admin_user_ban:" + +// CreateAdminUserBan 在一个事务内写封禁事实、切换 users.status、写审计并吊销 refresh session。 +// 目标已经因其他事实处于 banned 时只叠加事实,不重复写 banned->banned 日志;该约束也是历史永久封禁不被误恢复的判据。 +func (r *Repository) CreateAdminUserBan(ctx context.Context, command userservice.AdminUserBanCommand) (userservice.AdminUserBanPersistenceResult, error) { + if r == nil || r.db == nil { + return userservice.AdminUserBanPersistenceResult{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + appCode := appcode.FromContext(ctx) + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return userservice.AdminUserBanPersistenceResult{}, err + } + defer func() { _ = tx.Rollback() }() + + var oldStatus string + if err := tx.QueryRowContext(ctx, ` + SELECT status + FROM users + WHERE app_code = ? AND user_id = ? + FOR UPDATE`, appCode, command.TargetUserID).Scan(&oldStatus); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return userservice.AdminUserBanPersistenceResult{}, xerr.New(xerr.NotFound, "user not found") + } + return userservice.AdminUserBanPersistenceResult{}, err + } + if userdomain.Status(oldStatus) != userdomain.StatusActive && userdomain.Status(oldStatus) != userdomain.StatusBanned { + // disabled 是独立平台停用语义,不能用封禁事实覆盖;后台应先显式恢复后再封禁。 + return userservice.AdminUserBanPersistenceResult{}, xerr.New(xerr.InvalidArgument, "target user status cannot be banned") + } + + banID := idgen.New("admin_ban") + _, err = tx.ExecContext(ctx, ` + INSERT INTO admin_user_bans ( + app_code, ban_id, command_id, target_user_id, target_old_status, + expires_at_ms, status, reason, operator_admin_id, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, 'active', ?, ?, ?, ?)`, + appCode, banID, command.CommandID, command.TargetUserID, oldStatus, + command.ExpiresAtMs, command.Reason, command.OperatorAdminID, command.NowMs, command.NowMs, + ) + if err != nil { + if adminBanDuplicate(err) { + // 幂等命令可能在上一次调用中已经完整提交;回滚当前事务后读取既有事实和当前用户状态即可。 + _ = tx.Rollback() + ban, lookupErr := r.getAdminUserBanByCommand(ctx, command.CommandID) + if lookupErr != nil { + return userservice.AdminUserBanPersistenceResult{}, lookupErr + } + if ban.TargetUserID != command.TargetUserID || ban.OperatorAdminID != command.OperatorAdminID || ban.ExpiresAtMs != command.ExpiresAtMs || ban.Reason != command.Reason { + // command_id 是完整请求的幂等边界;同一 key 不能静默复用到另一位用户或另一种封禁期限。 + return userservice.AdminUserBanPersistenceResult{}, xerr.New(xerr.Conflict, "admin ban command payload does not match existing fact") + } + user, lookupErr := r.GetUser(ctx, ban.TargetUserID) + if lookupErr != nil { + return userservice.AdminUserBanPersistenceResult{}, lookupErr + } + return userservice.AdminUserBanPersistenceResult{Ban: ban, Status: userservice.UserStatusPersistenceResult{User: user}}, nil + } + return userservice.AdminUserBanPersistenceResult{}, err + } + + sessionIDs := make([]string, 0) + statusChanged := userdomain.Status(oldStatus) == userdomain.StatusActive + if statusChanged { + if _, err := tx.ExecContext(ctx, ` + UPDATE users SET status = 'banned', updated_at_ms = ? + WHERE app_code = ? AND user_id = ?`, command.NowMs, appCode, command.TargetUserID); err != nil { + return userservice.AdminUserBanPersistenceResult{}, err + } + if _, err := tx.ExecContext(ctx, ` + INSERT INTO user_status_change_logs ( + app_code, target_user_id, old_status, new_status, operator_type, + operator_user_id, reason, request_id, created_at_ms + ) VALUES (?, ?, ?, 'banned', 'admin', ?, ?, ?, ?)`, + appCode, command.TargetUserID, oldStatus, command.OperatorAdminID, + adminBanStatusReasonPrefix+banID, command.RequestID, command.NowMs, + ); err != nil { + return userservice.AdminUserBanPersistenceResult{}, err + } + var revokeErr error + sessionIDs, revokeErr = revokeActiveUserSessions(ctx, tx, appCode, command.TargetUserID, command.OperatorAdminID, command.RequestID, command.NowMs) + if revokeErr != nil { + return userservice.AdminUserBanPersistenceResult{}, revokeErr + } + } + + if err := tx.Commit(); err != nil { + return userservice.AdminUserBanPersistenceResult{}, err + } + ban, err := r.getAdminUserBan(ctx, banID) + if err != nil { + return userservice.AdminUserBanPersistenceResult{}, err + } + user, err := r.GetUser(ctx, command.TargetUserID) + if err != nil { + return userservice.AdminUserBanPersistenceResult{}, err + } + return userservice.AdminUserBanPersistenceResult{ + Ban: ban, + Status: userservice.UserStatusPersistenceResult{User: user, RevokedSessionIDs: sessionIDs}, + StatusChanged: statusChanged, + }, nil +} + +func (r *Repository) ReleaseAdminUserBan(ctx context.Context, command userservice.ReleaseAdminUserBanCommand) (userservice.AdminUserBanRelease, error) { + return r.releaseAdminUserBan(ctx, command) +} + +func (r *Repository) ExpireAdminUserBans(ctx context.Context, nowMS int64, limit int32) ([]userservice.AdminUserBanRelease, error) { + if r == nil || r.db == nil { + return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + rows, err := r.db.QueryContext(ctx, ` + SELECT ban_id, target_user_id + FROM admin_user_bans + WHERE app_code = ? AND status = 'active' AND expires_at_ms > 0 AND expires_at_ms <= ? + ORDER BY expires_at_ms ASC, ban_id ASC + LIMIT ?`, appcode.FromContext(ctx), nowMS, limit) + if err != nil { + return nil, err + } + type pendingBan struct { + banID string + targetUserID int64 + } + pending := make([]pendingBan, 0) + for rows.Next() { + var item pendingBan + if err := rows.Scan(&item.banID, &item.targetUserID); err != nil { + _ = rows.Close() + return nil, err + } + pending = append(pending, item) + } + if err := rows.Close(); err != nil { + return nil, err + } + + releases := make([]userservice.AdminUserBanRelease, 0, len(pending)) + for _, item := range pending { + release, err := r.releaseAdminUserBan(ctx, userservice.ReleaseAdminUserBanCommand{ + BanID: item.banID, + TargetUserID: item.targetUserID, + Status: userservice.AdminUserBanStatusExpired, + Reason: "admin_user_ban_expired", + RequestID: "admin_user_ban_expire:" + item.banID, + NowMs: nowMS, + }) + if err != nil { + return releases, err + } + releases = append(releases, release) + } + return releases, nil +} + +func (r *Repository) releaseAdminUserBan(ctx context.Context, command userservice.ReleaseAdminUserBanCommand) (userservice.AdminUserBanRelease, error) { + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return userservice.AdminUserBanRelease{}, err + } + defer func() { _ = tx.Rollback() }() + + // 先锁定命令幂等事实,再选择具体 ban。空 ban_id 的重试会复用第一次解析出的 resolved_ban_id, + // 不会因为上一条已 released 而误释放下一条重叠管理员封禁。 + existingCommand, resolvedBanID, err := claimAdminBanReleaseCommand(ctx, tx, command) + if err != nil { + return userservice.AdminUserBanRelease{}, err + } + var ban userservice.AdminUserBan + if existingCommand { + ban, err = r.getAdminUserBanForUpdate(ctx, tx, resolvedBanID, command.TargetUserID) + } else if strings.TrimSpace(command.BanID) == "" { + // 后台摘要可能正在展示更晚到期的经理封禁;按 target 选最新管理员事实,使重叠场景仍能逐条手动释放。 + ban, err = r.getLatestActiveAdminUserBanForUpdate(ctx, tx, command.TargetUserID) + } else { + ban, err = r.getAdminUserBanForUpdate(ctx, tx, command.BanID, command.TargetUserID) + } + if err != nil { + return userservice.AdminUserBanRelease{}, err + } + if !existingCommand { + if _, err := tx.ExecContext(ctx, ` + UPDATE admin_user_ban_release_commands + SET resolved_ban_id = ?, updated_at_ms = ? + WHERE app_code = ? AND command_id = ?`, + ban.BanID, command.NowMs, appcode.FromContext(ctx), command.RequestID, + ); err != nil { + return userservice.AdminUserBanRelease{}, err + } + } + if ban.Status != userservice.AdminUserBanStatusActive { + if err := tx.Commit(); err != nil { + return userservice.AdminUserBanRelease{}, err + } + user, err := r.GetUser(ctx, ban.TargetUserID) + return userservice.AdminUserBanRelease{Ban: ban, User: user}, err + } + + currentStatus, err := lockUserStatus(ctx, tx, appcode.FromContext(ctx), ban.TargetUserID) + if err != nil { + return userservice.AdminUserBanRelease{}, err + } + if _, err := tx.ExecContext(ctx, ` + UPDATE admin_user_bans + SET status = ?, released_by_admin_id = ?, released_reason = ?, released_at_ms = ?, updated_at_ms = ? + WHERE app_code = ? AND ban_id = ?`, + command.Status, command.OperatorAdminID, command.Reason, command.NowMs, command.NowMs, + appcode.FromContext(ctx), ban.BanID, + ); err != nil { + return userservice.AdminUserBanRelease{}, err + } + ban.Status = command.Status + ban.ReleasedByAdminID = command.OperatorAdminID + ban.ReleasedReason = command.Reason + ban.ReleasedAtMs = command.NowMs + ban.UpdatedAtMs = command.NowMs + + restored, err := restoreUserAfterManagedBanRelease(ctx, tx, managedBanRestoreCommand{ + TargetUserID: ban.TargetUserID, + CurrentStatus: currentStatus, + OperatorType: releaseOperatorType(command.Status), + OperatorID: command.OperatorAdminID, + Reason: "admin_user_unban:" + ban.BanID, + RequestID: command.RequestID, + NowMs: command.NowMs, + }) + if err != nil { + return userservice.AdminUserBanRelease{}, err + } + if err := tx.Commit(); err != nil { + return userservice.AdminUserBanRelease{}, err + } + user, err := r.GetUser(ctx, ban.TargetUserID) + if err != nil { + return userservice.AdminUserBanRelease{}, err + } + return userservice.AdminUserBanRelease{Ban: ban, User: user, Restored: restored}, nil +} + +// claimAdminBanReleaseCommand 把一次手动/自动释放固定为不可变命令载荷。 +// INSERT IGNORE 会在同一 command_id 的并发请求间形成行级串行化,第二个请求提交后只读取首个 resolved_ban_id。 +func claimAdminBanReleaseCommand(ctx context.Context, tx *sql.Tx, command userservice.ReleaseAdminUserBanCommand) (bool, string, error) { + commandID := strings.TrimSpace(command.RequestID) + if commandID == "" { + return false, "", xerr.New(xerr.InvalidArgument, "admin unban request_id is required") + } + requestedBanID := strings.TrimSpace(command.BanID) + result, err := tx.ExecContext(ctx, ` + INSERT IGNORE INTO admin_user_ban_release_commands ( + app_code, command_id, target_user_id, requested_ban_id, resolved_ban_id, + release_status, operator_admin_id, reason, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, '', ?, ?, ?, ?, ?)`, + appcode.FromContext(ctx), commandID, command.TargetUserID, requestedBanID, + command.Status, command.OperatorAdminID, command.Reason, command.NowMs, command.NowMs, + ) + if err != nil { + return false, "", err + } + inserted, err := result.RowsAffected() + if err != nil { + return false, "", err + } + if inserted == 1 { + return false, "", nil + } + + var targetUserID int64 + var storedRequestedBanID, resolvedBanID, releaseStatus, reason string + var operatorAdminID int64 + if err := tx.QueryRowContext(ctx, ` + SELECT target_user_id, requested_ban_id, resolved_ban_id, release_status, operator_admin_id, reason + FROM admin_user_ban_release_commands + WHERE app_code = ? AND command_id = ? + FOR UPDATE`, appcode.FromContext(ctx), commandID).Scan( + &targetUserID, &storedRequestedBanID, &resolvedBanID, &releaseStatus, &operatorAdminID, &reason, + ); err != nil { + return false, "", err + } + if targetUserID != command.TargetUserID || storedRequestedBanID != requestedBanID || + releaseStatus != command.Status || operatorAdminID != command.OperatorAdminID || reason != command.Reason { + return false, "", xerr.New(xerr.Conflict, "admin unban command payload does not match existing command") + } + if strings.TrimSpace(resolvedBanID) == "" { + return false, "", xerr.New(xerr.Conflict, "admin unban command has no resolved ban") + } + return true, resolvedBanID, nil +} + +type managedBanRestoreCommand struct { + TargetUserID int64 + CurrentStatus userdomain.Status + OperatorType string + OperatorID int64 + Reason string + RequestID string + NowMs int64 +} + +// restoreUserAfterManagedBanRelease 在持有 users 行锁时检查全部可管理封禁来源并恢复账号。 +// 最新 banned 日志必须来自 admin/manager 事实;历史无日志或其他平台封禁即使没有 active 事实也不会被自动恢复。 +func restoreUserAfterManagedBanRelease(ctx context.Context, tx *sql.Tx, command managedBanRestoreCommand) (bool, error) { + if command.CurrentStatus != userdomain.StatusBanned { + return false, nil + } + activeCount, err := countActiveManagedBans(ctx, tx, command.TargetUserID) + if err != nil || activeCount > 0 { + return false, err + } + managed, err := latestBanBelongsToManagedFact(ctx, tx, command.TargetUserID) + if err != nil || !managed { + return false, err + } + if _, err := tx.ExecContext(ctx, ` + UPDATE users SET status = 'active', updated_at_ms = ? + WHERE app_code = ? AND user_id = ? AND status = 'banned'`, + command.NowMs, appcode.FromContext(ctx), command.TargetUserID, + ); err != nil { + return false, err + } + if _, err := tx.ExecContext(ctx, ` + INSERT INTO user_status_change_logs ( + app_code, target_user_id, old_status, new_status, operator_type, + operator_user_id, reason, request_id, created_at_ms + ) VALUES (?, ?, 'banned', 'active', ?, ?, ?, ?, ?)`, + appcode.FromContext(ctx), command.TargetUserID, command.OperatorType, + command.OperatorID, command.Reason, command.RequestID, command.NowMs, + ); err != nil { + return false, err + } + return true, nil +} + +func lockUserStatus(ctx context.Context, tx *sql.Tx, appCode string, userID int64) (userdomain.Status, error) { + var status string + if err := tx.QueryRowContext(ctx, ` + SELECT status FROM users WHERE app_code = ? AND user_id = ? FOR UPDATE`, appCode, userID).Scan(&status); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return "", xerr.New(xerr.NotFound, "user not found") + } + return "", err + } + return userdomain.Status(status), nil +} + +func countActiveManagedBans(ctx context.Context, tx *sql.Tx, targetUserID int64) (int64, error) { + var count int64 + err := tx.QueryRowContext(ctx, ` + SELECT + (SELECT COUNT(*) FROM manager_user_blocks WHERE app_code = ? AND target_user_id = ? AND status = 'active') + + (SELECT COUNT(*) FROM admin_user_bans WHERE app_code = ? AND target_user_id = ? AND status = 'active')`, + appcode.FromContext(ctx), targetUserID, appcode.FromContext(ctx), targetUserID, + ).Scan(&count) + return count, err +} + +func latestBanBelongsToManagedFact(ctx context.Context, tx *sql.Tx, targetUserID int64) (bool, error) { + var reason string + err := tx.QueryRowContext(ctx, ` + SELECT reason + FROM user_status_change_logs + WHERE app_code = ? AND target_user_id = ? AND new_status = 'banned' + ORDER BY created_at_ms DESC, id DESC + LIMIT 1`, appcode.FromContext(ctx), targetUserID).Scan(&reason) + if errors.Is(err, sql.ErrNoRows) { + return false, nil + } + if err != nil { + return false, err + } + return strings.HasPrefix(reason, "manager_center_block:") || strings.HasPrefix(reason, adminBanStatusReasonPrefix), nil +} + +func releaseOperatorType(status string) string { + if status == userservice.AdminUserBanStatusExpired || status == userservice.ManagerUserBlockStatusExpired { + return userservice.OperatorTypeSystem + } + return userservice.OperatorTypeAdmin +} + +func revokeActiveUserSessions(ctx context.Context, tx *sql.Tx, appCode string, targetUserID int64, operatorUserID int64, requestID string, nowMs int64) ([]string, error) { + rows, err := tx.QueryContext(ctx, ` + SELECT session_id + FROM auth_sessions + WHERE app_code = ? AND user_id = ? AND expires_at_ms > ? AND revoked_at_ms IS NULL + FOR UPDATE`, appCode, targetUserID, nowMs) + if err != nil { + return nil, err + } + sessionIDs := make([]string, 0) + for rows.Next() { + var sessionID string + if err := rows.Scan(&sessionID); err != nil { + _ = rows.Close() + return nil, err + } + sessionIDs = append(sessionIDs, sessionID) + } + if err := rows.Close(); err != nil { + return nil, err + } + _, err = tx.ExecContext(ctx, ` + UPDATE auth_sessions + SET revoked_at_ms = ?, revoked_reason = 'USER_BANNED', revoked_request_id = ?, revoked_by = ?, updated_at_ms = ? + WHERE app_code = ? AND user_id = ? AND revoked_at_ms IS NULL`, + nowMs, requestID, revokedBy(operatorUserID), nowMs, appCode, targetUserID) + return sessionIDs, err +} + +func (r *Repository) getAdminUserBan(ctx context.Context, banID string) (userservice.AdminUserBan, error) { + return scanAdminUserBan(r.db.QueryRowContext(ctx, adminBanSelectSQL()+` WHERE app_code = ? AND ban_id = ?`, appcode.FromContext(ctx), banID)) +} + +func (r *Repository) getAdminUserBanByCommand(ctx context.Context, commandID string) (userservice.AdminUserBan, error) { + return scanAdminUserBan(r.db.QueryRowContext(ctx, adminBanSelectSQL()+` WHERE app_code = ? AND command_id = ?`, appcode.FromContext(ctx), commandID)) +} + +func (r *Repository) getAdminUserBanForUpdate(ctx context.Context, tx *sql.Tx, banID string, targetUserID int64) (userservice.AdminUserBan, error) { + return scanAdminUserBan(tx.QueryRowContext(ctx, adminBanSelectSQL()+` + WHERE app_code = ? AND ban_id = ? AND target_user_id = ? FOR UPDATE`, + appcode.FromContext(ctx), banID, targetUserID)) +} + +func (r *Repository) getLatestActiveAdminUserBanForUpdate(ctx context.Context, tx *sql.Tx, targetUserID int64) (userservice.AdminUserBan, error) { + return scanAdminUserBan(tx.QueryRowContext(ctx, adminBanSelectSQL()+` + WHERE app_code = ? AND target_user_id = ? AND status = 'active' + ORDER BY created_at_ms DESC, ban_id DESC + LIMIT 1 FOR UPDATE`, appcode.FromContext(ctx), targetUserID)) +} + +func adminBanSelectSQL() string { + return `SELECT ban_id, command_id, target_user_id, target_old_status, expires_at_ms, + status, reason, operator_admin_id, created_at_ms, updated_at_ms, + released_by_admin_id, released_reason, released_at_ms + FROM admin_user_bans` +} + +type adminBanScanner interface { + Scan(dest ...any) error +} + +func scanAdminUserBan(scanner adminBanScanner) (userservice.AdminUserBan, error) { + var ban userservice.AdminUserBan + var oldStatus string + if err := scanner.Scan( + &ban.BanID, &ban.CommandID, &ban.TargetUserID, &oldStatus, &ban.ExpiresAtMs, + &ban.Status, &ban.Reason, &ban.OperatorAdminID, &ban.CreatedAtMs, &ban.UpdatedAtMs, + &ban.ReleasedByAdminID, &ban.ReleasedReason, &ban.ReleasedAtMs, + ); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return userservice.AdminUserBan{}, xerr.New(xerr.NotFound, "admin user ban not found") + } + return userservice.AdminUserBan{}, err + } + ban.TargetOldStatus = userdomain.Status(oldStatus) + return ban, nil +} + +func adminBanDuplicate(err error) bool { + if err == nil { + return false + } + return strings.Contains(err.Error(), "Duplicate entry") || strings.Contains(err.Error(), "Error 1062") +} diff --git a/services/user-service/internal/storage/mysql/user/admin_ban_test.go b/services/user-service/internal/storage/mysql/user/admin_ban_test.go new file mode 100644 index 00000000..3f442ddd --- /dev/null +++ b/services/user-service/internal/storage/mysql/user/admin_ban_test.go @@ -0,0 +1,429 @@ +package user_test + +import ( + "context" + "testing" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + userdomain "hyapp/services/user-service/internal/domain/user" + userservice "hyapp/services/user-service/internal/service/user" + "hyapp/services/user-service/internal/testutil/mysqltest" +) + +func TestAdminUserBanOverlapRestoresOnlyAfterLastManagedFact(t *testing.T) { + repo := mysqltest.NewRepository(t) + ctx := appcode.WithContext(context.Background(), "lalu") + createAdminBanTestUser(t, repo, 18001, "188001", userdomain.StatusActive) + storage := repo.Repository.UserRepository() + + first, err := storage.CreateAdminUserBan(ctx, userservice.AdminUserBanCommand{ + CommandID: "admin-ban-first", TargetUserID: 18001, ExpiresAtMs: 2_000, + OperatorAdminID: 9001, Reason: "first", RequestID: "req-first", NowMs: 1_000, + }) + if err != nil { + t.Fatalf("create first admin ban failed: %v", err) + } + second, err := storage.CreateAdminUserBan(ctx, userservice.AdminUserBanCommand{ + CommandID: "admin-ban-second", TargetUserID: 18001, ExpiresAtMs: 0, + OperatorAdminID: 9002, Reason: "second", RequestID: "req-second", NowMs: 1_100, + }) + if err != nil { + t.Fatalf("create overlapping admin ban failed: %v", err) + } + if !first.StatusChanged || second.StatusChanged { + t.Fatalf("only the first ban may transition user status: first=%+v second=%+v", first, second) + } + if _, err := storage.SetUserStatus(ctx, userservice.UserStatusCommand{ + AppCode: "lalu", TargetUserID: 18001, Status: userdomain.StatusActive, + OperatorType: userservice.OperatorTypeAdmin, OperatorUserID: 9003, RequestID: "bypass", NowMs: 1_150, + }); xerr.CodeOf(err) != xerr.Conflict { + t.Fatalf("generic active status must not bypass active ban facts, err=%v", err) + } + + firstRelease, err := storage.ReleaseAdminUserBan(ctx, userservice.ReleaseAdminUserBanCommand{ + BanID: first.Ban.BanID, TargetUserID: 18001, OperatorAdminID: 9001, + Status: userservice.AdminUserBanStatusReleased, Reason: "release-first", RequestID: "release-first", NowMs: 1_200, + }) + if err != nil { + t.Fatalf("release first admin ban failed: %v", err) + } + if firstRelease.Restored || firstRelease.User.Status != userdomain.StatusBanned { + t.Fatalf("remaining admin ban must keep user banned: %+v", firstRelease) + } + + secondRelease, err := storage.ReleaseAdminUserBan(ctx, userservice.ReleaseAdminUserBanCommand{ + BanID: second.Ban.BanID, TargetUserID: 18001, OperatorAdminID: 9002, + Status: userservice.AdminUserBanStatusReleased, Reason: "release-second", RequestID: "release-second", NowMs: 1_300, + }) + if err != nil { + t.Fatalf("release second admin ban failed: %v", err) + } + if !secondRelease.Restored || secondRelease.User.Status != userdomain.StatusActive { + t.Fatalf("last managed ban release must restore active user: %+v", secondRelease) + } +} + +func TestAdminUserBanReleaseWithoutIDSelectsLatestAdminFact(t *testing.T) { + repo := mysqltest.NewRepository(t) + ctx := appcode.WithContext(context.Background(), "lalu") + createAdminBanTestUser(t, repo, 18007, "188007", userdomain.StatusActive) + storage := repo.Repository.UserRepository() + first, err := storage.CreateAdminUserBan(ctx, userservice.AdminUserBanCommand{ + CommandID: "admin-ban-select-first", TargetUserID: 18007, ExpiresAtMs: 8_000, + OperatorAdminID: 9001, Reason: "first", RequestID: "select-first", NowMs: 6_000, + }) + if err != nil { + t.Fatalf("create first admin ban failed: %v", err) + } + second, err := storage.CreateAdminUserBan(ctx, userservice.AdminUserBanCommand{ + CommandID: "admin-ban-select-second", TargetUserID: 18007, ExpiresAtMs: 0, + OperatorAdminID: 9002, Reason: "second", RequestID: "select-second", NowMs: 6_100, + }) + if err != nil { + t.Fatalf("create second admin ban failed: %v", err) + } + release, err := storage.ReleaseAdminUserBan(ctx, userservice.ReleaseAdminUserBanCommand{ + TargetUserID: 18007, OperatorAdminID: 9003, Status: userservice.AdminUserBanStatusReleased, + Reason: "release-latest", RequestID: "release-latest", NowMs: 6_200, + }) + if err != nil { + t.Fatalf("release latest admin ban failed: %v", err) + } + if release.Ban.BanID != second.Ban.BanID || release.Restored || release.User.Status != userdomain.StatusBanned { + t.Fatalf("latest fact must be released while older fact keeps ban: release=%+v first=%+v", release, first.Ban) + } + // 同一 request_id 代表同一次解封命令;重试必须返回原结果,不能继续释放第一条重叠封禁。 + retry, err := storage.ReleaseAdminUserBan(ctx, userservice.ReleaseAdminUserBanCommand{ + TargetUserID: 18007, OperatorAdminID: 9003, Status: userservice.AdminUserBanStatusReleased, + Reason: "release-latest", RequestID: "release-latest", NowMs: 6_300, + }) + if err != nil { + t.Fatalf("retry latest admin ban release failed: %v", err) + } + if retry.Ban.BanID != second.Ban.BanID || retry.Restored || retry.User.Status != userdomain.StatusBanned { + t.Fatalf("retry must reuse resolved ban and leave older fact active: retry=%+v first=%+v", retry, first.Ban) + } + finalRelease, err := storage.ReleaseAdminUserBan(ctx, userservice.ReleaseAdminUserBanCommand{ + TargetUserID: 18007, OperatorAdminID: 9003, Status: userservice.AdminUserBanStatusReleased, + Reason: "release-remaining", RequestID: "release-remaining", NowMs: 6_400, + }) + if err != nil { + t.Fatalf("release remaining admin ban failed: %v", err) + } + if finalRelease.Ban.BanID != first.Ban.BanID || !finalRelease.Restored || finalRelease.User.Status != userdomain.StatusActive { + t.Fatalf("new command must release remaining fact and restore user: %+v", finalRelease) + } +} + +func TestAdminUserBanDoesNotRestoreLegacyBannedUser(t *testing.T) { + repo := mysqltest.NewRepository(t) + ctx := appcode.WithContext(context.Background(), "lalu") + createAdminBanTestUser(t, repo, 18002, "188002", userdomain.StatusBanned) + storage := repo.Repository.UserRepository() + + persisted, err := storage.CreateAdminUserBan(ctx, userservice.AdminUserBanCommand{ + CommandID: "legacy-overlay", TargetUserID: 18002, OperatorAdminID: 9001, + Reason: "legacy-overlay", RequestID: "legacy-overlay", NowMs: 2_000, + }) + if err != nil { + t.Fatalf("create admin fact over legacy ban failed: %v", err) + } + if persisted.StatusChanged { + t.Fatalf("legacy banned user must not get a synthetic banned->banned transition: %+v", persisted) + } + + release, err := storage.ReleaseAdminUserBan(ctx, userservice.ReleaseAdminUserBanCommand{ + BanID: persisted.Ban.BanID, TargetUserID: 18002, OperatorAdminID: 9001, + Status: userservice.AdminUserBanStatusReleased, Reason: "manual-release", RequestID: "manual-release", NowMs: 2_100, + }) + if err != nil { + t.Fatalf("release legacy overlay failed: %v", err) + } + if release.Restored || release.User.Status != userdomain.StatusBanned { + t.Fatalf("legacy ban must remain permanent after overlay release: %+v", release) + } +} + +func TestManagerAndAdminBanOverlapRestoresOnlyAfterBothSourcesRelease(t *testing.T) { + repo := mysqltest.NewRepository(t) + ctx := appcode.WithContext(context.Background(), "lalu") + createAdminBanTestUser(t, repo, 18004, "188004", userdomain.StatusActive) + storage := repo.Repository.UserRepository() + target, err := storage.GetUser(ctx, 18004) + if err != nil { + t.Fatalf("get manager block target failed: %v", err) + } + managerBlock, err := storage.CreateManagerUserBlock(ctx, userservice.ManagerUserBlockCommand{ + CommandID: "manager-block", ManagerUserID: 7001, TargetUserID: 18004, + BlockedUntilMS: 8_000, Reason: "manager", RequestID: "manager-block", NowMS: 3_000, + }, target) + if err != nil { + t.Fatalf("create manager block fact failed: %v", err) + } + if _, err := storage.SetUserStatus(ctx, userservice.UserStatusCommand{ + AppCode: "lalu", TargetUserID: 18004, Status: userdomain.StatusBanned, + OperatorType: userservice.OperatorTypeAppUser, OperatorUserID: 7001, + Reason: "manager_center_block:" + managerBlock.BlockID, RequestID: "manager-block", NowMs: 3_000, + }); err != nil { + t.Fatalf("apply manager block status failed: %v", err) + } + adminBan, err := storage.CreateAdminUserBan(ctx, userservice.AdminUserBanCommand{ + CommandID: "admin-over-manager", TargetUserID: 18004, ExpiresAtMs: 9_000, + OperatorAdminID: 9001, Reason: "admin", RequestID: "admin-over-manager", NowMs: 3_100, + }) + if err != nil { + t.Fatalf("create admin overlap failed: %v", err) + } + + managerRelease, err := storage.ReleaseManagerUserBlock(ctx, userservice.ReleaseManagerUserBlockCommand{ + ManagerUserID: 7001, BlockID: managerBlock.BlockID, Status: userservice.ManagerUserBlockStatusReleased, + Reason: "manager-release", RequestID: "manager-release", NowMS: 3_200, + }) + if err != nil { + t.Fatalf("release manager block failed: %v", err) + } + if managerRelease.ShouldRestore || managerRelease.User.Status != userdomain.StatusBanned { + t.Fatalf("active admin ban must prevent manager release from restoring user: %+v", managerRelease) + } + + adminRelease, err := storage.ReleaseAdminUserBan(ctx, userservice.ReleaseAdminUserBanCommand{ + BanID: adminBan.Ban.BanID, TargetUserID: 18004, OperatorAdminID: 9001, + Status: userservice.AdminUserBanStatusReleased, Reason: "admin-release", RequestID: "admin-release", NowMs: 3_300, + }) + if err != nil { + t.Fatalf("release admin block failed: %v", err) + } + if !adminRelease.Restored || adminRelease.User.Status != userdomain.StatusActive { + t.Fatalf("last manager/admin source release must restore user: %+v", adminRelease) + } +} + +func TestAdminForceUnbanReleasesAllManagedSourcesAndRestoresUser(t *testing.T) { + repo := mysqltest.NewRepository(t) + ctx := appcode.WithContext(context.Background(), "lalu") + createAdminBanTestUser(t, repo, 18008, "188008", userdomain.StatusActive) + storage := repo.Repository.UserRepository() + target, err := storage.GetUser(ctx, 18008) + if err != nil { + t.Fatalf("get force unban target failed: %v", err) + } + managerBlock, err := storage.CreateManagerUserBlock(ctx, userservice.ManagerUserBlockCommand{ + CommandID: "force-manager", ManagerUserID: 7001, TargetUserID: 18008, + BlockedUntilMS: 20_000, Reason: "manager", RequestID: "force-manager", NowMS: 10_000, + }, target) + if err != nil { + t.Fatalf("create manager block failed: %v", err) + } + if _, err := storage.SetUserStatus(ctx, userservice.UserStatusCommand{ + AppCode: "lalu", TargetUserID: 18008, Status: userdomain.StatusBanned, + OperatorType: userservice.OperatorTypeAppUser, OperatorUserID: 7001, + Reason: "manager_center_block:" + managerBlock.BlockID, RequestID: "force-manager", NowMs: 10_000, + }); err != nil { + t.Fatalf("apply manager block status failed: %v", err) + } + if _, err := storage.CreateAdminUserBan(ctx, userservice.AdminUserBanCommand{ + CommandID: "force-admin-first", TargetUserID: 18008, ExpiresAtMs: 0, + OperatorAdminID: 9001, Reason: "admin-first", RequestID: "force-admin-first", NowMs: 10_100, + }); err != nil { + t.Fatalf("create first admin ban failed: %v", err) + } + if _, err := storage.CreateAdminUserBan(ctx, userservice.AdminUserBanCommand{ + CommandID: "force-admin-second", TargetUserID: 18008, ExpiresAtMs: 30_000, + OperatorAdminID: 9002, Reason: "admin-second", RequestID: "force-admin-second", NowMs: 10_200, + }); err != nil { + t.Fatalf("create second admin ban failed: %v", err) + } + + release, err := storage.ForceReleaseUserBans(ctx, userservice.AdminForceUnbanCommand{ + TargetUserID: 18008, OperatorAdminID: 9900, Reason: "manual override", + RequestID: "force-unban-all", NowMs: 10_300, + }) + if err != nil { + t.Fatalf("force unban failed: %v", err) + } + if release.ReleasedAdminBanCount != 2 || release.ReleasedManagerBlockCount != 1 || !release.StatusRestored || release.User.Status != userdomain.StatusActive { + t.Fatalf("force unban must release every source and restore user: %+v", release) + } + + var activeAdminCount, activeManagerCount int64 + if err := repo.RawDB().QueryRow(`SELECT COUNT(*) FROM admin_user_bans WHERE app_code = 'lalu' AND target_user_id = 18008 AND status = 'active'`).Scan(&activeAdminCount); err != nil { + t.Fatalf("count active admin bans failed: %v", err) + } + if err := repo.RawDB().QueryRow(`SELECT COUNT(*) FROM manager_user_blocks WHERE app_code = 'lalu' AND target_user_id = 18008 AND status = 'active'`).Scan(&activeManagerCount); err != nil { + t.Fatalf("count active manager blocks failed: %v", err) + } + if activeAdminCount != 0 || activeManagerCount != 0 { + t.Fatalf("force unban left active facts: admin=%d manager=%d", activeAdminCount, activeManagerCount) + } +} + +func TestAdminForceUnbanRetryDoesNotReleaseNewBan(t *testing.T) { + repo := mysqltest.NewRepository(t) + ctx := appcode.WithContext(context.Background(), "lalu") + createAdminBanTestUser(t, repo, 18009, "188009", userdomain.StatusBanned) + storage := repo.Repository.UserRepository() + command := userservice.AdminForceUnbanCommand{ + TargetUserID: 18009, OperatorAdminID: 9900, Reason: "legacy override", + RequestID: "force-unban-idempotent", NowMs: 11_000, + } + first, err := storage.ForceReleaseUserBans(ctx, command) + if err != nil { + t.Fatalf("force legacy unban failed: %v", err) + } + if first.ReleasedAdminBanCount != 0 || first.ReleasedManagerBlockCount != 0 || !first.StatusRestored || first.User.Status != userdomain.StatusActive { + t.Fatalf("legacy force unban mismatch: %+v", first) + } + newBan, err := storage.CreateAdminUserBan(ctx, userservice.AdminUserBanCommand{ + CommandID: "ban-after-force", TargetUserID: 18009, ExpiresAtMs: 0, + OperatorAdminID: 9001, Reason: "new independent ban", RequestID: "ban-after-force", NowMs: 11_100, + }) + if err != nil { + t.Fatalf("create new ban after force unban failed: %v", err) + } + + retry, err := storage.ForceReleaseUserBans(ctx, command) + if err != nil { + t.Fatalf("retry force unban failed: %v", err) + } + if !retry.StatusRestored || retry.User.Status != userdomain.StatusBanned { + t.Fatalf("retry must return first command result without restoring newly banned user: %+v", retry) + } + var newBanStatus string + if err := repo.RawDB().QueryRow(`SELECT status FROM admin_user_bans WHERE app_code = 'lalu' AND ban_id = ?`, newBan.Ban.BanID).Scan(&newBanStatus); err != nil { + t.Fatalf("read new ban status failed: %v", err) + } + if newBanStatus != userservice.AdminUserBanStatusActive { + t.Fatalf("retry released a ban created after the original command: status=%s", newBanStatus) + } +} + +func TestReleasedManagerBlockCannotReapplyBannedStatus(t *testing.T) { + repo := mysqltest.NewRepository(t) + ctx := appcode.WithContext(context.Background(), "lalu") + createAdminBanTestUser(t, repo, 18010, "188010", userdomain.StatusActive) + storage := repo.Repository.UserRepository() + target, err := storage.GetUser(ctx, 18010) + if err != nil { + t.Fatalf("get manager race target failed: %v", err) + } + block, err := storage.CreateManagerUserBlock(ctx, userservice.ManagerUserBlockCommand{ + CommandID: "manager-race", ManagerUserID: 7001, TargetUserID: 18010, + BlockedUntilMS: 30_000, Reason: "race", RequestID: "manager-race", NowMS: 12_000, + }, target) + if err != nil { + t.Fatalf("create manager race block failed: %v", err) + } + if _, err := storage.ForceReleaseUserBans(ctx, userservice.AdminForceUnbanCommand{ + TargetUserID: 18010, OperatorAdminID: 9900, Reason: "override pending block", + RequestID: "force-before-manager-status", NowMs: 12_100, + }); err != nil { + t.Fatalf("force release pending manager block failed: %v", err) + } + if _, err := storage.SetUserStatus(ctx, userservice.UserStatusCommand{ + AppCode: "lalu", TargetUserID: 18010, Status: userdomain.StatusBanned, + OperatorType: userservice.OperatorTypeAppUser, OperatorUserID: 7001, + Reason: "manager_center_block:" + block.BlockID, RequestID: "manager-race", NowMs: 12_200, + }); xerr.CodeOf(err) != xerr.Conflict { + t.Fatalf("released manager block must not reapply banned status, err=%v", err) + } + user, err := storage.GetUser(ctx, 18010) + if err != nil || user.Status != userdomain.StatusActive { + t.Fatalf("released manager race must leave user active: user=%+v err=%v", user, err) + } +} + +func TestExpireAdminUserBansSkipsPermanentAndRestoresTimedBan(t *testing.T) { + repo := mysqltest.NewRepository(t) + ctx := appcode.WithContext(context.Background(), "lalu") + createAdminBanTestUser(t, repo, 18005, "188005", userdomain.StatusActive) + createAdminBanTestUser(t, repo, 18006, "188006", userdomain.StatusActive) + storage := repo.Repository.UserRepository() + if _, err := storage.CreateAdminUserBan(ctx, userservice.AdminUserBanCommand{ + CommandID: "permanent-ban", TargetUserID: 18005, ExpiresAtMs: 0, + OperatorAdminID: 9001, RequestID: "permanent-ban", NowMs: 4_000, + }); err != nil { + t.Fatalf("create permanent ban failed: %v", err) + } + if _, err := storage.CreateAdminUserBan(ctx, userservice.AdminUserBanCommand{ + CommandID: "timed-ban", TargetUserID: 18006, ExpiresAtMs: 4_500, + OperatorAdminID: 9001, RequestID: "timed-ban", NowMs: 4_000, + }); err != nil { + t.Fatalf("create timed ban failed: %v", err) + } + + releases, err := storage.ExpireAdminUserBans(ctx, 5_000, 100) + if err != nil { + t.Fatalf("expire timed bans failed: %v", err) + } + if len(releases) != 1 || releases[0].Ban.TargetUserID != 18006 || !releases[0].Restored { + t.Fatalf("expiry must release only timed ban: %+v", releases) + } + permanentUser, err := storage.GetUser(ctx, 18005) + if err != nil || permanentUser.Status != userdomain.StatusBanned { + t.Fatalf("permanent ban must remain banned: user=%+v err=%v", permanentUser, err) + } +} + +func TestBatchGetUserAdminProfilesUsesSuccessfulLoginAndActiveRoles(t *testing.T) { + repo := mysqltest.NewRepository(t) + ctx := appcode.WithContext(context.Background(), "lalu") + createAdminBanTestUser(t, repo, 18003, "188003", userdomain.StatusActive) + db := repo.RawDB() + if _, err := db.Exec(`UPDATE users SET birth_date = '2000-02-03', register_device = 'Pixel 9' WHERE app_code = 'lalu' AND user_id = 18003`); err != nil { + t.Fatalf("seed admin profile user fields failed: %v", err) + } + if _, err := db.Exec(` + INSERT INTO login_audit (app_code, request_id, user_id, login_type, channel, platform, result, blocked, block_reason, created_at_ms) + VALUES ('lalu', 'success-login', 18003, 'password', 'account', 'android', 'success', 0, '', 3000), + ('lalu', 'failed-login', 18003, 'password', 'account', 'android', 'failed', 0, '', 4000)`); err != nil { + t.Fatalf("seed admin profile login facts failed: %v", err) + } + if _, err := db.Exec(` + INSERT INTO host_profiles (app_code, user_id, status, current_agency_id, current_membership_id, source, first_became_host_at_ms, created_at_ms, updated_at_ms) + VALUES ('lalu', 18003, 'active', NULL, NULL, 'admin_add_host', 3000, 3000, 3000)`); err != nil { + t.Fatalf("seed admin profile host role failed: %v", err) + } + if _, err := db.Exec(` + INSERT INTO manager_profiles (app_code, user_id, status, contact_info, created_by_admin_id, created_at_ms, updated_at_ms) + VALUES ('lalu', 18003, 'active', '', 9001, 3000, 3000)`); err != nil { + t.Fatalf("seed admin profile manager role failed: %v", err) + } + + profiles, err := repo.Repository.UserRepository().BatchGetUserAdminProfiles(ctx, []int64{18003}, 5_000) + if err != nil { + t.Fatalf("batch admin profile failed: %v", err) + } + profile := profiles[18003] + if profile.BirthDate != "2000-02-03" || profile.RegisterDevice != "Pixel 9" || profile.LastSuccessLoginAtMs != 3_000 { + t.Fatalf("admin profile base/login facts mismatch: %+v", profile) + } + if len(profile.Roles) != 2 || profile.Roles[0] != userdomain.AdminRoleHost || profile.Roles[1] != userdomain.AdminRoleManager { + t.Fatalf("admin profile roles mismatch: %+v", profile.Roles) + } +} + +func createAdminBanTestUser(t *testing.T, repo *mysqltest.Repository, userID int64, displayUserID string, status userdomain.Status) { + t.Helper() + err := repo.CreateUserWithIdentity(appcode.WithContext(context.Background(), "lalu"), userdomain.User{ + AppCode: "lalu", + UserID: userID, + DefaultDisplayUserID: displayUserID, + CurrentDisplayUserID: displayUserID, + CurrentDisplayUserIDKind: userdomain.DisplayUserIDKindDefault, + Username: "admin-ban-user", + Status: status, + CreatedAtMs: 100, + UpdatedAtMs: 100, + }, userdomain.Identity{ + AppCode: "lalu", + UserID: userID, + DisplayUserID: displayUserID, + DefaultDisplayUserID: displayUserID, + DisplayUserIDKind: userdomain.DisplayUserIDKindDefault, + Status: userdomain.DisplayUserIDStatusActive, + }) + if err != nil { + t.Fatalf("create admin ban test user failed: %v", err) + } +} diff --git a/services/user-service/internal/storage/mysql/user/admin_profile.go b/services/user-service/internal/storage/mysql/user/admin_profile.go new file mode 100644 index 00000000..74438035 --- /dev/null +++ b/services/user-service/internal/storage/mysql/user/admin_profile.go @@ -0,0 +1,252 @@ +package user + +import ( + "context" + "fmt" + "strings" + + "hyapp/pkg/appcode" + userdomain "hyapp/services/user-service/internal/domain/user" +) + +// BatchGetUserAdminProfiles 聚合 user-service 同库内的用户、登录、角色和封禁事实。 +// 该查询不读取 wallet/activity 等 owner 数据;admin-server 会分别批量读取并组装最终 DTO。 +func (r *Repository) BatchGetUserAdminProfiles(ctx context.Context, userIDs []int64, _ int64) (map[int64]userdomain.AdminProfile, error) { + users, err := r.BatchGetUsers(ctx, userIDs) + if err != nil { + return nil, err + } + profiles := make(map[int64]userdomain.AdminProfile, len(users)) + for userID, item := range users { + profiles[userID] = userdomain.AdminProfile{ + User: item, + BirthDate: item.BirthDate, + RegisterDevice: item.RegisterDevice, + Roles: make([]string, 0, 6), + } + } + if len(profiles) == 0 { + return profiles, nil + } + + placeholders, idArgs := adminProfileIDArgs(userIDs) + if err := r.hydrateAdminLastSuccessLogin(ctx, profiles, placeholders, idArgs); err != nil { + return nil, err + } + if err := r.hydrateAdminRoles(ctx, profiles, placeholders, idArgs); err != nil { + return nil, err + } + if err := r.hydrateAdminLastOperations(ctx, profiles, placeholders, idArgs); err != nil { + return nil, err + } + if err := r.hydrateAdminBanSummaries(ctx, profiles, placeholders, idArgs); err != nil { + return nil, err + } + return profiles, nil +} + +func (r *Repository) hydrateAdminLastSuccessLogin(ctx context.Context, profiles map[int64]userdomain.AdminProfile, placeholders string, idArgs []any) error { + args := append([]any{appcode.FromContext(ctx)}, idArgs...) + rows, err := r.db.QueryContext(ctx, fmt.Sprintf(` + SELECT user_id, MAX(created_at_ms) + FROM login_audit + WHERE app_code = ? AND result = 'success' AND user_id IN (%s) + GROUP BY user_id`, placeholders), args...) + if err != nil { + return err + } + defer rows.Close() + for rows.Next() { + var userID, loggedInAtMs int64 + if err := rows.Scan(&userID, &loggedInAtMs); err != nil { + return err + } + profile, ok := profiles[userID] + if ok { + profile.LastSuccessLoginAtMs = loggedInAtMs + profiles[userID] = profile + } + } + return rows.Err() +} + +func (r *Repository) hydrateAdminRoles(ctx context.Context, profiles map[int64]userdomain.AdminProfile, placeholders string, idArgs []any) error { + args := append([]any{appcode.FromContext(ctx)}, idArgs...) + rows, err := r.db.QueryContext(ctx, fmt.Sprintf(` + SELECT u.user_id, + EXISTS(SELECT 1 FROM host_profiles h WHERE h.app_code = u.app_code AND h.user_id = u.user_id AND h.status = 'active'), + EXISTS(SELECT 1 FROM agencies a WHERE a.app_code = u.app_code AND a.active_owner_user_id = u.user_id), + EXISTS(SELECT 1 FROM bd_profiles b WHERE b.app_code = u.app_code AND b.user_id = u.user_id AND b.status = 'active'), + EXISTS(SELECT 1 FROM bd_leader_profiles bl WHERE bl.app_code = u.app_code AND bl.user_id = u.user_id AND bl.status = 'active'), + EXISTS(SELECT 1 FROM coin_seller_profiles c WHERE c.app_code = u.app_code AND c.user_id = u.user_id AND c.status = 'active'), + EXISTS(SELECT 1 FROM manager_profiles m WHERE m.app_code = u.app_code AND m.user_id = u.user_id AND m.status = 'active') + FROM users u + WHERE u.app_code = ? AND u.user_id IN (%s)`, placeholders), args...) + if err != nil { + return err + } + defer rows.Close() + for rows.Next() { + var userID int64 + var host, agency, bd, bdLeader, coinSeller, manager bool + if err := rows.Scan(&userID, &host, &agency, &bd, &bdLeader, &coinSeller, &manager); err != nil { + return err + } + profile, ok := profiles[userID] + if !ok { + continue + } + // 固定顺序让 CSV、列表和详情在相同事实下始终产生一致展示,避免 map/set 顺序漂移。 + profile.Roles = appendAdminRole(profile.Roles, host, userdomain.AdminRoleHost) + profile.Roles = appendAdminRole(profile.Roles, agency, userdomain.AdminRoleAgency) + profile.Roles = appendAdminRole(profile.Roles, bd, userdomain.AdminRoleBD) + profile.Roles = appendAdminRole(profile.Roles, bdLeader, userdomain.AdminRoleBDLeader) + profile.Roles = appendAdminRole(profile.Roles, coinSeller, userdomain.AdminRoleCoinSeller) + profile.Roles = appendAdminRole(profile.Roles, manager, userdomain.AdminRoleManager) + profiles[userID] = profile + } + return rows.Err() +} + +func (r *Repository) hydrateAdminLastOperations(ctx context.Context, profiles map[int64]userdomain.AdminProfile, placeholders string, idArgs []any) error { + args := make([]any, 0, len(idArgs)+2) + args = append(args, appcode.FromContext(ctx)) + args = append(args, idArgs...) + args = append(args, appcode.FromContext(ctx)) + rows, err := r.db.QueryContext(ctx, fmt.Sprintf(` + SELECT log.target_user_id, log.operator_type, log.operator_user_id, log.reason, log.created_at_ms + FROM user_status_change_logs log + INNER JOIN ( + SELECT target_user_id, MAX(id) AS latest_id + FROM user_status_change_logs + WHERE app_code = ? AND target_user_id IN (%s) + GROUP BY target_user_id + ) latest ON latest.latest_id = log.id + WHERE log.app_code = ?`, placeholders), args...) + if err != nil { + return err + } + defer rows.Close() + for rows.Next() { + var userID, operatorUserID, operatedAtMs int64 + var operatorType, reason string + if err := rows.Scan(&userID, &operatorType, &operatorUserID, &reason, &operatedAtMs); err != nil { + return err + } + profile, ok := profiles[userID] + if ok { + profile.LastOperatorType = operatorType + profile.LastOperatorUserID = operatorUserID + profile.LastOperationReason = reason + profile.LastOperationAtMs = operatedAtMs + profiles[userID] = profile + } + } + return rows.Err() +} + +func (r *Repository) hydrateAdminBanSummaries(ctx context.Context, profiles map[int64]userdomain.AdminProfile, placeholders string, idArgs []any) error { + args := make([]any, 0, len(idArgs)*2+2) + args = append(args, appcode.FromContext(ctx)) + args = append(args, idArgs...) + args = append(args, appcode.FromContext(ctx)) + args = append(args, idArgs...) + rows, err := r.db.QueryContext(ctx, fmt.Sprintf(` + SELECT 'admin', ban_id, target_user_id, expires_at_ms, operator_admin_id, reason, created_at_ms + FROM admin_user_bans + WHERE app_code = ? AND status = 'active' AND target_user_id IN (%s) + UNION ALL + SELECT 'manager', block_id, target_user_id, blocked_until_ms, manager_user_id, reason, created_at_ms + FROM manager_user_blocks + WHERE app_code = ? AND status = 'active' AND target_user_id IN (%s)`, placeholders, placeholders), args...) + if err != nil { + return err + } + defer rows.Close() + for rows.Next() { + var source, banID, reason string + var targetUserID, expiresAtMs, operatorUserID, createdAtMs int64 + if err := rows.Scan(&source, &banID, &targetUserID, &expiresAtMs, &operatorUserID, &reason, &createdAtMs); err != nil { + return err + } + profile, ok := profiles[targetUserID] + if !ok { + continue + } + profile.Ban = mergeActiveBan(profile.Ban, userdomain.ActiveBanSummary{ + Active: true, + Source: source, + BanID: banID, + Permanent: expiresAtMs == 0, + ExpiresAtMs: expiresAtMs, + UserStatus: profile.User.Status, + OperatorType: source, + OperatorUserID: operatorUserID, + Reason: reason, + CreatedAtMs: createdAtMs, + ActiveBanCount: 1, + }) + profiles[targetUserID] = profile + } + if err := rows.Err(); err != nil { + return err + } + + for userID, profile := range profiles { + if profile.Ban.Active || (profile.User.Status != userdomain.StatusBanned && profile.User.Status != userdomain.StatusDisabled) { + continue + } + // 历史 banned/disabled 没有新事实表记录时一律视为永久;cron 绝不能仅凭时间猜测并恢复这些账号。 + profile.Ban = userdomain.ActiveBanSummary{ + Active: true, + Source: userdomain.BanSourceLegacy, + Permanent: true, + UserStatus: profile.User.Status, + OperatorType: profile.LastOperatorType, + OperatorUserID: profile.LastOperatorUserID, + Reason: profile.LastOperationReason, + CreatedAtMs: profile.LastOperationAtMs, + ActiveBanCount: 1, + } + profiles[userID] = profile + } + return nil +} + +func adminProfileIDArgs(userIDs []int64) (string, []any) { + placeholders := make([]string, 0, len(userIDs)) + args := make([]any, 0, len(userIDs)) + for _, userID := range userIDs { + placeholders = append(placeholders, "?") + args = append(args, userID) + } + return strings.Join(placeholders, ","), args +} + +func appendAdminRole(roles []string, active bool, role string) []string { + if active { + return append(roles, role) + } + return roles +} + +func mergeActiveBan(current userdomain.ActiveBanSummary, candidate userdomain.ActiveBanSummary) userdomain.ActiveBanSummary { + if !current.Active { + return candidate + } + current.ActiveBanCount += candidate.ActiveBanCount + // 任一永久封禁都会让聚合视图永久;多个永久事实取最新一条作为可操作 ban_id 和审计展示。 + if candidate.Permanent && (!current.Permanent || candidate.CreatedAtMs > current.CreatedAtMs) { + candidate.ActiveBanCount = current.ActiveBanCount + return candidate + } + if current.Permanent { + return current + } + // 全部是限时事实时,账号只有在最晚一条也到期后才能恢复,因此摘要展示最晚截止时间。 + if candidate.ExpiresAtMs > current.ExpiresAtMs || (candidate.ExpiresAtMs == current.ExpiresAtMs && candidate.CreatedAtMs > current.CreatedAtMs) { + candidate.ActiveBanCount = current.ActiveBanCount + return candidate + } + return current +} diff --git a/services/user-service/internal/storage/mysql/user/admin_profile_test.go b/services/user-service/internal/storage/mysql/user/admin_profile_test.go new file mode 100644 index 00000000..553db376 --- /dev/null +++ b/services/user-service/internal/storage/mysql/user/admin_profile_test.go @@ -0,0 +1,47 @@ +package user + +import ( + "reflect" + "testing" + + userdomain "hyapp/services/user-service/internal/domain/user" +) + +func TestMergeActiveBanUsesPermanentFactAndPreservesCount(t *testing.T) { + finite := userdomain.ActiveBanSummary{ + Active: true, Source: userdomain.BanSourceManager, BanID: "manager-1", + ExpiresAtMs: 2_000, CreatedAtMs: 100, ActiveBanCount: 1, + } + permanent := userdomain.ActiveBanSummary{ + Active: true, Source: userdomain.BanSourceAdmin, BanID: "admin-1", + Permanent: true, CreatedAtMs: 200, ActiveBanCount: 1, + } + + merged := mergeActiveBan(finite, permanent) + if !merged.Permanent || merged.BanID != "admin-1" || merged.ActiveBanCount != 2 { + t.Fatalf("permanent ban must govern aggregate: %+v", merged) + } +} + +func TestMergeActiveBanUsesLatestFiniteExpiry(t *testing.T) { + first := userdomain.ActiveBanSummary{Active: true, BanID: "first", ExpiresAtMs: 3_000, CreatedAtMs: 100, ActiveBanCount: 1} + second := userdomain.ActiveBanSummary{Active: true, BanID: "second", ExpiresAtMs: 5_000, CreatedAtMs: 50, ActiveBanCount: 1} + + merged := mergeActiveBan(first, second) + if merged.BanID != "second" || merged.ExpiresAtMs != 5_000 || merged.ActiveBanCount != 2 { + t.Fatalf("latest finite expiry must govern aggregate: %+v", merged) + } +} + +func TestAppendAdminRoleKeepsStableDisplayOrder(t *testing.T) { + roles := make([]string, 0, 6) + roles = appendAdminRole(roles, true, userdomain.AdminRoleHost) + roles = appendAdminRole(roles, true, userdomain.AdminRoleAgency) + roles = appendAdminRole(roles, false, userdomain.AdminRoleBD) + roles = appendAdminRole(roles, true, userdomain.AdminRoleManager) + + want := []string{userdomain.AdminRoleHost, userdomain.AdminRoleAgency, userdomain.AdminRoleManager} + if !reflect.DeepEqual(roles, want) { + t.Fatalf("role order mismatch: got=%v want=%v", roles, want) + } +} diff --git a/services/user-service/internal/storage/mysql/user/force_unban.go b/services/user-service/internal/storage/mysql/user/force_unban.go new file mode 100644 index 00000000..1f06b585 --- /dev/null +++ b/services/user-service/internal/storage/mysql/user/force_unban.go @@ -0,0 +1,171 @@ +package user + +import ( + "context" + "database/sql" + "errors" + "fmt" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + userdomain "hyapp/services/user-service/internal/domain/user" + userservice "hyapp/services/user-service/internal/service/user" +) + +// ForceReleaseUserBans 是后台管理员覆盖全部封禁来源的唯一持久化入口。 +// 用户主状态、管理员封禁、经理封禁和命令幂等事实必须在同一事务提交,避免出现“状态已恢复但封禁仍 active”的中间态。 +func (r *Repository) ForceReleaseUserBans(ctx context.Context, command userservice.AdminForceUnbanCommand) (userservice.AdminForceUnbanResult, error) { + if r == nil || r.db == nil { + return userservice.AdminForceUnbanResult{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + appCode := appcode.FromContext(ctx) + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return userservice.AdminForceUnbanResult{}, err + } + defer func() { _ = tx.Rollback() }() + + // 先锁 users 行,使同一用户的状态切换串行化;事实表的条件 UPDATE 随后锁定当前全部 active 范围。 + // 经理封禁切状态前也会复验其 block 仍 active,因此与本事务并发时不会留下 released block + banned 用户的不一致组合。 + currentStatus, err := lockUserStatus(ctx, tx, appCode, command.TargetUserID) + if err != nil { + return userservice.AdminForceUnbanResult{}, err + } + existing, result, err := claimAdminForceUnbanCommand(ctx, tx, appCode, command) + if err != nil { + return userservice.AdminForceUnbanResult{}, err + } + if existing { + if err := tx.Commit(); err != nil { + return userservice.AdminForceUnbanResult{}, err + } + // 幂等重试返回当前用户状态,但绝不重新扫描 active 事实;首次命令之后的新封禁必须由新的 request_id 显式释放。 + result.User, err = r.GetUser(ctx, command.TargetUserID) + return result, err + } + + releaseReason := fmt.Sprintf("admin_force_unban:%d:%s", command.OperatorAdminID, command.Reason) + adminUpdate, err := tx.ExecContext(ctx, ` + UPDATE admin_user_bans + SET status = ?, released_by_admin_id = ?, released_reason = ?, released_at_ms = ?, updated_at_ms = ? + WHERE app_code = ? AND target_user_id = ? AND status = ?`, + userservice.AdminUserBanStatusReleased, command.OperatorAdminID, releaseReason, command.NowMs, command.NowMs, + appCode, command.TargetUserID, userservice.AdminUserBanStatusActive, + ) + if err != nil { + return userservice.AdminForceUnbanResult{}, err + } + result.ReleasedAdminBanCount, err = adminUpdate.RowsAffected() + if err != nil { + return userservice.AdminForceUnbanResult{}, err + } + + managerUpdate, err := tx.ExecContext(ctx, ` + UPDATE manager_user_blocks + SET status = ?, released_reason = ?, released_at_ms = ?, updated_at_ms = ? + WHERE app_code = ? AND target_user_id = ? AND status = ?`, + userservice.ManagerUserBlockStatusReleased, releaseReason, command.NowMs, command.NowMs, + appCode, command.TargetUserID, userservice.ManagerUserBlockStatusActive, + ) + if err != nil { + return userservice.AdminForceUnbanResult{}, err + } + result.ReleasedManagerBlockCount, err = managerUpdate.RowsAffected() + if err != nil { + return userservice.AdminForceUnbanResult{}, err + } + + newStatus := currentStatus + switch currentStatus { + case userdomain.StatusActive: + // 数据修复场景可能存在 active 用户仍挂 active 封禁事实;释放事实即可,不制造 active -> active 审计噪声。 + case userdomain.StatusBanned, userdomain.StatusDisabled: + newStatus = userdomain.StatusActive + result.StatusRestored = true + if _, err := tx.ExecContext(ctx, ` + UPDATE users SET status = ?, updated_at_ms = ? + WHERE app_code = ? AND user_id = ?`, + string(newStatus), command.NowMs, appCode, command.TargetUserID, + ); err != nil { + return userservice.AdminForceUnbanResult{}, err + } + if _, err := tx.ExecContext(ctx, ` + INSERT INTO user_status_change_logs ( + app_code, target_user_id, old_status, new_status, operator_type, + operator_user_id, reason, request_id, created_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + appCode, command.TargetUserID, string(currentStatus), string(newStatus), userservice.OperatorTypeAdmin, + command.OperatorAdminID, "admin_force_unban", command.RequestID, command.NowMs, + ); err != nil { + return userservice.AdminForceUnbanResult{}, err + } + default: + // 未知主状态不能被强制改写,否则会把未来新增的独立治理状态静默降级为 active。 + return userservice.AdminForceUnbanResult{}, xerr.New(xerr.Conflict, "user status cannot be force unbanned") + } + + if _, err := tx.ExecContext(ctx, ` + UPDATE admin_user_force_unban_commands + SET released_admin_ban_count = ?, released_manager_block_count = ?, + old_status = ?, new_status = ?, status_restored = ?, updated_at_ms = ? + WHERE app_code = ? AND command_id = ?`, + result.ReleasedAdminBanCount, result.ReleasedManagerBlockCount, + string(currentStatus), string(newStatus), result.StatusRestored, command.NowMs, + appCode, command.RequestID, + ); err != nil { + return userservice.AdminForceUnbanResult{}, err + } + if err := tx.Commit(); err != nil { + return userservice.AdminForceUnbanResult{}, err + } + result.User, err = r.GetUser(ctx, command.TargetUserID) + return result, err +} + +// claimAdminForceUnbanCommand 固化一次“释放全部来源”的命令载荷和首次结果。 +// INSERT IGNORE + FOR UPDATE 使并发重试等待首个事务完成,并拒绝同一 request_id 被复用到其他用户或管理员。 +func claimAdminForceUnbanCommand(ctx context.Context, tx *sql.Tx, appCode string, command userservice.AdminForceUnbanCommand) (bool, userservice.AdminForceUnbanResult, error) { + result := userservice.AdminForceUnbanResult{} + insert, err := tx.ExecContext(ctx, ` + INSERT IGNORE INTO admin_user_force_unban_commands ( + app_code, command_id, target_user_id, operator_admin_id, reason, + created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?)`, + appCode, command.RequestID, command.TargetUserID, command.OperatorAdminID, command.Reason, + command.NowMs, command.NowMs, + ) + if err != nil { + return false, result, err + } + inserted, err := insert.RowsAffected() + if err != nil { + return false, result, err + } + if inserted == 1 { + return false, result, nil + } + + var targetUserID, operatorAdminID int64 + var reason string + var restored bool + err = tx.QueryRowContext(ctx, ` + SELECT target_user_id, operator_admin_id, reason, + released_admin_ban_count, released_manager_block_count, status_restored + FROM admin_user_force_unban_commands + WHERE app_code = ? AND command_id = ? + FOR UPDATE`, appCode, command.RequestID).Scan( + &targetUserID, &operatorAdminID, &reason, + &result.ReleasedAdminBanCount, &result.ReleasedManagerBlockCount, &restored, + ) + if errors.Is(err, sql.ErrNoRows) { + return false, result, xerr.New(xerr.Conflict, "admin force unban command disappeared") + } + if err != nil { + return false, result, err + } + if targetUserID != command.TargetUserID || operatorAdminID != command.OperatorAdminID || reason != command.Reason { + return false, result, xerr.New(xerr.Conflict, "admin force unban command payload does not match existing command") + } + result.StatusRestored = restored + return true, result, nil +} diff --git a/services/user-service/internal/storage/mysql/user/moderation.go b/services/user-service/internal/storage/mysql/user/moderation.go index 8c69e19d..f55c3c49 100644 --- a/services/user-service/internal/storage/mysql/user/moderation.go +++ b/services/user-service/internal/storage/mysql/user/moderation.go @@ -13,6 +13,23 @@ import ( userservice "hyapp/services/user-service/internal/service/user" ) +const managerBlockStatusReasonPrefix = "manager_center_block:" + +// ManagerCrossRegionEnabled 读取 active 经理的通用数据范围开关;功能权限仍由 gateway/user host service 分别校验。 +func (r *Repository) ManagerCrossRegionEnabled(ctx context.Context, managerUserID int64) (bool, error) { + var enabled bool + err := r.db.QueryRowContext(ctx, ` + SELECT can_manage_cross_region = 1 + FROM manager_profiles + WHERE app_code = ? AND user_id = ? AND status = 'active' + LIMIT 1 + `, appcode.FromContext(ctx), managerUserID).Scan(&enabled) + if errors.Is(err, sql.ErrNoRows) { + return false, nil + } + return enabled, err +} + // SetUserStatus 原子更新用户主状态,并在非 active 状态下吊销该用户所有 active refresh session。 func (r *Repository) SetUserStatus(ctx context.Context, command userservice.UserStatusCommand) (userservice.UserStatusPersistenceResult, error) { if r == nil || r.db == nil { @@ -41,6 +58,36 @@ func (r *Repository) SetUserStatus(ctx context.Context, command userservice.User } return userservice.UserStatusPersistenceResult{}, err } + if command.Status == userdomain.StatusActive { + activeBanCount, err := countActiveManagedBans(ctx, tx, command.TargetUserID) + if err != nil { + return userservice.UserStatusPersistenceResult{}, err + } + if activeBanCount > 0 { + // 所有恢复入口都必须尊重 admin/manager 独立封禁事实;不能通过旧 SetUserStatus(active) 绕过新状态机。 + return userservice.UserStatusPersistenceResult{}, xerr.New(xerr.Conflict, "user still has active ban facts") + } + } + if command.Status == userdomain.StatusBanned && strings.HasPrefix(command.Reason, managerBlockStatusReasonPrefix) { + blockID := strings.TrimSpace(strings.TrimPrefix(command.Reason, managerBlockStatusReasonPrefix)) + var blockStatus string + err := tx.QueryRowContext(ctx, ` + SELECT status + FROM manager_user_blocks + WHERE app_code = ? AND block_id = ? AND target_user_id = ? + FOR UPDATE`, appCode, blockID, command.TargetUserID).Scan(&blockStatus) + if errors.Is(err, sql.ErrNoRows) { + // 经理封禁先写 block 再切用户状态;后台可能已在两步之间强制释放该 block,此时不能重新制造无 active 事实的 banned 状态。 + return userservice.UserStatusPersistenceResult{}, xerr.New(xerr.Conflict, "manager block is no longer active") + } + if err != nil { + return userservice.UserStatusPersistenceResult{}, err + } + if blockStatus != userservice.ManagerUserBlockStatusActive { + // block 已被经理自行解除、自动到期或后台强制释放时,后到达的状态写入必须作为冲突终止。 + return userservice.UserStatusPersistenceResult{}, xerr.New(xerr.Conflict, "manager block is no longer active") + } + } _, err = tx.ExecContext(ctx, ` UPDATE users @@ -163,7 +210,7 @@ func (r *Repository) ListManagerUserBlocks(ctx context.Context, managerUserID in } func (r *Repository) ReleaseManagerUserBlock(ctx context.Context, command userservice.ReleaseManagerUserBlockCommand) (userservice.ManagerUserBlockRelease, error) { - return r.releaseManagerUserBlock(ctx, command.BlockID, command.ManagerUserID, command.Status, command.Reason, command.NowMS) + return r.releaseManagerUserBlock(ctx, command.BlockID, command.ManagerUserID, command.Status, command.Reason, command.RequestID, command.NowMS) } func (r *Repository) ExpireManagerUserBlocks(ctx context.Context, nowMS int64, limit int32) ([]userservice.ManagerUserBlockRelease, error) { @@ -202,7 +249,7 @@ func (r *Repository) ExpireManagerUserBlocks(ctx context.Context, nowMS int64, l releases := make([]userservice.ManagerUserBlockRelease, 0, len(pending)) for _, item := range pending { // 逐条释放而不是批量 UPDATE,是为了给每个目标用户单独计算是否还存在其他 active 经理封禁。 - release, err := r.releaseManagerUserBlock(ctx, item.blockID, item.managerUserID, userservice.ManagerUserBlockStatusExpired, "manager_center_block_expired", nowMS) + release, err := r.releaseManagerUserBlock(ctx, item.blockID, item.managerUserID, userservice.ManagerUserBlockStatusExpired, "manager_center_block_expired", "manager_block_expire:"+item.blockID, nowMS) if err != nil { return releases, err } @@ -211,7 +258,7 @@ func (r *Repository) ExpireManagerUserBlocks(ctx context.Context, nowMS int64, l return releases, nil } -func (r *Repository) releaseManagerUserBlock(ctx context.Context, blockID string, managerUserID int64, status string, reason string, nowMS int64) (userservice.ManagerUserBlockRelease, error) { +func (r *Repository) releaseManagerUserBlock(ctx context.Context, blockID string, managerUserID int64, status string, reason string, requestID string, nowMS int64) (userservice.ManagerUserBlockRelease, error) { tx, err := r.db.BeginTx(ctx, nil) if err != nil { return userservice.ManagerUserBlockRelease{}, err @@ -225,7 +272,12 @@ func (r *Repository) releaseManagerUserBlock(ctx context.Context, blockID string if err := tx.Commit(); err != nil { return userservice.ManagerUserBlockRelease{}, err } - return userservice.ManagerUserBlockRelease{Block: block}, nil + user, err := r.GetUser(ctx, block.TargetUserID) + return userservice.ManagerUserBlockRelease{Block: block, User: user}, err + } + currentStatus, err := lockUserStatus(ctx, tx, appcode.FromContext(ctx), block.TargetUserID) + if err != nil { + return userservice.ManagerUserBlockRelease{}, err } _, err = tx.ExecContext(ctx, ` UPDATE manager_user_blocks @@ -238,21 +290,29 @@ func (r *Repository) releaseManagerUserBlock(ctx context.Context, blockID string } block.Status = status block.UpdatedAtMS = nowMS - activeCount, err := r.countActiveManagerBlocks(ctx, tx, block.TargetUserID) - if err != nil { - return userservice.ManagerUserBlockRelease{}, err - } - // 只在“没有其他经理封禁 + 原状态是 active + 最近一次 ban 由经理封禁写入”时恢复,避免误解后台封禁。 - latestManagerBan, err := r.latestBanBelongsToManagerBlock(ctx, tx, block.TargetUserID) + restored, err := restoreUserAfterManagedBanRelease(ctx, tx, managedBanRestoreCommand{ + TargetUserID: block.TargetUserID, + CurrentStatus: currentStatus, + OperatorType: managerReleaseOperatorType(status), + OperatorID: managerUserID, + Reason: managerReleaseStatusReason(status, block.BlockID), + RequestID: requestID, + NowMs: nowMS, + }) if err != nil { return userservice.ManagerUserBlockRelease{}, err } if err := tx.Commit(); err != nil { return userservice.ManagerUserBlockRelease{}, err } + user, err := r.GetUser(ctx, block.TargetUserID) + if err != nil { + return userservice.ManagerUserBlockRelease{}, err + } return userservice.ManagerUserBlockRelease{ Block: block, - ShouldRestore: activeCount == 0 && block.TargetOldStatus == userdomain.StatusActive && latestManagerBan, + User: user, + ShouldRestore: restored, }, nil } @@ -275,34 +335,18 @@ func (r *Repository) getManagerUserBlockForUpdate(ctx context.Context, tx *sql.T return scanManagerUserBlock(row) } -func (r *Repository) countActiveManagerBlocks(ctx context.Context, tx *sql.Tx, targetUserID int64) (int64, error) { - var count int64 - err := tx.QueryRowContext(ctx, ` - SELECT COUNT(*) - FROM manager_user_blocks - WHERE app_code = ? AND target_user_id = ? AND status = 'active'`, - appcode.FromContext(ctx), targetUserID, - ).Scan(&count) - return count, err +func managerReleaseOperatorType(status string) string { + if status == userservice.ManagerUserBlockStatusExpired { + return userservice.OperatorTypeSystem + } + return userservice.OperatorTypeAppUser } -func (r *Repository) latestBanBelongsToManagerBlock(ctx context.Context, tx *sql.Tx, targetUserID int64) (bool, error) { - var reason string - err := tx.QueryRowContext(ctx, ` - SELECT reason - FROM user_status_change_logs - WHERE app_code = ? AND target_user_id = ? AND new_status = 'banned' - ORDER BY created_at_ms DESC, id DESC - LIMIT 1`, - appcode.FromContext(ctx), targetUserID, - ).Scan(&reason) - if errors.Is(err, sql.ErrNoRows) { - return false, nil +func managerReleaseStatusReason(status string, blockID string) string { + if status == userservice.ManagerUserBlockStatusExpired { + return "manager_center_block_expired:" + blockID } - if err != nil { - return false, err - } - return strings.HasPrefix(reason, "manager_center_block:"), nil + return "manager_center_unblock:" + blockID } func managerBlockSelectSQL() string { diff --git a/services/user-service/internal/storage/mysql/user/repository.go b/services/user-service/internal/storage/mysql/user/repository.go index b878b571..cf9e4d5c 100644 --- a/services/user-service/internal/storage/mysql/user/repository.go +++ b/services/user-service/internal/storage/mysql/user/repository.go @@ -294,7 +294,8 @@ func insertUserRegionChangedOutbox(ctx context.Context, tx *sql.Tx, command user if err != nil { return err } - eventID := fmt.Sprintf("UserRegionChanged:%s:%d:%d:%d:%d", source, command.UserID, oldRegionID, command.NewRegionID, command.ChangedAtMs) + // 普通用户没有修改冷却期;同一毫秒内连续切换同一区域的两个国家时,国家码必须参与幂等键,避免第二条事实被 INSERT IGNORE 吞掉。 + eventID := fmt.Sprintf("UserRegionChanged:%s:%d:%s:%s:%d:%d:%d", source, command.UserID, userdomain.NormalizeCountryCode(oldCountry), command.NewCountry, oldRegionID, command.NewRegionID, command.ChangedAtMs) _, err = tx.ExecContext(ctx, ` INSERT IGNORE INTO user_outbox (app_code, event_id, event_type, aggregate_type, aggregate_id, status, payload_json, created_at_ms, updated_at_ms) VALUES (?, ?, 'UserRegionChanged', 'user', ?, 'pending', ?, ?, ?) diff --git a/services/user-service/internal/storage/mysql/user/social_repository.go b/services/user-service/internal/storage/mysql/user/social_repository.go index 34591bee..e03f53c6 100644 --- a/services/user-service/internal/storage/mysql/user/social_repository.go +++ b/services/user-service/internal/storage/mysql/user/social_repository.go @@ -136,6 +136,15 @@ func (r *Repository) FollowUser(ctx context.Context, followerUserID int64, follo } } if shouldIncrement { + // 新增粉丝按事件净增统计;状态未变化的重复关注不写事件,避免客户端重试重复 +1。 + if _, err := tx.ExecContext(ctx, ` + INSERT INTO user_follow_events ( + app_code, follower_user_id, followee_user_id, delta, occurred_at_ms + ) VALUES (?, ?, ?, 1, ?)`, + appCode, followerUserID, followeeUserID, nowMs, + ); err != nil { + return userdomain.ProfileStats{}, err + } if _, err := tx.ExecContext(ctx, ` UPDATE user_profile_stats SET following_count = following_count + 1, updated_at_ms = ? @@ -176,6 +185,15 @@ func (r *Repository) UnfollowUser(ctx context.Context, followerUserID int64, fol ); err != nil { return userdomain.ProfileStats{}, err } + // 取消关注写 -1,确保筛选区间内的新增粉丝展示为净增而不是只累计关注动作。 + if _, err := tx.ExecContext(ctx, ` + INSERT INTO user_follow_events ( + app_code, follower_user_id, followee_user_id, delta, occurred_at_ms + ) VALUES (?, ?, ?, -1, ?)`, + appCode, followerUserID, followeeUserID, nowMs, + ); err != nil { + return userdomain.ProfileStats{}, err + } if _, err := tx.ExecContext(ctx, ` UPDATE user_profile_stats SET following_count = GREATEST(following_count - 1, 0), updated_at_ms = ? diff --git a/services/user-service/internal/storage/mysql/user_outbox.go b/services/user-service/internal/storage/mysql/user_outbox.go index 047eb457..005f57ab 100644 --- a/services/user-service/internal/storage/mysql/user_outbox.go +++ b/services/user-service/internal/storage/mysql/user_outbox.go @@ -25,6 +25,29 @@ type UserOutboxRecord struct { LockUntilMS int64 } +// ListUserOutboxAppCodes enumerates real tenant keys from the owner table. +// PRIMARY begins with app_code, and the worker caches this discovery for five +// minutes; normal claims continue to use status/lock indexes within one App. +func (r *Repository) ListUserOutboxAppCodes(ctx context.Context) ([]string, error) { + if r == nil || r.db == nil { + return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + rows, err := r.db.QueryContext(ctx, `SELECT DISTINCT app_code FROM user_outbox FORCE INDEX (PRIMARY) ORDER BY app_code`) + if err != nil { + return nil, err + } + defer rows.Close() + appCodes := make([]string, 0, 4) + for rows.Next() { + var appCode string + if err := rows.Scan(&appCode); err != nil { + return nil, err + } + appCodes = append(appCodes, appCode) + } + return appCodes, rows.Err() +} + // ClaimPendingUserOutbox locks pending user facts so one worker can publish them. func (r *Repository) ClaimPendingUserOutbox(ctx context.Context, workerID string, batchSize int) ([]UserOutboxRecord, error) { if r == nil || r.db == nil { @@ -46,20 +69,14 @@ func (r *Repository) ClaimPendingUserOutbox(ctx context.Context, workerID string lockUntilMS := nowMS + (30 * time.Second).Milliseconds() appCode := appcode.FromContext(ctx) records := make([]UserOutboxRecord, 0, batchSize) + // Recovery work must claim before fresh pending facts. A continuously growing + // pending backlog can otherwise fill every batch and permanently strand both + // due retries and running rows whose worker lease expired. Keep the states in + // separate SKIP LOCKED queries so each branch retains its status-leading index. claimBranches := []struct { query string args []any }{ - { - query: ` - SELECT app_code, event_id, event_type, aggregate_type, aggregate_id, CAST(payload_json AS CHAR), created_at_ms - FROM user_outbox FORCE INDEX (idx_user_outbox_status_created) - WHERE app_code = ? AND status = 'pending' AND next_retry_at_ms <= ? AND lock_until_ms = 0 - ORDER BY created_at_ms ASC, event_id ASC - LIMIT ? FOR UPDATE SKIP LOCKED - `, - args: []any{appCode, nowMS}, - }, { query: ` SELECT app_code, event_id, event_type, aggregate_type, aggregate_id, CAST(payload_json AS CHAR), created_at_ms @@ -77,6 +94,16 @@ func (r *Repository) ClaimPendingUserOutbox(ctx context.Context, workerID string WHERE app_code = ? AND status = 'running' AND lock_until_ms <= ? ORDER BY created_at_ms ASC, event_id ASC LIMIT ? FOR UPDATE SKIP LOCKED + `, + args: []any{appCode, nowMS}, + }, + { + query: ` + SELECT app_code, event_id, event_type, aggregate_type, aggregate_id, CAST(payload_json AS CHAR), created_at_ms + FROM user_outbox FORCE INDEX (idx_user_outbox_status_created) + WHERE app_code = ? AND status = 'pending' AND next_retry_at_ms <= ? AND lock_until_ms = 0 + ORDER BY created_at_ms ASC, event_id ASC + LIMIT ? FOR UPDATE SKIP LOCKED `, args: []any{appCode, nowMS}, }, diff --git a/services/user-service/internal/storage/mysql/user_outbox_app_codes_test.go b/services/user-service/internal/storage/mysql/user_outbox_app_codes_test.go new file mode 100644 index 00000000..02dc9a65 --- /dev/null +++ b/services/user-service/internal/storage/mysql/user_outbox_app_codes_test.go @@ -0,0 +1,30 @@ +package mysql + +import ( + "context" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestListUserOutboxAppCodesForcesPrimaryPrefixIndex(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT app_code FROM user_outbox FORCE INDEX (PRIMARY) ORDER BY app_code`)). + WillReturnRows(sqlmock.NewRows([]string{"app_code"}).AddRow("fami").AddRow("huwaa").AddRow("lalu")) + + apps, err := (&Repository{db: db}).ListUserOutboxAppCodes(context.Background()) + if err != nil { + t.Fatalf("list user outbox apps: %v", err) + } + if len(apps) != 3 || apps[0] != "fami" || apps[1] != "huwaa" || apps[2] != "lalu" { + t.Fatalf("user outbox apps mismatch: %v", apps) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("user app discovery query mismatch: %v", err) + } +} diff --git a/services/user-service/internal/storage/mysql/user_outbox_claim_priority_test.go b/services/user-service/internal/storage/mysql/user_outbox_claim_priority_test.go new file mode 100644 index 00000000..53b7eed0 --- /dev/null +++ b/services/user-service/internal/storage/mysql/user_outbox_claim_priority_test.go @@ -0,0 +1,79 @@ +package mysql + +import ( + "context" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "hyapp/pkg/appcode" +) + +func TestClaimPendingUserOutboxPrioritizesRecoveryOverPendingAtBatchOne(t *testing.T) { + tests := []struct { + name string + eventID string + recoveryStatus string + expectEmptyRetry bool + }{ + {name: "due retry", eventID: "user-retry", recoveryStatus: "retryable"}, + {name: "expired run", eventID: "user-expired", recoveryStatus: "running", expectEmptyRetry: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + + mock.ExpectBegin() + if tt.expectEmptyRetry { + mock.ExpectQuery(userOutboxRetryClaimPattern()). + WithArgs("lalu", sqlmock.AnyArg(), 1). + WillReturnRows(userOutboxClaimRows()) + } + claimPattern := userOutboxRetryClaimPattern() + if tt.recoveryStatus == "running" { + claimPattern = userOutboxExpiredClaimPattern() + } + mock.ExpectQuery(claimPattern). + WithArgs("lalu", sqlmock.AnyArg(), 1). + WillReturnRows(userOutboxClaimRows().AddRow( + "lalu", tt.eventID, "UserRegistered", "user", int64(1001), `{}`, int64(200), + )) + mock.ExpectExec(`(?s)UPDATE user_outbox\s+SET status = 'running', worker_id = \?, lock_until_ms = \?, updated_at_ms = \?\s+WHERE app_code = \? AND event_id = \?`). + WithArgs("worker-1", sqlmock.AnyArg(), sqlmock.AnyArg(), "lalu", tt.eventID). + WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectCommit() + + records, err := (&Repository{db: db}).ClaimPendingUserOutbox( + appcode.WithContext(context.Background(), "lalu"), "worker-1", 1, + ) + if err != nil { + t.Fatalf("claim user recovery row: %v", err) + } + if len(records) != 1 || records[0].EventID != tt.eventID { + t.Fatalf("batch=1 must claim %s before an older pending row: %+v", tt.recoveryStatus, records) + } + // No pending query is registered: issuing one after the recovery row filled + // the batch makes sqlmock fail, protecting the starvation boundary directly. + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("user claim priority mismatch: %v", err) + } + }) + } +} + +func userOutboxClaimRows() *sqlmock.Rows { + return sqlmock.NewRows([]string{ + "app_code", "event_id", "event_type", "aggregate_type", "aggregate_id", "payload_json", "created_at_ms", + }) +} + +func userOutboxRetryClaimPattern() string { + return `(?s)FROM user_outbox FORCE INDEX \(idx_user_outbox_status_created\).*WHERE app_code = \? AND status = 'retryable' AND next_retry_at_ms <= \? AND lock_until_ms = 0.*FOR UPDATE SKIP LOCKED` +} + +func userOutboxExpiredClaimPattern() string { + return `(?s)FROM user_outbox FORCE INDEX \(idx_user_outbox_lock\).*WHERE app_code = \? AND status = 'running' AND lock_until_ms <= \?.*FOR UPDATE SKIP LOCKED` +} diff --git a/services/user-service/internal/testutil/mysqltest/mysqltest.go b/services/user-service/internal/testutil/mysqltest/mysqltest.go index 8d1864ca..41ab04ee 100644 --- a/services/user-service/internal/testutil/mysqltest/mysqltest.go +++ b/services/user-service/internal/testutil/mysqltest/mysqltest.go @@ -129,6 +129,11 @@ func (r *Repository) BatchGetUsers(ctx context.Context, userIDs []int64) (map[in return r.Repository.UserRepository().BatchGetUsers(ctx, userIDs) } +// BatchGetUserAdminProfiles 让跨包 MySQL 测试继续复用真实后台投影查询。 +func (r *Repository) BatchGetUserAdminProfiles(ctx context.Context, userIDs []int64, nowMs int64) (map[int64]userdomain.AdminProfile, error) { + return r.Repository.UserRepository().BatchGetUserAdminProfiles(ctx, userIDs, nowMs) +} + // BatchGetRoomBasicUsers 让测试 wrapper 继续满足房间首屏基础资料查询接口。 func (r *Repository) BatchGetRoomBasicUsers(ctx context.Context, userIDs []int64) (map[int64]userdomain.RoomBasicUser, error) { return r.Repository.UserRepository().BatchGetRoomBasicUsers(ctx, userIDs) @@ -349,6 +354,16 @@ func (r *Repository) FindActiveSessionByRefreshHash(ctx context.Context, refresh return r.Repository.AuthRepository().FindActiveSessionByRefreshHash(ctx, refreshTokenHash, nowMs) } +// FindLatestActiveSessionByUser 让测试 wrapper 继续满足认证接口。 +func (r *Repository) FindLatestActiveSessionByUser(ctx context.Context, userID int64, nowMs int64) (authdomain.Session, error) { + return r.Repository.AuthRepository().FindLatestActiveSessionByUser(ctx, userID, nowMs) +} + +// UpsertUserDevice 让测试 wrapper 继续满足认证接口。 +func (r *Repository) UpsertUserDevice(ctx context.Context, device authdomain.UserDevice) error { + return r.Repository.AuthRepository().UpsertUserDevice(ctx, device) +} + // FindActiveSessionByID 让测试 wrapper 继续满足认证接口。 func (r *Repository) FindActiveSessionByID(ctx context.Context, sessionID string, nowMs int64) (authdomain.Session, error) { return r.Repository.AuthRepository().FindActiveSessionByID(ctx, sessionID, nowMs) @@ -824,9 +839,9 @@ func (r *Repository) PutManagerProfile(profile hostdomain.ManagerProfile) { _, err := r.schema.DB.ExecContext(context.Background(), ` INSERT INTO manager_profiles ( user_id, status, contact_info, can_grant_avatar_frame, can_grant_vehicle, can_grant_badge, - can_grant_vip, can_update_user_level, can_add_bd_leader, can_add_admin, can_add_superadmin, can_block_user, can_transfer_user_country, + can_grant_vip, can_update_user_level, can_add_bd_leader, can_manage_cross_region, can_add_admin, can_add_superadmin, can_block_user, can_transfer_user_country, created_by_admin_id, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE status = VALUES(status), contact_info = VALUES(contact_info), @@ -836,6 +851,7 @@ func (r *Repository) PutManagerProfile(profile hostdomain.ManagerProfile) { can_grant_vip = VALUES(can_grant_vip), can_update_user_level = VALUES(can_update_user_level), can_add_bd_leader = VALUES(can_add_bd_leader), + can_manage_cross_region = VALUES(can_manage_cross_region), can_add_admin = VALUES(can_add_admin), can_add_superadmin = VALUES(can_add_superadmin), can_block_user = VALUES(can_block_user), @@ -843,7 +859,7 @@ func (r *Repository) PutManagerProfile(profile hostdomain.ManagerProfile) { updated_at_ms = VALUES(updated_at_ms) `, profile.UserID, profile.Status, profile.Contact, defaultTrue(profile.CanGrantAvatarFrame), defaultTrue(profile.CanGrantVehicle), defaultTrue(profile.CanGrantBadge), defaultTrue(profile.CanGrantVIP), - defaultTrue(profile.CanUpdateUserLevel), defaultTrue(profile.CanAddBDLeader), defaultTrue(profile.CanAddAdmin), + defaultTrue(profile.CanUpdateUserLevel), defaultTrue(profile.CanAddBDLeader), profile.CanManageCrossRegion, defaultTrue(profile.CanAddAdmin), defaultTrue(profile.CanAddSuperadmin), defaultTrue(profile.CanBlockUser), defaultTrue(profile.CanTransferCountry), profile.CreatedByAdminID, profile.CreatedAtMs, profile.UpdatedAtMs) if err != nil { @@ -851,6 +867,19 @@ func (r *Repository) PutManagerProfile(profile hostdomain.ManagerProfile) { } } +func (r *Repository) SetManagerCrossRegion(userID int64, enabled bool) { + r.t.Helper() + + _, err := r.schema.DB.ExecContext(context.Background(), ` + UPDATE manager_profiles + SET can_manage_cross_region = ? + WHERE user_id = ? + `, enabled, userID) + if err != nil { + r.t.Fatalf("set manager region BD Leader permission %d failed: %v", userID, err) + } +} + func (r *Repository) SetManagerGrantVIP(userID int64, enabled bool) { r.t.Helper() diff --git a/services/user-service/internal/transport/grpc/admin_convert_test.go b/services/user-service/internal/transport/grpc/admin_convert_test.go new file mode 100644 index 00000000..4c27ebd8 --- /dev/null +++ b/services/user-service/internal/transport/grpc/admin_convert_test.go @@ -0,0 +1,63 @@ +package grpc + +import ( + "reflect" + "testing" + + hostdomain "hyapp/services/user-service/internal/domain/host" + userdomain "hyapp/services/user-service/internal/domain/user" + userservice "hyapp/services/user-service/internal/service/user" +) + +func TestToProtoUserAdminProfilePreservesOwnerFacts(t *testing.T) { + profile := userdomain.AdminProfile{ + User: userdomain.User{UserID: 10001, Status: userdomain.StatusBanned}, + BirthDate: "2000-02-03", + RegisterDevice: "Pixel 9", + LastSuccessLoginAtMs: 4_000, + Roles: []string{userdomain.AdminRoleHost, userdomain.AdminRoleManager}, + Ban: userdomain.ActiveBanSummary{ + Active: true, Source: userdomain.BanSourceAdmin, BanID: "admin-ban-1", + Permanent: true, UserStatus: userdomain.StatusBanned, OperatorType: "admin", + OperatorUserID: 9001, ActiveBanCount: 2, + }, + LastOperatorType: "admin", + LastOperatorUserID: 9001, + LastOperationReason: "admin_user_ban:admin-ban-1", + LastOperationAtMs: 4_000, + } + + got := toProtoUserAdminProfile(profile) + if got.GetUser().GetUserId() != 10001 || got.GetBirth() != profile.BirthDate || got.GetRegisterDevice() != profile.RegisterDevice { + t.Fatalf("admin profile base projection mismatch: %+v", got) + } + if !reflect.DeepEqual(got.GetRoles(), profile.Roles) || !got.GetBan().GetPermanent() || got.GetBan().GetActiveBanCount() != 2 { + t.Fatalf("admin profile role/ban projection mismatch: %+v", got) + } +} + +func TestToProtoAdminUserBanMarksZeroExpiryPermanent(t *testing.T) { + got := toProtoAdminUserBan(userservice.AdminUserBan{ + BanID: "admin-ban-1", TargetUserID: 10001, ExpiresAtMs: 0, Status: userservice.AdminUserBanStatusActive, + }) + if got == nil || !got.GetPermanent() || got.GetExpiresAtMs() != 0 { + t.Fatalf("zero expiry must be exposed as permanent: %+v", got) + } +} + +func TestRoleNamesFromSummaryKeepsStableMultiRoleOrder(t *testing.T) { + roles := roleNamesFromSummary(hostdomain.UserRoleSummary{ + IsHost: true, IsAgency: true, IsBD: true, IsBDLeader: true, IsCoinSeller: true, IsManager: true, + }) + want := []string{ + userdomain.AdminRoleHost, + userdomain.AdminRoleAgency, + userdomain.AdminRoleBD, + userdomain.AdminRoleBDLeader, + userdomain.AdminRoleCoinSeller, + userdomain.AdminRoleManager, + } + if !reflect.DeepEqual(roles, want) { + t.Fatalf("role order mismatch: got=%v want=%v", roles, want) + } +} diff --git a/services/user-service/internal/transport/grpc/auth_meta_test.go b/services/user-service/internal/transport/grpc/auth_meta_test.go new file mode 100644 index 00000000..b0e86de1 --- /dev/null +++ b/services/user-service/internal/transport/grpc/auth_meta_test.go @@ -0,0 +1,21 @@ +package grpc + +import ( + "testing" + + userv1 "hyapp.local/api/proto/user/v1" +) + +func TestAuthMetaMapsClientVersionSnapshot(t *testing.T) { + meta := authMeta(&userv1.RequestMeta{ + RequestId: "req-version", + AppCode: "lalu", + AppVersion: "2.4.0", + BuildNumber: "24001", + }) + + // 密码登录没有独立的版本字段,只能依赖 RequestMeta;这里防止 transport 后续新增字段时漏掉审计映射。 + if meta.AppVersion != "2.4.0" || meta.BuildNumber != "24001" { + t.Fatalf("client version meta mismatch: %+v", meta) + } +} diff --git a/services/user-service/internal/transport/grpc/convert.go b/services/user-service/internal/transport/grpc/convert.go index 3b534d04..a78e9df6 100644 --- a/services/user-service/internal/transport/grpc/convert.go +++ b/services/user-service/internal/transport/grpc/convert.go @@ -126,6 +126,61 @@ func toProtoManagerUserBlock(block userservice.ManagerUserBlock) *userv1.Manager } } +func toProtoAdminUserBan(ban userservice.AdminUserBan) *userv1.AdminUserBan { + if ban.BanID == "" { + return nil + } + return &userv1.AdminUserBan{ + BanId: ban.BanID, + CommandId: ban.CommandID, + TargetUserId: ban.TargetUserID, + ExpiresAtMs: ban.ExpiresAtMs, + Permanent: ban.ExpiresAtMs == 0, + Status: ban.Status, + Reason: ban.Reason, + OperatorAdminId: ban.OperatorAdminID, + CreatedAtMs: ban.CreatedAtMs, + UpdatedAtMs: ban.UpdatedAtMs, + ReleasedByAdminId: ban.ReleasedByAdminID, + ReleasedReason: ban.ReleasedReason, + ReleasedAtMs: ban.ReleasedAtMs, + } +} + +func toProtoUserAdminProfile(profile userdomain.AdminProfile) *userv1.UserAdminProfile { + return &userv1.UserAdminProfile{ + User: toProtoUser(profile.User), + Birth: profile.BirthDate, + RegisterDevice: profile.RegisterDevice, + LastSuccessLoginAtMs: profile.LastSuccessLoginAtMs, + Roles: append([]string(nil), profile.Roles...), + Ban: toProtoActiveUserBanSummary(profile.Ban), + LastOperatorType: profile.LastOperatorType, + LastOperatorUserId: profile.LastOperatorUserID, + LastOperationReason: profile.LastOperationReason, + LastOperationAtMs: profile.LastOperationAtMs, + } +} + +func toProtoActiveUserBanSummary(summary userdomain.ActiveBanSummary) *userv1.ActiveUserBanSummary { + if !summary.Active { + return nil + } + return &userv1.ActiveUserBanSummary{ + Active: true, + Source: summary.Source, + BanId: summary.BanID, + Permanent: summary.Permanent, + ExpiresAtMs: summary.ExpiresAtMs, + UserStatus: toProtoStatus(summary.UserStatus), + OperatorType: summary.OperatorType, + OperatorUserId: summary.OperatorUserID, + Reason: summary.Reason, + CreatedAtMs: summary.CreatedAtMs, + ActiveBanCount: summary.ActiveBanCount, + } +} + func toProtoInviteOverview(overview invitedomain.Overview) *userv1.InviteOverview { if overview.MyInviteCode == "" && !overview.InviteEnabled && overview.InviteCount == 0 && overview.ValidInviteCount == 0 && overview.ValidInviteThresholdCoin == 0 { return nil @@ -388,14 +443,20 @@ func authMeta(meta *userv1.RequestMeta) authservice.Meta { // 只提取认证审计需要的字段,device_id 在各 RPC 中单独传入。 return authservice.Meta{ - AppCode: meta.GetAppCode(), - RequestID: meta.GetRequestId(), - ClientIP: meta.GetClientIp(), - UserAgent: meta.GetUserAgent(), - CountryByIP: meta.GetCountryByIp(), - Platform: meta.GetPlatform(), - Language: meta.GetLanguage(), - Timezone: meta.GetTimezone(), + AppCode: meta.GetAppCode(), + RequestID: meta.GetRequestId(), + ClientIP: meta.GetClientIp(), + UserAgent: meta.GetUserAgent(), + CountryByIP: meta.GetCountryByIp(), + Platform: meta.GetPlatform(), + AppVersion: meta.GetAppVersion(), + BuildNumber: meta.GetBuildNumber(), + Language: meta.GetLanguage(), + Timezone: meta.GetTimezone(), + DeviceModel: meta.GetDeviceModel(), + DeviceManufacturer: meta.GetDeviceManufacturer(), + OSVersion: meta.GetOsVersion(), + IMEI: meta.GetImei(), } } @@ -553,9 +614,33 @@ func toProtoUserRoleSummary(summary hostdomain.UserRoleSummary) *userv1.UserRole BdStatus: summary.BDStatus, CoinSellerStatus: summary.CoinSellerStatus, PendingRoleInvitations: summary.PendingRoleInvitations, + Roles: roleNamesFromSummary(summary), } } +func roleNamesFromSummary(summary hostdomain.UserRoleSummary) []string { + roles := make([]string, 0, 6) + if summary.IsHost { + roles = append(roles, userdomain.AdminRoleHost) + } + if summary.IsAgency { + roles = append(roles, userdomain.AdminRoleAgency) + } + if summary.IsBD { + roles = append(roles, userdomain.AdminRoleBD) + } + if summary.IsBDLeader { + roles = append(roles, userdomain.AdminRoleBDLeader) + } + if summary.IsCoinSeller { + roles = append(roles, userdomain.AdminRoleCoinSeller) + } + if summary.IsManager { + roles = append(roles, userdomain.AdminRoleManager) + } + return roles +} + // toProtoAgencyMembership 把 Agency membership 事实转换为内部 gRPC 投影。 func toProtoAgencyMembership(membership hostdomain.AgencyMembership) *userv1.AgencyMembership { if membership.MembershipID == 0 { diff --git a/services/user-service/internal/transport/grpc/cron.go b/services/user-service/internal/transport/grpc/cron.go index 0aac7845..c6d8a721 100644 --- a/services/user-service/internal/transport/grpc/cron.go +++ b/services/user-service/internal/transport/grpc/cron.go @@ -9,6 +9,7 @@ import ( userdomain "hyapp/services/user-service/internal/domain/user" authservice "hyapp/services/user-service/internal/service/auth" mictimeservice "hyapp/services/user-service/internal/service/mictime" + roomtimeservice "hyapp/services/user-service/internal/service/roomtime" userservice "hyapp/services/user-service/internal/service/user" ) @@ -82,6 +83,28 @@ func (s *Server) CompensateMicOpenSessions(ctx context.Context, req *userv1.Cron }, nil } +// CompensateRoomOpenSessions 关闭异常长时间未收到 Left/Kicked 事件的用户在房统计 session,不反写 Room Cell。 +func (s *Server) CompensateRoomOpenSessions(ctx context.Context, req *userv1.CronBatchRequest) (*userv1.CronBatchResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + if s.roomTimeSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "room time service is not configured")) + } + result, err := s.roomTimeSvc.ProcessOpenSessionCompensationBatch(ctx, roomtimeservice.CompensationWorkerOptions{ + WorkerID: req.GetWorkerId(), + BatchSize: int(req.GetBatchSize()), + OpenSessionMaxAge: durationFromMillis(req.GetOpenSessionMaxAgeMs()), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &userv1.CronBatchResponse{ + ClaimedCount: int32(result.ClosedCount), + ProcessedCount: int32(result.ClosedCount + result.OrphanLeavesDeleted), + SuccessCount: int32(result.ClosedCount), + HasMore: result.ClosedCount >= normalizedBatchSize(req.GetBatchSize(), 200), + }, nil +} + // ExpireManagerUserBlocks 释放到期的经理封禁记录;真正是否恢复 users.status 由 user-service 内部封禁来源判断。 func (s *Server) ExpireManagerUserBlocks(ctx context.Context, req *userv1.CronBatchRequest) (*userv1.CronBatchResponse, error) { ctx = contextWithApp(ctx, req.GetMeta()) @@ -100,6 +123,24 @@ func (s *Server) ExpireManagerUserBlocks(ctx context.Context, req *userv1.CronBa }, nil } +// ExpireAdminUserBans 释放到期的后台管理员限时封禁;永久封禁和最终恢复判断都由 user-service 持有。 +func (s *Server) ExpireAdminUserBans(ctx context.Context, req *userv1.CronBatchRequest) (*userv1.CronBatchResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + if s.userSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "user service is not configured")) + } + processed, err := s.userSvc.ExpireAdminUserBans(ctx, req.GetBatchSize()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &userv1.CronBatchResponse{ + ClaimedCount: int32(processed), + ProcessedCount: int32(processed), + SuccessCount: int32(processed), + HasMore: processed >= normalizedBatchSize(req.GetBatchSize(), 100), + }, nil +} + // RefreshCPIntimacyLeaderboard 重建 CP 亲密值排行榜 Redis zset;cron-service 只负责调度和租约。 func (s *Server) RefreshCPIntimacyLeaderboard(ctx context.Context, req *userv1.CronBatchRequest) (*userv1.CronBatchResponse, error) { // cron-service 每个 app_code 单独触发一次;这里把 app_code 放进 context 后,MySQL 查询和 Redis key 都会按租户隔离。 diff --git a/services/user-service/internal/transport/grpc/host.go b/services/user-service/internal/transport/grpc/host.go index 6c2dbce8..f986a88c 100644 --- a/services/user-service/internal/transport/grpc/host.go +++ b/services/user-service/internal/transport/grpc/host.go @@ -3,6 +3,7 @@ package grpc import ( "context" + "hyapp/pkg/appcode" "hyapp/pkg/xerr" hostservice "hyapp/services/user-service/internal/service/host" @@ -104,7 +105,7 @@ func (s *Server) InviteAgency(ctx context.Context, req *userv1.InviteAgencyReque return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured")) } ctx = contextWithApp(ctx, req.GetMeta()) - // 邀请人的 BD 身份和目标用户的区域/身份冲突都由业务事务校验。 + // 邀请人的 BD 身份、App 级区域策略和目标身份冲突都由业务事务校验。 invitation, err := s.hostSvc.InviteAgency(ctx, hostservice.InviteAgencyInput{ CommandID: req.GetCommandId(), InviterUserID: req.GetInviterUserId(), @@ -539,6 +540,21 @@ func (s *Server) CheckBusinessCapability(ctx context.Context, req *userv1.CheckB return &userv1.CheckBusinessCapabilityResponse{Allowed: allowed, Reason: reason}, nil } +// GetRoleScopePolicy 暴露 App 级角色范围元数据,让 gateway/admin 只消费策略结果而不复制 app_code 分支。 +func (s *Server) GetRoleScopePolicy(ctx context.Context, req *userv1.GetRoleScopePolicyRequest) (*userv1.GetRoleScopePolicyResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + policy, err := hostservice.ResolveRoleScopePolicy(appcode.FromContext(ctx), req.GetScene()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &userv1.GetRoleScopePolicyResponse{Policy: &userv1.RoleScopePolicy{ + Scene: policy.Scene, + BaseScope: string(policy.BaseScope), + ExpandedScope: string(policy.ExpandedScope), + RegionExpansionConfigurable: policy.RegionExpansionConfigurable, + }}, nil +} + // GetAgencyMembers 返回 Agency 成员关系事实列表。 func (s *Server) GetAgencyMembers(ctx context.Context, req *userv1.GetAgencyMembersRequest) (*userv1.GetAgencyMembersResponse, error) { if s.hostSvc == nil { diff --git a/services/user-service/internal/transport/grpc/host_stats.go b/services/user-service/internal/transport/grpc/host_stats.go new file mode 100644 index 00000000..525c70d7 --- /dev/null +++ b/services/user-service/internal/transport/grpc/host_stats.go @@ -0,0 +1,50 @@ +package grpc + +import ( + "context" + + userv1 "hyapp.local/api/proto/user/v1" + "hyapp/pkg/xerr" + hostdomain "hyapp/services/user-service/internal/domain/host" +) + +// GetHostEngagementStats 返回 user-service 拥有的主播互动统计;调用方不能提交聚合值。 +func (s *Server) GetHostEngagementStats(ctx context.Context, req *userv1.GetHostEngagementStatsRequest) (*userv1.GetHostEngagementStatsResponse, error) { + if s.hostSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured")) + } + ctx = contextWithApp(ctx, req.GetMeta()) + stats, err := s.hostSvc.GetHostEngagementStats(ctx, hostdomain.EngagementStatsQuery{ + HostUserID: req.GetHostUserId(), + StartAtMS: req.GetStartAtMs(), + EndAtMS: req.GetEndAtMs(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &userv1.GetHostEngagementStatsResponse{Stats: &userv1.HostEngagementStats{ + OnlineDurationMs: stats.OnlineDurationMS, + ValidMicDurationMs: stats.ValidMicDurationMS, + ValidMicDays: stats.ValidMicDays, + PrivateMessageSenders: stats.PrivateMessageSenders, + NewFollowers: stats.NewFollowers, + }}, nil +} + +// RecordPrivateMessageEvent 只接受 gateway 已鉴权回调产生的最小事件事实。 +func (s *Server) RecordPrivateMessageEvent(ctx context.Context, req *userv1.RecordPrivateMessageEventRequest) (*userv1.RecordPrivateMessageEventResponse, error) { + if s.hostSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured")) + } + ctx = contextWithApp(ctx, req.GetMeta()) + created, err := s.hostSvc.RecordPrivateMessageEvent(ctx, hostdomain.PrivateMessageEvent{ + EventID: req.GetEventId(), + SenderUserID: req.GetSenderUserId(), + TargetUserID: req.GetTargetUserId(), + OccurredAtMS: req.GetOccurredAtMs(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &userv1.RecordPrivateMessageEventResponse{Created: created}, nil +} diff --git a/services/user-service/internal/transport/grpc/server.go b/services/user-service/internal/transport/grpc/server.go index ba7fb982..16b60fda 100644 --- a/services/user-service/internal/transport/grpc/server.go +++ b/services/user-service/internal/transport/grpc/server.go @@ -14,6 +14,7 @@ import ( cpservice "hyapp/services/user-service/internal/service/cp" hostservice "hyapp/services/user-service/internal/service/host" mictimeservice "hyapp/services/user-service/internal/service/mictime" + roomtimeservice "hyapp/services/user-service/internal/service/roomtime" userservice "hyapp/services/user-service/internal/service/user" ) @@ -58,6 +59,8 @@ type Server struct { hostSvc *hostservice.Service // micTimeSvc 承载用户维度麦位在线时长聚合查询。 micTimeSvc *mictimeservice.Service + // roomTimeSvc 承载用户维度在房时长会话补偿。 + roomTimeSvc *roomtimeservice.Service // cpSvc 承载 CP/兄弟/姐妹申请、关系和房间送礼消费用例。 cpSvc *cpservice.Service } @@ -81,6 +84,11 @@ func (s *Server) SetMicTimeService(micTimeSvc *mictimeservice.Service) { s.micTimeSvc = micTimeSvc } +// SetRoomTimeService 挂载可选的在房时长 read model,避免破坏现有测试构造函数签名。 +func (s *Server) SetRoomTimeService(roomTimeSvc *roomtimeservice.Service) { + s.roomTimeSvc = roomTimeSvc +} + // SetCPService 挂载 CP 关系 service,保持 NewServer 老测试构造方式兼容。 func (s *Server) SetCPService(cpSvc *cpservice.Service) { s.cpSvc = cpSvc @@ -470,6 +478,45 @@ func (s *Server) SetUserStatus(ctx context.Context, req *userv1.SetUserStatusReq return toProtoSetUserStatusResponse(result), nil } +// AdminBanUser 创建后台管理员永久或限时封禁;expires_at_ms=0 明确表示永久。 +func (s *Server) AdminBanUser(ctx context.Context, req *userv1.AdminBanUserRequest) (*userv1.AdminBanUserResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + ban, status, err := s.userSvc.AdminBanUser(ctx, userservice.AdminUserBanCommand{ + CommandID: req.GetCommandId(), + TargetUserID: req.GetTargetUserId(), + ExpiresAtMs: req.GetExpiresAtMs(), + OperatorAdminID: req.GetOperatorAdminId(), + Reason: req.GetReason(), + RequestID: req.GetMeta().GetRequestId(), + NowMs: req.GetMeta().GetSentAtMs(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &userv1.AdminBanUserResponse{Ban: toProtoAdminUserBan(ban), Status: toProtoSetUserStatusResponse(status)}, nil +} + +// AdminUnbanUser 由后台管理员强制释放目标用户全部管理员、经理和 legacy 封禁来源。 +func (s *Server) AdminUnbanUser(ctx context.Context, req *userv1.AdminUnbanUserRequest) (*userv1.AdminUnbanUserResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + release, status, err := s.userSvc.AdminUnbanUser(ctx, userservice.AdminForceUnbanCommand{ + TargetUserID: req.GetTargetUserId(), + OperatorAdminID: req.GetOperatorAdminId(), + Reason: req.GetReason(), + RequestID: req.GetMeta().GetRequestId(), + NowMs: req.GetMeta().GetSentAtMs(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &userv1.AdminUnbanUserResponse{ + Status: toProtoSetUserStatusResponse(status), + ReleasedAdminBanCount: release.ReleasedAdminBanCount, + ReleasedManagerBlockCount: release.ReleasedManagerBlockCount, + StatusRestored: release.StatusRestored, + }, nil +} + // CreateManagerUserBlock 创建经理中心封禁记录,并同步把目标账号置为 banned。 func (s *Server) CreateManagerUserBlock(ctx context.Context, req *userv1.CreateManagerUserBlockRequest) (*userv1.CreateManagerUserBlockResponse, error) { ctx = contextWithApp(ctx, req.GetMeta()) @@ -512,6 +559,7 @@ func (s *Server) UnblockManagerUser(ctx context.Context, req *userv1.UnblockMana BlockID: req.GetBlockId(), ManagerUserID: req.GetManagerUserId(), Reason: req.GetReason(), + RequestID: req.GetMeta().GetRequestId(), NowMS: req.GetMeta().GetSentAtMs(), }) if err != nil { @@ -687,6 +735,44 @@ func (s *Server) BatchGetUsers(ctx context.Context, req *userv1.BatchGetUsersReq return &userv1.BatchGetUsersResponse{Users: result}, nil } +// BatchGetUserAdminProfiles 返回后台列表需要的用户、登录、身份和封禁 owner 事实。 +func (s *Server) BatchGetUserAdminProfiles(ctx context.Context, req *userv1.BatchGetUserAdminProfilesRequest) (*userv1.BatchGetUserAdminProfilesResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + profiles, err := s.userSvc.BatchGetUserAdminProfiles(ctx, req.GetUserIds()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + result := make(map[int64]*userv1.UserAdminProfile, len(profiles)) + for userID, profile := range profiles { + result[userID] = toProtoUserAdminProfile(profile) + } + return &userv1.BatchGetUserAdminProfilesResponse{Profiles: result}, nil +} + +// AdminIssueUserAccessToken 后台按用户最新 active session 重签 access token。 +// 不写 login_audit、不动 session 行;取用审计由 admin 操作日志承担。 +func (s *Server) AdminIssueUserAccessToken(ctx context.Context, req *userv1.AdminIssueUserAccessTokenRequest) (*userv1.AdminIssueUserAccessTokenResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + meta := authMeta(req.GetMeta()) + if code := strings.TrimSpace(req.GetAppCode()); code != "" { + // 显式 app_code 字段优先于 meta,后台调用方不依赖 meta 组装细节。 + ctx = appcode.WithContext(ctx, code) + meta.AppCode = code + } + token, session, err := s.authSvc.AdminIssueUserAccessToken(ctx, req.GetUserId(), meta) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &userv1.AdminIssueUserAccessTokenResponse{ + AccessToken: token.AccessToken, + TokenType: token.TokenType, + ExpiresInSec: token.ExpiresInSec, + SessionId: token.SessionID, + DeviceId: session.DeviceID, + SessionLastHeartbeatAtMs: session.LastHeartbeatAtMs, + }, nil +} + // BatchGetRoomBasicUsers 批量返回房间首屏最小用户展示资料。 func (s *Server) BatchGetRoomBasicUsers(ctx context.Context, req *userv1.BatchGetRoomBasicUsersRequest) (*userv1.BatchGetRoomBasicUsersResponse, error) { ctx = contextWithApp(ctx, req.GetMeta()) diff --git a/services/user-service/internal/transport/grpc/server_test.go b/services/user-service/internal/transport/grpc/server_test.go index 70c0d259..523c49b6 100644 --- a/services/user-service/internal/transport/grpc/server_test.go +++ b/services/user-service/internal/transport/grpc/server_test.go @@ -138,6 +138,19 @@ func TestLoginThirdPartyMapsRegistrationProfile(t *testing.T) { if user.RegisterDevice != "iPhone 15" || user.RegisterOSVersion != "iOS 18.1" || user.BirthDate != "2000-01-02" || user.RegisterAppVersion != "1.2.3" || user.RegisterBuildNumber != "123" || user.RegisterInstallChannel != "app_store" || user.RegisterCampaign != "spring_launch" || user.RegisterPlatform != "ios" || user.Timezone != "America/Los_Angeles" { t.Fatalf("profile fields were not mapped: %+v", user) } + var auditedAppVersion string + var auditedBuildNumber string + if err := repository.RawDB().QueryRowContext(ctx, ` + SELECT app_version, build_number + FROM login_audit + WHERE app_code = 'lalu' AND request_id = 'req-third' AND user_id = ? + AND login_type = 'third_party' AND result = 'success' AND blocked = 0 + `, resp.GetToken().GetUserId()).Scan(&auditedAppVersion, &auditedBuildNumber); err != nil { + t.Fatalf("query third-party login client version audit failed: %v", err) + } + if auditedAppVersion != "1.2.3" || auditedBuildNumber != "123" { + t.Fatalf("third-party login client version audit mismatch: app_version=%q build_number=%q", auditedAppVersion, auditedBuildNumber) + } } func TestCompleteOnboardingReturnsReplacementAccessToken(t *testing.T) { diff --git a/services/wallet-service/Dockerfile b/services/wallet-service/Dockerfile index ea0b1fac..71567241 100644 --- a/services/wallet-service/Dockerfile +++ b/services/wallet-service/Dockerfile @@ -19,7 +19,7 @@ COPY . . RUN --mount=type=cache,id=hyapp-go-mod,target=/go/pkg/mod \ --mount=type=cache,id=hyapp-go-build,target=/root/.cache/go-build \ mkdir -p /out && \ - GOWORK=off CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/ ./services/wallet-service/cmd/server ./cmd/grpc-health-probe + GOWORK=off CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/ ./services/wallet-service/cmd/server ./services/wallet-service/cmd/replay-outbox ./cmd/grpc-health-probe FROM alpine:3.20 @@ -30,6 +30,7 @@ WORKDIR /app RUN apk add --no-cache ca-certificates && adduser -D -g '' appuser COPY --from=builder /out/server /app/server +COPY --from=builder /out/replay-outbox /app/replay-outbox COPY --from=builder /out/grpc-health-probe /app/grpc-health-probe COPY services/wallet-service/configs/config.docker.yaml /app/config.yaml diff --git a/services/wallet-service/cmd/replay-outbox/main.go b/services/wallet-service/cmd/replay-outbox/main.go new file mode 100644 index 00000000..15c2356e --- /dev/null +++ b/services/wallet-service/cmd/replay-outbox/main.go @@ -0,0 +1,79 @@ +// Command replay-outbox publishes one explicitly bounded wallet owner-outbox +// batch. It is intentionally separate from the long-running server worker: +// batch-size there controls one poll, while --limit here caps the whole process. +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "os" + "strings" + "time" + + "hyapp/pkg/outboxreplay" + "hyapp/services/wallet-service/internal/app" + "hyapp/services/wallet-service/internal/config" +) + +type repeatedFlag []string + +func (values *repeatedFlag) String() string { return strings.Join(*values, ",") } + +func (values *repeatedFlag) Set(value string) error { + value = strings.TrimSpace(value) + if value == "" { + return errors.New("event-type cannot be empty") + } + *values = append(*values, value) + return nil +} + +func main() { + configPath := flag.String("config", "services/wallet-service/configs/config.yaml", "path to wallet-service config") + appCode := flag.String("app-code", "", "exact App partition to replay") + limit := flag.Int("limit", 0, "process-wide maximum rows, 1..500") + execute := flag.Bool("execute", false, "publish the bounded batch; default is a no-write dry-run") + confirmation := flag.String("confirm", "", "required execution confirmation") + timeout := flag.Duration("timeout", 2*time.Minute, "total command timeout, at most 30m") + var eventTypes repeatedFlag + flag.Var(&eventTypes, "event-type", "exact wallet event type to include; repeat for multiple types") + flag.Parse() + + scope, err := outboxreplay.NewScope(*appCode, *limit, 500) + if err != nil { + fatal(err) + } + if *timeout <= 0 || *timeout > 30*time.Minute { + fatal(errors.New("timeout must be within (0,30m]")) + } + if *execute && strings.TrimSpace(*confirmation) != outboxreplay.ExecuteConfirmation { + fatal(fmt.Errorf("--execute requires --confirm=%s", outboxreplay.ExecuteConfirmation)) + } + cfg, err := config.Load(strings.TrimSpace(*configPath)) + if err != nil { + fatal(err) + } + normalizedTypes, err := app.ValidateWalletReplayEventTypes(cfg, eventTypes) + if err != nil { + fatal(err) + } + if !*execute { + fmt.Printf("mode=dry-run app_code=%s limit=%d event_types=%s no_writes=true\n", scope.AppCode, scope.Limit, strings.Join(normalizedTypes, ",")) + return + } + + ctx, cancel := context.WithTimeout(context.Background(), *timeout) + defer cancel() + result, err := app.ReplayWalletOutboxOnce(ctx, cfg, scope, normalizedTypes) + if err != nil { + fatal(fmt.Errorf("wallet replay stopped after %d rows: %w", result.Processed, err)) + } + fmt.Printf("mode=execute app_code=%s limit=%d processed=%d drained=%t event_types=%s\n", scope.AppCode, scope.Limit, result.Processed, result.Drained, strings.Join(normalizedTypes, ",")) +} + +func fatal(err error) { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) +} diff --git a/services/wallet-service/cmd/replay-outbox/main_test.go b/services/wallet-service/cmd/replay-outbox/main_test.go new file mode 100644 index 00000000..da76a324 --- /dev/null +++ b/services/wallet-service/cmd/replay-outbox/main_test.go @@ -0,0 +1,19 @@ +package main + +import "testing" + +func TestRepeatedEventTypeFlagAcceptsMultipleAndRejectsEmpty(t *testing.T) { + var values repeatedFlag + if err := values.Set(" GiftConfigChanged "); err != nil { + t.Fatalf("first event type failed: %v", err) + } + if err := values.Set("ResourceChanged"); err != nil { + t.Fatalf("second event type failed: %v", err) + } + if len(values) != 2 || values[0] != "GiftConfigChanged" || values[1] != "ResourceChanged" { + t.Fatalf("repeated event types mismatch: %v", values) + } + if err := values.Set(" "); err == nil { + t.Fatal("expected empty event type rejection") + } +} diff --git a/services/wallet-service/configs/config.docker.yaml b/services/wallet-service/configs/config.docker.yaml index 575da69b..ce902c21 100644 --- a/services/wallet-service/configs/config.docker.yaml +++ b/services/wallet-service/configs/config.docker.yaml @@ -33,6 +33,8 @@ rocketmq: namespace: "" send_timeout: "3s" retry: 2 + # Phase A 保持 legacy;所有消费者完成兼容滚动后才允许切换为 event_type。 + wallet_event_tag_mode: "legacy" wallet_outbox: enabled: true topic: "hyapp_wallet_outbox" @@ -50,7 +52,11 @@ google_play: token_url: "https://oauth2.googleapis.com/token" http_timeout: "10s" consume_enabled: true - +google_paid_sync_worker: + enabled: true + poll_interval: "15s" + batch_size: 100 + request_timeout: "2m" mifapay: # MiFaPay 是 H5 统一三方支付入口;商家公钥只用于记录商户侧配置,真实下单签名必须继续使用商户私钥。 enabled: true @@ -143,6 +149,7 @@ external_recharge: v5pay_return_url: "https://h5.global-interaction.com/recharge/index.html?env=test" outbox_worker: enabled: true + app_codes: [] poll_interval: "1s" batch_size: 200 concurrency: 4 @@ -152,6 +159,7 @@ outbox_worker: max_backoff: "5m" realtime_outbox_worker: enabled: true + app_codes: [] poll_interval: "500ms" batch_size: 50 concurrency: 2 diff --git a/services/wallet-service/configs/config.tencent.example.yaml b/services/wallet-service/configs/config.tencent.example.yaml index fd12806f..fc665372 100644 --- a/services/wallet-service/configs/config.tencent.example.yaml +++ b/services/wallet-service/configs/config.tencent.example.yaml @@ -33,6 +33,8 @@ rocketmq: namespace: "hyapp-prod" send_timeout: "3s" retry: 2 + # Phase A 必须保持 legacy;Phase B 经消费者订阅与积压验证后,仅将该值切为 event_type。 + wallet_event_tag_mode: "legacy" wallet_outbox: enabled: true topic: "hyapp_wallet_outbox" @@ -50,6 +52,11 @@ google_play: token_url: "https://oauth2.googleapis.com/token" http_timeout: "10s" consume_enabled: true +google_paid_sync_worker: + enabled: true + poll_interval: "15s" + batch_size: 100 + request_timeout: "2m" mifapay: # MiFaPay 是 H5 统一三方支付入口;商家公钥只用于记录商户侧配置,真实下单签名必须继续使用商户私钥。 enabled: true @@ -142,6 +149,8 @@ external_recharge: v5pay_return_url: "https://h5.global-interaction.com/recharge/index.html" outbox_worker: enabled: true + # 空值自动发现全部 App;历史积压回放时先设 ["lalu", "fami"],核对后再加入 "huwaa"。 + app_codes: [] poll_interval: "1s" batch_size: 200 concurrency: 8 @@ -151,6 +160,8 @@ outbox_worker: max_backoff: "5m" realtime_outbox_worker: enabled: true + # 实时 lane 必须与普通 lane 使用同一阶段 allowlist,避免同表出现跨 App 抢占差异。 + app_codes: [] poll_interval: "500ms" batch_size: 50 concurrency: 4 diff --git a/services/wallet-service/configs/config.yaml b/services/wallet-service/configs/config.yaml index 7cff133c..2a6820f9 100644 --- a/services/wallet-service/configs/config.yaml +++ b/services/wallet-service/configs/config.yaml @@ -33,6 +33,8 @@ rocketmq: namespace: "" send_timeout: "3s" retry: 2 + # Phase A 保持 legacy;确认所有 wallet consumer 已双订阅且稳定后,Phase B 只把此值改为 event_type。 + wallet_event_tag_mode: "legacy" wallet_outbox: enabled: true topic: "hyapp_wallet_outbox" @@ -50,6 +52,11 @@ google_play: token_url: "https://oauth2.googleapis.com/token" http_timeout: "10s" consume_enabled: true +google_paid_sync_worker: + enabled: true + poll_interval: "15s" + batch_size: 100 + request_timeout: "2m" mifapay: # MiFaPay 是 H5 统一三方支付入口;商家公钥只用于记录商户侧配置,真实下单签名必须继续使用商户私钥。 enabled: true @@ -144,6 +151,7 @@ external_recharge_reconcile_worker: batch_size: 50 outbox_worker: enabled: true + app_codes: [] poll_interval: "1s" batch_size: 100 concurrency: 1 @@ -153,6 +161,7 @@ outbox_worker: max_backoff: "5m" realtime_outbox_worker: enabled: true + app_codes: [] poll_interval: "500ms" batch_size: 50 concurrency: 2 diff --git a/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql b/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql index f39e6c27..69bc93b7 100644 --- a/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql +++ b/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql @@ -131,6 +131,7 @@ CREATE TABLE IF NOT EXISTS wallet_policy_instances ( effective_to_ms BIGINT NOT NULL DEFAULT 0 COMMENT '0 表示长期有效', host_point_asset_type VARCHAR(32) NOT NULL DEFAULT 'POINT' COMMENT '主播收益积分资产', host_point_ratio_ppm BIGINT NOT NULL DEFAULT 700000 COMMENT '有效付费礼物转 POINT 比例,ppm', + agency_point_ratio_ppm BIGINT NOT NULL DEFAULT 0 COMMENT '有效付费礼物给 Agency owner 的 POINT 分成比例,ppm', points_per_usd BIGINT NOT NULL DEFAULT 100000 COMMENT 'POINT/USD 展示换算比例', withdraw_fee_bps INT NOT NULL DEFAULT 500 COMMENT '提现手续费 bps', rule_json JSON NOT NULL COMMENT '完整政策快照', @@ -142,6 +143,41 @@ CREATE TABLE IF NOT EXISTS wallet_policy_instances ( KEY idx_wallet_policy_active (app_code, region_id, status, effective_from_ms, effective_to_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='钱包运行侧收益政策实例表'; +-- 三套 App 共享同一张配置表,但 app_code 是主键的一部分;新 App 只需配置数据,不再复制表和业务代码。 +-- 未配置任何 active 行时 H5 币商提现入口显示为空,不能用伪造默认币商兜底。 +CREATE TABLE IF NOT EXISTS point_withdrawal_coin_seller_configs ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码', + seller_user_id BIGINT NOT NULL COMMENT '币商用户 ID', + sort_order INT NOT NULL DEFAULT 0 COMMENT '展示顺序,越小越靠前', + point_amount BIGINT NOT NULL COMMENT '兑换比例中的 POINT 分母', + seller_coin_amount BIGINT NOT NULL COMMENT '兑换比例中的币商金币分子', + service_country_codes JSON NOT NULL COMMENT '服务国家码数组;空数组表示不限国家', + status VARCHAR(24) NOT NULL DEFAULT 'active' COMMENT 'active/disabled', + created_by_admin_id BIGINT UNSIGNED NOT NULL DEFAULT 0, + updated_by_admin_id BIGINT UNSIGNED NOT NULL DEFAULT 0, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, seller_user_id), + KEY idx_point_withdraw_seller_list (app_code, status, sort_order, seller_user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='POINT 提现币商白名单与兑换比例'; + +-- Agency 分成独立流水同时承担归属快照和审计依据;不能按当前成员关系反推历史收益。 +CREATE TABLE IF NOT EXISTS agency_point_share_entries ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + transaction_id VARCHAR(96) NOT NULL COMMENT '礼物钱包交易 ID', + host_user_id BIGINT NOT NULL COMMENT '收礼主播用户 ID 快照', + agency_owner_user_id BIGINT NOT NULL COMMENT '送礼时 Agency owner 用户 ID 快照', + sender_user_id BIGINT NOT NULL COMMENT '送礼用户 ID', + room_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '房间 ID,私聊送礼为空', + point_delta BIGINT NOT NULL COMMENT 'Agency owner 本次 POINT 分成', + ratio_ppm BIGINT NOT NULL COMMENT 'Agency 分成比例快照', + policy_instance_code VARCHAR(96) NOT NULL DEFAULT '' COMMENT '收益策略实例快照', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + PRIMARY KEY (app_code, transaction_id, agency_owner_user_id), + KEY idx_agency_point_share_owner_time (app_code, agency_owner_user_id, created_at_ms), + KEY idx_agency_point_share_host_time (app_code, host_user_id, created_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Agency POINT 礼物分成流水'; + -- 主播工资周期钻石账户:只记录政策结算用累计值,不作为用户可消费或可提现的钱包资产。 -- cycle_key 当前固定为 UTC yyyy-MM;后续日结/半月结不清空该账户,月末清算任务按策略处理。 CREATE TABLE IF NOT EXISTS host_period_diamond_accounts ( @@ -887,6 +923,9 @@ CREATE TABLE IF NOT EXISTS payment_orders ( paid_tax_micro BIGINT NOT NULL DEFAULT 0 COMMENT '订单税额微单位(实付币种)', paid_net_micro BIGINT NOT NULL DEFAULT 0 COMMENT 'Google 扣费后开发者净收入微单位(实付币种)', paid_synced_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '实付明细同步时间,UTC epoch ms;0 表示未同步', + paid_sync_attempt_count INT NOT NULL DEFAULT 0 COMMENT 'Google 实付同步连续失败次数', + paid_sync_last_error VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'Google 实付同步最近错误', + paid_sync_next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT 'Google 实付同步下次重试时间,UTC epoch ms', provider_payload JSON NULL COMMENT 'Google Play 校验返回快照', created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', @@ -895,7 +934,8 @@ CREATE TABLE IF NOT EXISTS payment_orders ( UNIQUE KEY uk_payment_orders_command (app_code, command_id), KEY idx_payment_orders_user_time (app_code, user_id, created_at_ms), KEY idx_payment_orders_provider_order (app_code, provider, provider_order_id), - KEY idx_payment_orders_wallet_tx (app_code, wallet_transaction_id) + KEY idx_payment_orders_wallet_tx (app_code, wallet_transaction_id), + KEY idx_payment_orders_paid_sync (provider, paid_synced_at_ms, paid_sync_next_retry_at_ms, created_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='支付订单与 provider 审计表'; CREATE TABLE IF NOT EXISTS coin_seller_stock_records ( @@ -1513,6 +1553,27 @@ CREATE TABLE IF NOT EXISTS red_packet_user_daily_counters ( PRIMARY KEY (app_code, user_id, send_day) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='红包用户每日发送计数表'; +-- vip_program_configs 是 VIP 行为的唯一 App 级策略来源。购买、发放和权益查询必须读取该表, +-- 禁止在服务代码中用 app_code 分支复制 Lalu/Fami 两套状态机。 +CREATE TABLE IF NOT EXISTS vip_program_configs ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + program_type VARCHAR(32) NOT NULL COMMENT '体系类型:legacy_timed/tiered_privilege_v1', + level_count INT NOT NULL COMMENT '该 App 可配置等级数量', + same_level_expiry_policy VARCHAR(32) NOT NULL COMMENT '同级续费有效期策略:extend_remaining', + upgrade_expiry_policy VARCHAR(32) NOT NULL COMMENT '升级有效期策略:extend_remaining/replace_from_now', + downgrade_purchase_policy VARCHAR(32) NOT NULL COMMENT '降级购买策略:reject', + benefit_inheritance_policy VARCHAR(32) NOT NULL COMMENT '权益矩阵策略:当前固定 target_only,每级保存完整集合', + grant_mode VARCHAR(32) NOT NULL COMMENT '后台赠送语义:direct_membership/trial_card', + trial_card_enabled BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否允许体验卡背包和佩戴流程', + status VARCHAR(32) NOT NULL COMMENT '业务状态:active/disabled', + config_version BIGINT NOT NULL DEFAULT 1 COMMENT '配置版本;订单和会员状态持久化该快照版本', + updated_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '最后更新管理员 ID', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code), + KEY idx_vip_program_status (status, updated_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='按 App 配置的 VIP 体系规则'; + CREATE TABLE IF NOT EXISTS vip_levels ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', level INT NOT NULL COMMENT '等级', @@ -1544,6 +1605,8 @@ CREATE TABLE IF NOT EXISTS user_vip_memberships ( level INT NOT NULL COMMENT '等级', name VARCHAR(64) NOT NULL DEFAULT '' COMMENT '名称', status VARCHAR(32) NOT NULL COMMENT '业务状态', + program_type VARCHAR(32) NOT NULL DEFAULT 'legacy_timed' COMMENT '激活时使用的 VIP 体系快照', + config_version BIGINT NOT NULL DEFAULT 1 COMMENT '激活时使用的 VIP 配置版本', started_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '开始时间,UTC epoch ms', expires_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '过期时间,UTC epoch ms', created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', @@ -1552,6 +1615,36 @@ CREATE TABLE IF NOT EXISTS user_vip_memberships ( KEY idx_user_vip_expiry (app_code, status, expires_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户 VIP 成员关系表'; +-- 用户偏好和 VIP 资格分离:没有记录时运行时默认两项开启;首次 PATCH 才物化。 +-- 主键包含 app_code,确保同一 user_id 在 Fami/Lalu 可独立设置。 +CREATE TABLE IF NOT EXISTS user_vip_settings ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + user_id BIGINT NOT NULL COMMENT '用户 ID', + room_entry_notice_enabled BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否开启进房通知', + online_global_notice_enabled BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否开启上线全服通知', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户按 App 隔离的 VIP 功能开关'; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_vip_memberships' AND COLUMN_NAME = 'program_type') = 0, + 'ALTER TABLE user_vip_memberships ADD COLUMN program_type VARCHAR(32) NOT NULL DEFAULT ''legacy_timed'' COMMENT ''激活时使用的 VIP 体系快照'' AFTER status', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_vip_memberships' AND COLUMN_NAME = 'config_version') = 0, + 'ALTER TABLE user_vip_memberships ADD COLUMN config_version BIGINT NOT NULL DEFAULT 1 COMMENT ''激活时使用的 VIP 配置版本'' AFTER program_type', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + CREATE TABLE IF NOT EXISTS vip_purchase_orders ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', order_id VARCHAR(96) NOT NULL COMMENT '订单 ID', @@ -1559,7 +1652,13 @@ CREATE TABLE IF NOT EXISTS vip_purchase_orders ( user_id BIGINT NOT NULL COMMENT '用户 ID', target_level INT NOT NULL COMMENT '目标等级', previous_level INT NOT NULL COMMENT '原等级', + previous_expires_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '购买前会员过期时间快照,UTC epoch ms', + new_expires_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '购买后会员过期时间快照,UTC epoch ms', price_coin BIGINT NOT NULL COMMENT 'VIP 价格金币数', + duration_ms BIGINT NOT NULL DEFAULT 0 COMMENT '购买等级的有效期配置快照', + program_type VARCHAR(32) NOT NULL DEFAULT 'legacy_timed' COMMENT '订单执行的 VIP 体系快照', + config_version BIGINT NOT NULL DEFAULT 1 COMMENT '订单执行的 VIP 配置版本', + expiry_policy VARCHAR(32) NOT NULL DEFAULT 'extend_remaining' COMMENT '本单实际采用的有效期策略', status VARCHAR(32) NOT NULL COMMENT '业务状态', wallet_transaction_id VARCHAR(96) NOT NULL COMMENT '钱包交易 ID', resource_grant_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '资源发放 ID', @@ -1571,6 +1670,60 @@ CREATE TABLE IF NOT EXISTS vip_purchase_orders ( KEY idx_vip_purchase_user_time (app_code, user_id, created_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='VIP 购买订单表'; +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vip_purchase_orders' AND COLUMN_NAME = 'previous_expires_at_ms') = 0, + 'ALTER TABLE vip_purchase_orders ADD COLUMN previous_expires_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''购买前会员过期时间快照,UTC epoch ms'' AFTER previous_level', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vip_purchase_orders' AND COLUMN_NAME = 'new_expires_at_ms') = 0, + 'ALTER TABLE vip_purchase_orders ADD COLUMN new_expires_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''购买后会员过期时间快照,UTC epoch ms'' AFTER previous_expires_at_ms', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vip_purchase_orders' AND COLUMN_NAME = 'duration_ms') = 0, + 'ALTER TABLE vip_purchase_orders ADD COLUMN duration_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''购买等级的有效期配置快照'' AFTER price_coin', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vip_purchase_orders' AND COLUMN_NAME = 'program_type') = 0, + 'ALTER TABLE vip_purchase_orders ADD COLUMN program_type VARCHAR(32) NOT NULL DEFAULT ''legacy_timed'' COMMENT ''订单执行的 VIP 体系快照'' AFTER duration_ms', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vip_purchase_orders' AND COLUMN_NAME = 'config_version') = 0, + 'ALTER TABLE vip_purchase_orders ADD COLUMN config_version BIGINT NOT NULL DEFAULT 1 COMMENT ''订单执行的 VIP 配置版本'' AFTER program_type', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vip_purchase_orders' AND COLUMN_NAME = 'expiry_policy') = 0, + 'ALTER TABLE vip_purchase_orders ADD COLUMN expiry_policy VARCHAR(32) NOT NULL DEFAULT ''extend_remaining'' COMMENT ''本单实际采用的有效期策略'' AFTER config_version', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + CREATE TABLE IF NOT EXISTS user_vip_history ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', history_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '历史 ID', @@ -1578,11 +1731,228 @@ CREATE TABLE IF NOT EXISTS user_vip_history ( action VARCHAR(32) NOT NULL COMMENT '操作', previous_level INT NOT NULL COMMENT '原等级', new_level INT NOT NULL COMMENT '新等级', + previous_expires_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '变更前过期时间快照,UTC epoch ms', + new_expires_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '变更后过期时间快照,UTC epoch ms', + program_type VARCHAR(32) NOT NULL DEFAULT 'legacy_timed' COMMENT '变更使用的 VIP 体系快照', + config_version BIGINT NOT NULL DEFAULT 1 COMMENT '变更使用的 VIP 配置版本', order_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '订单 ID', created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', KEY idx_user_vip_history_user_time (app_code, user_id, created_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户 VIP 历史表'; +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_vip_history' AND COLUMN_NAME = 'previous_expires_at_ms') = 0, + 'ALTER TABLE user_vip_history ADD COLUMN previous_expires_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''变更前过期时间快照,UTC epoch ms'' AFTER new_level', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_vip_history' AND COLUMN_NAME = 'new_expires_at_ms') = 0, + 'ALTER TABLE user_vip_history ADD COLUMN new_expires_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''变更后过期时间快照,UTC epoch ms'' AFTER previous_expires_at_ms', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_vip_history' AND COLUMN_NAME = 'program_type') = 0, + 'ALTER TABLE user_vip_history ADD COLUMN program_type VARCHAR(32) NOT NULL DEFAULT ''legacy_timed'' COMMENT ''变更使用的 VIP 体系快照'' AFTER new_expires_at_ms', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_vip_history' AND COLUMN_NAME = 'config_version') = 0, + 'ALTER TABLE user_vip_history ADD COLUMN config_version BIGINT NOT NULL DEFAULT 1 COMMENT ''变更使用的 VIP 配置版本'' AFTER program_type', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 重复执行 initdb 也可能面对旧 history 有效期快照为 0 的开发库。只为没有任何完整 +-- 新格式 history 的当前会员补迁移基线;不 UPDATE 旧行,也不改写已有新格式历史语义。 +SET @vip_history_baseline_ms := CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED); + +INSERT INTO user_vip_history ( + app_code, user_id, action, previous_level, new_level, previous_expires_at_ms, + new_expires_at_ms, program_type, config_version, order_id, created_at_ms +) +SELECT + memberships.app_code, + memberships.user_id, + 'migration_baseline', + 0, + memberships.level, + 0, + memberships.expires_at_ms, + memberships.program_type, + memberships.config_version, + 'vip_configurable_060_baseline', + @vip_history_baseline_ms +FROM user_vip_memberships AS memberships +WHERE memberships.level > 0 + AND memberships.expires_at_ms > 0 + AND NOT EXISTS ( + SELECT 1 + FROM user_vip_history AS complete_history + WHERE complete_history.app_code = memberships.app_code + AND complete_history.user_id = memberships.user_id + AND complete_history.new_level > 0 + AND complete_history.new_expires_at_ms > 0 + ) + AND NOT EXISTS ( + SELECT 1 + FROM user_vip_history AS existing_baseline + WHERE existing_baseline.app_code = memberships.app_code + AND existing_baseline.user_id = memberships.user_id + AND existing_baseline.order_id = 'vip_configurable_060_baseline' + ); + +-- vip_level_benefits 按“具体等级”保存完整权益集合。unlock_level 只保留 P1 来源信息, +-- 运行时不得靠它自动继承,以便后台将来可以显式调整任意等级的最终集合。 +CREATE TABLE IF NOT EXISTS vip_level_benefits ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + level INT NOT NULL COMMENT '拥有该权益的具体 VIP 等级', + benefit_code VARCHAR(96) NOT NULL COMMENT '稳定权益编码,跨服务权限判断只使用该值', + name VARCHAR(128) NOT NULL COMMENT '权益展示名称', + benefit_type VARCHAR(32) NOT NULL COMMENT '权益类型:decoration/function', + unlock_level INT NOT NULL COMMENT 'P1 初始解锁等级,仅用于展示来源,不参与运行时继承', + status VARCHAR(32) NOT NULL COMMENT '业务状态:active/disabled', + trial_enabled BOOLEAN NOT NULL DEFAULT TRUE COMMENT '体验卡是否可获得;金币返现必须为 false', + resource_id BIGINT NOT NULL DEFAULT 0 COMMENT '绑定资源 ID;0 表示尚未绑定或纯功能权益', + resource_type VARCHAR(32) NOT NULL DEFAULT '' COMMENT '绑定资源类型;空表示纯功能或待运营配置', + execution_scope VARCHAR(32) NOT NULL COMMENT '实际执行方:wallet/room/user/notice', + auto_equip BOOLEAN NOT NULL DEFAULT FALSE COMMENT '资源下发后是否自动佩戴最高等级样式', + sort_order INT NOT NULL DEFAULT 0 COMMENT '权益展示顺序', + metadata_json JSON NULL COMMENT '执行方扩展配置;初始 P1 权益不猜测参数', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, level, benefit_code), + KEY idx_vip_benefit_code (app_code, benefit_code, status, level), + KEY idx_vip_benefit_level_sort (app_code, level, status, sort_order) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='VIP 每等级完整权益矩阵'; + +-- user_vip_trial_cards 只保存体验卡的 VIP 元数据;卡片库存与当前佩戴仍分别复用 +-- user_resource_entitlements 和 user_resource_equipment,切换佩戴不会重置或作废其它卡片。 +CREATE TABLE IF NOT EXISTS user_vip_trial_cards ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + trial_card_id VARCHAR(96) NOT NULL COMMENT '体验卡实例 ID', + command_id VARCHAR(128) NOT NULL COMMENT '发卡命令幂等 ID', + request_hash VARCHAR(128) NOT NULL COMMENT '发卡请求语义哈希', + user_id BIGINT NOT NULL COMMENT '持有用户 ID', + entitlement_id VARCHAR(96) NOT NULL COMMENT '通用背包权益实例 ID,也是佩戴入口标识', + resource_id BIGINT NOT NULL DEFAULT 0 COMMENT '可选资源 ID;0 表示直接按等级和时长发卡', + level INT NOT NULL COMMENT '体验卡 VIP 等级快照', + name VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'VIP 等级名称快照', + status VARCHAR(32) NOT NULL COMMENT '卡片业务状态:active/revoked', + duration_ms BIGINT NOT NULL COMMENT '发卡时长快照;佩戴切换不改变该值', + effective_at_ms BIGINT NOT NULL COMMENT '卡片开始计时,UTC epoch ms', + expires_at_ms BIGINT NOT NULL COMMENT '卡片绝对过期时间,UTC epoch ms', + grant_source VARCHAR(32) NOT NULL COMMENT '发放来源', + source_grant_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '通用资源发放 ID', + operator_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '发放操作人用户 ID', + reason VARCHAR(256) NOT NULL DEFAULT '' COMMENT '发放原因', + program_type VARCHAR(32) NOT NULL COMMENT '发卡时 VIP 体系快照', + config_version BIGINT NOT NULL COMMENT '发卡时 VIP 配置版本', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, trial_card_id), + UNIQUE KEY uk_vip_trial_card_command (app_code, command_id), + UNIQUE KEY uk_vip_trial_card_entitlement (app_code, entitlement_id), + KEY idx_vip_trial_card_user (app_code, user_id, status, expires_at_ms), + KEY idx_vip_trial_card_resource (app_code, resource_id, status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='VIP 体验卡背包元数据'; + +-- VIP 购买、直发、体验卡和返现领取先锁定同一守卫键,再进入会员、账户或资源业务锁。守卫与业务事实同事务提交, +-- 既避免并发首发返回可变错误,也禁止 purchase/direct grant/trial grant 交叉复用 command_id。 +CREATE TABLE IF NOT EXISTS vip_command_locks ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + command_id VARCHAR(128) NOT NULL COMMENT 'VIP 写命令幂等 ID', + biz_type VARCHAR(64) NOT NULL COMMENT '命令类型:vip_purchase/vip_grant/vip_trial_card_grant/vip_daily_coin_rebate', + request_hash VARCHAR(128) NOT NULL COMMENT '请求语义哈希', + created_at_ms BIGINT NOT NULL COMMENT '首次命令时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '最后写入时间,UTC epoch ms', + PRIMARY KEY (app_code, command_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='VIP 写命令串行化守卫'; + +-- vip_daily_coin_rebate_runs 固化单 App、单 UTC 日的金额矩阵和配置版本。分页只推进本表游标, +-- 不能在每页重新读取 vip_level_benefits,否则日切期间后台改价会让同一天用户拿到不同金额版本。 +CREATE TABLE IF NOT EXISTS vip_daily_coin_rebate_runs ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + task_day CHAR(10) NOT NULL COMMENT 'UTC 日期,YYYY-MM-DD', + run_id VARCHAR(96) NOT NULL COMMENT '稳定批任务 ID', + day_start_ms BIGINT NOT NULL COMMENT 'UTC 当日开始,epoch ms', + day_end_ms BIGINT NOT NULL COMMENT 'UTC 次日开始,排他结束 epoch ms', + config_version BIGINT NOT NULL COMMENT '创建 run 时 VIP 配置版本', + level_amounts_json JSON NOT NULL COMMENT '等级到返现金额的不可变快照', + status VARCHAR(32) NOT NULL COMMENT 'running/completed', + last_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '已扫描会员游标', + scanned_count BIGINT NOT NULL DEFAULT 0 COMMENT '累计扫描会员数', + created_count BIGINT NOT NULL DEFAULT 0 COMMENT '累计创建返现数', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + completed_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '完成时间,UTC epoch ms', + PRIMARY KEY (app_code, task_day), + UNIQUE KEY uk_vip_rebate_run_id (app_code, run_id), + KEY idx_vip_rebate_run_status (app_code, status, task_day) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='VIP 每日金币返现运行快照'; + +-- vip_daily_coin_rebates 是付费 VIP 日切资格事实。体验卡不写入此表;领取只允许在 +-- [available_at_ms, expires_at_ms) 内完成,并与 COIN 账户、分录和 outbox 同事务提交。 +CREATE TABLE IF NOT EXISTS vip_daily_coin_rebates ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + rebate_id VARCHAR(96) NOT NULL COMMENT '返现事实 ID', + run_id VARCHAR(96) NOT NULL COMMENT '来源日 run ID', + task_day CHAR(10) NOT NULL COMMENT 'UTC 日期,YYYY-MM-DD', + user_id BIGINT NOT NULL COMMENT '领取用户 ID', + vip_level INT NOT NULL COMMENT 'UTC 日切时付费 VIP 等级快照', + vip_name VARCHAR(64) NOT NULL COMMENT 'VIP 名称快照', + coin_amount BIGINT NOT NULL COMMENT '返现金币正整数快照', + status VARCHAR(32) NOT NULL COMMENT 'claimable/claimed;expired 由查询层投影', + available_at_ms BIGINT NOT NULL COMMENT '领取开始,UTC epoch ms', + expires_at_ms BIGINT NOT NULL COMMENT '领取排他结束,UTC epoch ms', + config_version BIGINT NOT NULL COMMENT '来源 run 的 VIP 配置版本', + claimed_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '实际领取时间,UTC epoch ms', + wallet_transaction_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '领取入账交易 ID', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, rebate_id), + UNIQUE KEY uk_vip_rebate_user_day (app_code, task_day, user_id), + KEY idx_vip_rebate_user_day (app_code, user_id, task_day, rebate_id), + KEY idx_vip_rebate_expiry (app_code, status, expires_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='VIP 每日金币返现领取事实'; + +-- initdb 也允许在保留数据的开发库重复执行,因此同步补齐守卫,不能只覆盖全新空库。 +INSERT IGNORE INTO vip_command_locks ( + app_code, command_id, biz_type, request_hash, created_at_ms, updated_at_ms +) +SELECT app_code, command_id, biz_type, request_hash, created_at_ms, updated_at_ms +FROM wallet_transactions +WHERE biz_type IN ('vip_purchase', 'vip_grant', 'vip_daily_coin_rebate'); + +INSERT IGNORE INTO vip_command_locks ( + app_code, command_id, biz_type, request_hash, created_at_ms, updated_at_ms +) +SELECT app_code, command_id, 'vip_trial_card_grant', request_hash, created_at_ms, updated_at_ms +FROM user_vip_trial_cards; + +INSERT IGNORE INTO vip_program_configs ( + app_code, program_type, level_count, same_level_expiry_policy, upgrade_expiry_policy, + downgrade_purchase_policy, benefit_inheritance_policy, grant_mode, trial_card_enabled, + status, config_version, updated_by_admin_id, created_at_ms, updated_at_ms +) VALUES + ('lalu', 'legacy_timed', 10, 'extend_remaining', 'extend_remaining', + 'reject', 'target_only', 'direct_membership', FALSE, 'active', 1, 0, 0, 0), + ('fami', 'tiered_privilege_v1', 9, 'extend_remaining', 'replace_from_now', + 'reject', 'target_only', 'trial_card', TRUE, 'active', 1, 0, 0, 0); + INSERT IGNORE INTO vip_levels ( app_code, level, name, status, price_coin, duration_ms, reward_resource_group_id, required_recharge_coin_amount, sort_order, created_at_ms, updated_at_ms @@ -1596,7 +1966,126 @@ INSERT IGNORE INTO vip_levels ( ('lalu', 7, 'VIP7', 'disabled', 0, 604800000, 0, 0, 70, 0, 0), ('lalu', 8, 'VIP8', 'disabled', 0, 604800000, 0, 0, 80, 0, 0), ('lalu', 9, 'VIP9', 'disabled', 0, 604800000, 0, 0, 90, 0, 0), - ('lalu', 10, 'VIP10', 'disabled', 0, 604800000, 0, 0, 100, 0, 0); + ('lalu', 10, 'VIP10', 'disabled', 0, 604800000, 0, 0, 100, 0, 0), + -- Fami 的价格和资源组必须由运营后台配置;30 天仅满足后台正数校验且等级保持 disabled, + -- 不能把 Lalu 的任何商业参数复制为可购买默认值。 + ('fami', 1, 'VIP1', 'disabled', 0, 2592000000, 0, 0, 10, 0, 0), + ('fami', 2, 'VIP2', 'disabled', 0, 2592000000, 0, 0, 20, 0, 0), + ('fami', 3, 'VIP3', 'disabled', 0, 2592000000, 0, 0, 30, 0, 0), + ('fami', 4, 'VIP4', 'disabled', 0, 2592000000, 0, 0, 40, 0, 0), + ('fami', 5, 'VIP5', 'disabled', 0, 2592000000, 0, 0, 50, 0, 0), + ('fami', 6, 'VIP6', 'disabled', 0, 2592000000, 0, 0, 60, 0, 0), + ('fami', 7, 'VIP7', 'disabled', 0, 2592000000, 0, 0, 70, 0, 0), + ('fami', 8, 'VIP8', 'disabled', 0, 2592000000, 0, 0, 80, 0, 0), + ('fami', 9, 'VIP9', 'disabled', 0, 2592000000, 0, 0, 90, 0, 0); + +-- P1 权益按每个等级展开成完整集合:例如 VIP4 会实际写入 VIP1..VIP4 的全部权益。 +-- resource_id/resource_type 保持空,待运营绑定真实资源;体验卡仅排除每日金币返现。 +INSERT IGNORE INTO vip_level_benefits ( + app_code, level, benefit_code, name, benefit_type, unlock_level, status, trial_enabled, + resource_id, resource_type, execution_scope, auto_equip, sort_order, metadata_json, + created_at_ms, updated_at_ms +) +SELECT + 'fami', levels.level, benefits.benefit_code, benefits.name, benefits.benefit_type, + benefits.unlock_level, + CASE WHEN benefits.benefit_code = 'daily_coin_rebate' THEN 'disabled' ELSE 'active' END, + benefits.trial_enabled, 0, '', benefits.execution_scope, + benefits.auto_equip, benefits.sort_order, + CASE + WHEN benefits.benefit_code = 'online_global_notice' + THEN JSON_OBJECT('message', '欢迎进入Fami,祝你有美好的一天') + ELSE NULL + END, + 0, 0 +FROM ( + SELECT 1 AS level UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 + UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 +) AS levels +INNER JOIN ( + SELECT 'vip_badge_identity' AS benefit_code, 'VIP标识' AS name, 'function' AS benefit_type, 1 AS unlock_level, TRUE AS trial_enabled, 'user' AS execution_scope, FALSE AS auto_equip, 10 AS sort_order + UNION ALL SELECT 'vip_medal', 'VIP勋章', 'decoration', 1, TRUE, 'user', TRUE, 20 + UNION ALL SELECT 'avatar_frame', 'VIP头像框', 'decoration', 1, TRUE, 'user', TRUE, 30 + UNION ALL SELECT 'vip_title', 'VIP称号', 'function', 1, TRUE, 'user', FALSE, 40 + UNION ALL SELECT 'chat_bubble', 'VIP聊天气泡', 'decoration', 2, TRUE, 'room', TRUE, 50 + UNION ALL SELECT 'vip_gift', 'VIP礼物', 'function', 2, TRUE, 'room', FALSE, 60 + UNION ALL SELECT 'entry_effect', 'VIP进场通知', 'function', 2, TRUE, 'room', FALSE, 70 + UNION ALL SELECT 'visitor_history', '查看访客', 'function', 2, TRUE, 'user', FALSE, 80 + UNION ALL SELECT 'voice_wave', 'VIP声波纹', 'decoration', 3, TRUE, 'room', TRUE, 90 + UNION ALL SELECT 'profile_card', 'VIP资料卡皮肤', 'decoration', 3, TRUE, 'user', TRUE, 100 + UNION ALL SELECT 'custom_room_background', '自定义房间背景', 'function', 3, TRUE, 'room', FALSE, 110 + UNION ALL SELECT 'room_image_message', '房间发图', 'function', 3, TRUE, 'room', FALSE, 120 + UNION ALL SELECT 'animated_avatar', '动态头像', 'function', 4, TRUE, 'user', FALSE, 130 + UNION ALL SELECT 'animated_room_cover', '动态房间封面', 'function', 4, TRUE, 'room', FALSE, 140 + UNION ALL SELECT 'mic_skin', '麦克风皮肤', 'decoration', 4, TRUE, 'room', TRUE, 150 + UNION ALL SELECT 'room_border', '房间边框', 'decoration', 4, TRUE, 'room', TRUE, 160 + UNION ALL SELECT 'vehicle', 'VIP座驾', 'decoration', 4, TRUE, 'room', TRUE, 170 + UNION ALL SELECT 'colored_room_name', 'VIP彩色房间名称', 'function', 5, TRUE, 'room', FALSE, 180 + UNION ALL SELECT 'colored_nickname', 'VIP彩色昵称', 'function', 5, TRUE, 'user', FALSE, 190 + UNION ALL SELECT 'colored_id', 'VIP彩色ID', 'function', 5, TRUE, 'user', FALSE, 200 + UNION ALL SELECT 'gift_tray_skin', '送礼托盘皮肤', 'function', 5, TRUE, 'room', FALSE, 210 + UNION ALL SELECT 'hide_profile_data', '隐藏个人数据', 'function', 6, TRUE, 'user', FALSE, 220 + UNION ALL SELECT 'custom_avatar_frame', '定制头框', 'function', 6, TRUE, 'user', FALSE, 230 + UNION ALL SELECT 'leaderboard_invisible', '榜单隐身', 'function', 6, TRUE, 'user', FALSE, 240 + UNION ALL SELECT 'daily_coin_rebate', '金币返现', 'function', 6, FALSE, 'wallet', FALSE, 250 + UNION ALL SELECT 'custom_profile_card', '定制资料卡', 'function', 7, TRUE, 'user', FALSE, 260 + UNION ALL SELECT 'anonymous_profile_visit', '匿名访问主页', 'function', 7, TRUE, 'user', FALSE, 270 + UNION ALL SELECT 'custom_gift', '定制礼物', 'function', 7, TRUE, 'room', FALSE, 280 + -- 产品已确认该权益只在当前房间展示,编码和名称均不再使用“全服”。 + UNION ALL SELECT 'room_entry_notice', '进房高亮通知', 'function', 8, TRUE, 'room', FALSE, 290 + UNION ALL SELECT 'custom_pretty_id', '定制靓号', 'function', 8, TRUE, 'user', FALSE, 300 + UNION ALL SELECT 'custom_vehicle', '定制座驾', 'function', 8, TRUE, 'room', FALSE, 310 + UNION ALL SELECT 'anti_kick', '防踢', 'function', 9, TRUE, 'room', FALSE, 320 + UNION ALL SELECT 'anti_mute', '防禁言', 'function', 9, TRUE, 'room', FALSE, 330 + UNION ALL SELECT 'online_global_notice', '上线全服通知', 'function', 9, TRUE, 'notice', FALSE, 340 +) AS benefits ON benefits.unlock_level <= levels.level; + +-- 原型未给出各等级金额,不能猜默认值。未配置正 int64 金额前保持 disabled;运营填入 +-- metadata_json.coin_amount 并启用后,后台校验和日 run 创建都会再次严格校验。 +UPDATE vip_level_benefits +SET status = 'disabled', trial_enabled = FALSE +WHERE app_code = 'fami' + AND benefit_code = 'daily_coin_rebate' + AND ( + JSON_EXTRACT(metadata_json, '$.coin_amount') IS NULL + OR JSON_TYPE(JSON_EXTRACT(metadata_json, '$.coin_amount')) <> 'INTEGER' + OR CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.coin_amount')) AS DECIMAL(65, 0)) <= 0 + OR CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.coin_amount')) AS DECIMAL(65, 0)) > 9223372036854775807 + ); + +-- 老环境可能先执行过不含文案的 seed;这里只补空元数据,运营已配置的 App 文案必须保留。 +UPDATE vip_level_benefits +SET metadata_json = JSON_OBJECT('message', '欢迎进入Fami,祝你有美好的一天') +WHERE app_code = 'fami' + AND benefit_code = 'online_global_notice' + AND COALESCE(JSON_LENGTH(metadata_json), 0) = 0; + +-- Fami 的九种体验卡只预置可发放的背包资源和等级元数据,不预置素材、不固定 resource_id、 +-- 不猜测卡片时长;实际发放必须由 GrantVipTrialCard 显式提交 level+duration_ms。 +INSERT IGNORE INTO resources ( + app_code, resource_code, resource_type, name, status, grantable, manager_grant_enabled, + grant_strategy, wallet_asset_type, wallet_asset_amount, price_type, coin_price, gift_point_amount, + usage_scope_json, asset_url, preview_url, animation_url, metadata_json, sort_order, + created_by_user_id, updated_by_user_id, created_at_ms, updated_at_ms +) VALUES + ('fami', 'vip_trial_card_1', 'vip_trial_card', 'VIP1体验卡', 'active', TRUE, TRUE, + 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":1}', 10, 0, 0, 0, 0), + ('fami', 'vip_trial_card_2', 'vip_trial_card', 'VIP2体验卡', 'active', TRUE, TRUE, + 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":2}', 20, 0, 0, 0, 0), + ('fami', 'vip_trial_card_3', 'vip_trial_card', 'VIP3体验卡', 'active', TRUE, TRUE, + 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":3}', 30, 0, 0, 0, 0), + ('fami', 'vip_trial_card_4', 'vip_trial_card', 'VIP4体验卡', 'active', TRUE, TRUE, + 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":4}', 40, 0, 0, 0, 0), + ('fami', 'vip_trial_card_5', 'vip_trial_card', 'VIP5体验卡', 'active', TRUE, TRUE, + 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":5}', 50, 0, 0, 0, 0), + ('fami', 'vip_trial_card_6', 'vip_trial_card', 'VIP6体验卡', 'active', TRUE, TRUE, + 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":6}', 60, 0, 0, 0, 0), + ('fami', 'vip_trial_card_7', 'vip_trial_card', 'VIP7体验卡', 'active', TRUE, TRUE, + 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":7}', 70, 0, 0, 0, 0), + ('fami', 'vip_trial_card_8', 'vip_trial_card', 'VIP8体验卡', 'active', TRUE, TRUE, + 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":8}', 80, 0, 0, 0, 0), + ('fami', 'vip_trial_card_9', 'vip_trial_card', 'VIP9体验卡', 'active', TRUE, TRUE, + 'new_entitlement', '', 0, 'free', 0, 0, '{}', '', '', '', '{"vip_level":9}', 90, 0, 0, 0, 0); INSERT IGNORE INTO gift_type_configs ( app_code, type_code, name, tab_key, status, sort_order, diff --git a/services/wallet-service/deploy/mysql/maintenance/20260711_retry_incomplete_resource_outbox.sql b/services/wallet-service/deploy/mysql/maintenance/20260711_retry_incomplete_resource_outbox.sql new file mode 100644 index 00000000..ba28ad8e --- /dev/null +++ b/services/wallet-service/deploy/mysql/maintenance/20260711_retry_incomplete_resource_outbox.sql @@ -0,0 +1,110 @@ +-- wallet resource outbox transaction_id repair and controlled retry. +-- +-- This file is deliberately outside initdb/migrations: Phase A must not replay any +-- historical message. Run it only after Phase B (`wallet_event_tag_mode=event_type`) +-- is live, all wallet consumer subscriptions are healthy, and the target rows below +-- have been reviewed. One execution handles at most 100 unlocked rows; repeat while +-- observing MQ lag and consumer errors. +-- +-- Preview-only (default, never mutates rows): +-- mysql ... < 20260711_retry_incomplete_resource_outbox.sql +-- +-- Explicit Phase-B execution in the same mysql session: +-- SET @execute_wallet_resource_outbox_retry = 1; +-- SOURCE /absolute/path/20260711_retry_incomplete_resource_outbox.sql; + +SET @execute_wallet_resource_outbox_retry := + IF(COALESCE(@execute_wallet_resource_outbox_retry, 0) = 1, 1, 0); + +DROP TEMPORARY TABLE IF EXISTS tmp_wallet_resource_outbox_retry; +CREATE TEMPORARY TABLE tmp_wallet_resource_outbox_retry ( + app_code VARCHAR(32) NOT NULL, + event_id VARCHAR(128) NOT NULL, + PRIMARY KEY (app_code, event_id) +) ENGINE=MEMORY; + +-- The event list is the complete set currently created through resourceOutboxEvent. +-- Empty transaction_id plus the exact encoder error prevents this maintenance action +-- from touching financial facts or resource failures caused by any other condition. +INSERT INTO tmp_wallet_resource_outbox_retry (app_code, event_id) +SELECT app_code, event_id +FROM wallet_outbox +WHERE asset_type = 'RESOURCE' + AND event_type IN ( + 'GiftConfigChanged', + 'ResourceChanged', + 'ResourceGrantRevoked', + 'ResourceGranted', + 'ResourceGroupChanged', + 'ResourceGroupGranted', + 'UserResourceChanged', + 'VipEffectiveChanged', + 'VipTrialCardGranted' + ) + AND transaction_id = '' + AND BINARY last_error = BINARY 'wallet outbox message is incomplete' + AND status IN ('failed', 'retryable') + AND worker_id = '' + AND lock_until_ms IS NULL +ORDER BY created_at_ms, event_id +LIMIT 100; + +SELECT + wo.app_code, + wo.event_id, + wo.event_type, + wo.status, + wo.retry_count, + wo.created_at_ms +FROM wallet_outbox wo +JOIN tmp_wallet_resource_outbox_retry target + ON target.app_code = wo.app_code AND target.event_id = wo.event_id +ORDER BY wo.created_at_ms, wo.event_id; + +START TRANSACTION; + +-- @execute_wallet_resource_outbox_retry defaults to 0. When explicitly set to 1, +-- transaction_id=event_id restores a stable correlation ID and status=pending asks +-- the already-running Phase-B worker to publish exactly the original outbox fact. +UPDATE wallet_outbox wo +JOIN tmp_wallet_resource_outbox_retry target + ON target.app_code = wo.app_code AND target.event_id = wo.event_id +SET wo.transaction_id = wo.event_id, + wo.status = 'pending', + wo.worker_id = '', + wo.lock_until_ms = NULL, + wo.retry_count = 0, + wo.next_retry_at_ms = NULL, + wo.last_error = NULL, + wo.updated_at_ms = CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED) +WHERE @execute_wallet_resource_outbox_retry = 1 + AND wo.asset_type = 'RESOURCE' + AND wo.event_type IN ( + 'GiftConfigChanged', + 'ResourceChanged', + 'ResourceGrantRevoked', + 'ResourceGranted', + 'ResourceGroupChanged', + 'ResourceGroupGranted', + 'UserResourceChanged', + 'VipEffectiveChanged', + 'VipTrialCardGranted' + ) + AND wo.transaction_id = '' + AND BINARY wo.last_error = BINARY 'wallet outbox message is incomplete' + AND wo.status IN ('failed', 'retryable') + AND wo.worker_id = '' + AND wo.lock_until_ms IS NULL; + +SET @wallet_resource_outbox_requeued_rows := ROW_COUNT(); +COMMIT; + +SELECT + @execute_wallet_resource_outbox_retry AS execution_enabled, + @wallet_resource_outbox_requeued_rows AS requeued_rows, + CASE + WHEN @execute_wallet_resource_outbox_retry = 1 THEN 'phase_b_batch_requeued' + ELSE 'preview_only_no_rows_changed' + END AS result; + +DROP TEMPORARY TABLE IF EXISTS tmp_wallet_resource_outbox_retry; diff --git a/services/wallet-service/internal/app/app.go b/services/wallet-service/internal/app/app.go index b9aea1a8..353f4169 100644 --- a/services/wallet-service/internal/app/app.go +++ b/services/wallet-service/internal/app/app.go @@ -13,6 +13,7 @@ import ( "hyapp/pkg/grpchealth" "hyapp/pkg/healthhttp" "hyapp/pkg/logx" + "hyapp/pkg/outboxpartition" "hyapp/pkg/rocketmqx" servicegrpc "hyapp/pkg/servicekit/grpcserver" servicehealth "hyapp/pkg/servicekit/health" @@ -31,22 +32,27 @@ import ( // App 装配 wallet-service gRPC 入口。 type App struct { - server *grpc.Server - listener net.Listener - health *grpchealth.ServingChecker - healthHTTP *healthhttp.Server - mysqlRepo *mysqlstorage.Repository - walletSvc *walletservice.Service - activityConn *grpc.ClientConn - outboxProducer *rocketmqx.Producer - realtimeOutboxProducer *rocketmqx.Producer - projectionConsumer *rocketmqx.Consumer - outboxWorkerCfg config.OutboxWorkerConfig - realtimeOutboxWorkerCfg config.OutboxWorkerConfig + server *grpc.Server + listener net.Listener + health *grpchealth.ServingChecker + healthHTTP *healthhttp.Server + mysqlRepo *mysqlstorage.Repository + walletSvc *walletservice.Service + activityConn *grpc.ClientConn + outboxProducer *rocketmqx.Producer + realtimeOutboxProducer *rocketmqx.Producer + projectionConsumer *rocketmqx.Consumer + outboxWorkerCfg config.OutboxWorkerConfig + realtimeOutboxWorkerCfg config.OutboxWorkerConfig + // 普通与实时 lane 共享同一张表但可独立配置事件类型;各自维护 App 轮转游标,避免一个 lane 的吞吐改变另一个 lane 的公平性。 + outboxPartitions *outboxpartition.Partitions + realtimeOutboxPartitions *outboxpartition.Partitions projectionWorkerCfg config.ProjectionWorkerConfig + googlePaidSyncWorkerCfg config.GooglePaidSyncWorkerConfig externalRechargeReconcileWorkerCfg config.ExternalRechargeReconcileWorkerConfig walletOutboxTopic string realtimeWalletOutboxTopic string + walletEventTagMode config.WalletEventTagMode nodeID string redPacketExpiryWorkerCfg config.RedPacketExpiryWorkerConfig stopWorker context.CancelFunc @@ -185,6 +191,9 @@ func New(cfg config.Config) (*App, error) { _ = repository.Close() return nil, err } + googlePaidSyncWorkerCfg := cfg.GooglePaidSyncWorker + // A disabled Google client cannot make progress; suppress the worker instead of emitting a permanent error loop in local environments. + googlePaidSyncWorkerCfg.Enabled = googlePaidSyncWorkerCfg.Enabled && cfg.GooglePlay.Enabled return &App{ server: server, @@ -199,10 +208,14 @@ func New(cfg config.Config) (*App, error) { projectionConsumer: projectionConsumer, outboxWorkerCfg: cfg.OutboxWorker, realtimeOutboxWorkerCfg: cfg.RealtimeOutboxWorker, + outboxPartitions: outboxpartition.New(cfg.OutboxWorker.AppCodes, outboxpartition.DefaultDiscoveryRefreshInterval), + realtimeOutboxPartitions: outboxpartition.New(cfg.RealtimeOutboxWorker.AppCodes, outboxpartition.DefaultDiscoveryRefreshInterval), projectionWorkerCfg: cfg.ProjectionWorker, + googlePaidSyncWorkerCfg: googlePaidSyncWorkerCfg, externalRechargeReconcileWorkerCfg: cfg.ExternalRechargeReconcileWorker, walletOutboxTopic: cfg.RocketMQ.WalletOutbox.Topic, realtimeWalletOutboxTopic: cfg.RocketMQ.RealtimeOutbox.Topic, + walletEventTagMode: cfg.RocketMQ.WalletEventTagMode, nodeID: cfg.NodeID, redPacketExpiryWorkerCfg: cfg.RedPacketExpiryWorker, }, nil diff --git a/services/wallet-service/internal/app/background_workers.go b/services/wallet-service/internal/app/background_workers.go index e8ec4ccb..5ac97f22 100644 --- a/services/wallet-service/internal/app/background_workers.go +++ b/services/wallet-service/internal/app/background_workers.go @@ -14,14 +14,17 @@ func (a *App) runBackgroundWorkers() { // 普通账务 worker 在实时通道可用时跳过红包 UI 事件,避免两个 worker 抢同一行。 excludedRealtimeTypes = a.realtimeOutboxWorkerCfg.EventTypes } - a.startWalletOutboxWorkers(ctx, "wallet-outbox", a.outboxWorkerCfg, a.outboxProducer, a.walletOutboxTopic, nil, excludedRealtimeTypes) + a.startWalletOutboxWorkers(ctx, "wallet-outbox", a.outboxWorkerCfg, a.outboxPartitions, a.outboxProducer, a.walletOutboxTopic, nil, excludedRealtimeTypes) } if a.realtimeOutboxWorkerCfg.Enabled && a.realtimeOutboxProducer != nil { - a.startWalletOutboxWorkers(ctx, "wallet-realtime-outbox", a.realtimeOutboxWorkerCfg, a.realtimeOutboxProducer, a.realtimeWalletOutboxTopic, a.realtimeOutboxWorkerCfg.EventTypes, nil) + a.startWalletOutboxWorkers(ctx, "wallet-realtime-outbox", a.realtimeOutboxWorkerCfg, a.realtimeOutboxPartitions, a.realtimeOutboxProducer, a.realtimeWalletOutboxTopic, a.realtimeOutboxWorkerCfg.EventTypes, nil) } if a.externalRechargeReconcileWorkerCfg.Enabled { a.startExternalRechargeReconcileWorker(ctx) } + if a.googlePaidSyncWorkerCfg.Enabled { + a.startGooglePaidSyncWorker(ctx) + } if a.redPacketExpiryWorkerCfg.Enabled { a.startRedPacketExpiryWorker(ctx) } diff --git a/services/wallet-service/internal/app/google_paid_sync_worker.go b/services/wallet-service/internal/app/google_paid_sync_worker.go new file mode 100644 index 00000000..31de1af2 --- /dev/null +++ b/services/wallet-service/internal/app/google_paid_sync_worker.go @@ -0,0 +1,45 @@ +package app + +import ( + "context" + "errors" + "log/slog" + "time" + + "hyapp/pkg/logx" +) + +func (a *App) startGooglePaidSyncWorker(ctx context.Context) { + workerID := "google-paid-sync-" + a.nodeID + a.workers.Add(1) + go func() { + defer a.workers.Done() + ticker := time.NewTicker(a.googlePaidSyncWorkerCfg.PollInterval) + defer ticker.Stop() + for { + requestCtx, cancel := context.WithTimeout(ctx, a.googlePaidSyncWorkerCfg.RequestTimeout) + result, err := a.walletSvc.ProcessGooglePaymentPaidSyncBatch(requestCtx, a.googlePaidSyncWorkerCfg.BatchSize) + cancel() + if err != nil && !errors.Is(err, context.Canceled) { + logx.Error(ctx, "google_paid_sync_batch_failed", err, slog.String("worker_id", workerID)) + } + if result.Claimed > 0 { + logx.Info(ctx, "google_paid_sync_batch_completed", + slog.String("worker_id", workerID), + slog.Int("claimed", result.Claimed), + slog.Int("succeeded", result.Succeeded), + slog.Int("failed", result.Failed), + ) + } + // A full batch means historical backlog remains. Drain immediately; once caught up, poll every 15 seconds. + if err == nil && result.Claimed >= a.googlePaidSyncWorkerCfg.BatchSize { + continue + } + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + } + }() +} diff --git a/services/wallet-service/internal/app/mq.go b/services/wallet-service/internal/app/mq.go index 140d3c68..de71e791 100644 --- a/services/wallet-service/internal/app/mq.go +++ b/services/wallet-service/internal/app/mq.go @@ -31,12 +31,22 @@ func newWalletProjectionConsumer(cfg config.Config, svc *walletservice.Service) if svc == nil || !cfg.ProjectionWorker.Enabled { return nil, nil } + // 钱包自有投影只关心礼物墙和徽章发放事实;兼容 selector 同时保留 + // legacy Tag,待旧消息排空后可在后续阶段只留下这三个 typed Tag。 + tagExpression, err := walletmq.LegacyCompatibleTagExpression( + walletmq.EventTypeWalletGiftDebited, + walletmq.EventTypeResourceGranted, + walletmq.EventTypeResourceGroupGranted, + ) + if err != nil { + return nil, err + } consumer, err := rocketmqx.NewConsumer(rocketMQProjectionConsumerConfig(cfg.RocketMQ, cfg.ProjectionWorker)) if err != nil { return nil, err } workerID := "wallet-projection-" + cfg.NodeID - if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, tagExpression, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { walletMessage, err := walletmq.DecodeWalletOutboxMessage(message.Body) if err != nil { return err diff --git a/services/wallet-service/internal/app/outbox_replay.go b/services/wallet-service/internal/app/outbox_replay.go new file mode 100644 index 00000000..bb96875d --- /dev/null +++ b/services/wallet-service/internal/app/outbox_replay.go @@ -0,0 +1,168 @@ +package app + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/outboxreplay" + "hyapp/pkg/rocketmqx" + servicemq "hyapp/pkg/servicekit/mq" + "hyapp/pkg/walletmq" + "hyapp/services/wallet-service/internal/config" + mysqlstorage "hyapp/services/wallet-service/internal/storage/mysql" +) + +// ReplayWalletOutboxOnce publishes at most scope.Limit claimable facts from +// exactly one App partition, then closes both MySQL and MQ. Each iteration uses +// the production claim/publish/mark path with batch size one, so a process +// timeout cannot leave a claimed tail waiting for lease expiry. +func ReplayWalletOutboxOnce(ctx context.Context, cfg config.Config, scope outboxreplay.Scope, eventTypes []string) (outboxreplay.Result, error) { + validatedScope, err := outboxreplay.NewScope(scope.AppCode, scope.Limit, 500) + if err != nil { + return outboxreplay.Result{}, err + } + includeEventTypes, err := ValidateWalletReplayEventTypes(cfg, eventTypes) + if err != nil { + return outboxreplay.Result{}, err + } + if !cfg.RocketMQ.WalletOutbox.Enabled { + return outboxreplay.Result{}, errors.New("wallet RocketMQ outbox is not enabled") + } + + repository, err := mysqlstorage.Open(ctx, cfg.MySQLDSN) + if err != nil { + return outboxreplay.Result{}, err + } + defer func() { _ = repository.Close() }() + producer, err := rocketmqx.NewProducer(rocketMQProducerConfig(cfg.RocketMQ, cfg.RocketMQ.WalletOutbox.ProducerGroup)) + if err != nil { + return outboxreplay.Result{}, err + } + if err := servicemq.StartProducers([]*rocketmqx.Producer{producer}); err != nil { + return outboxreplay.Result{}, err + } + defer servicemq.ShutdownProducers([]*rocketmqx.Producer{producer}) + + workerOptions := cfg.OutboxWorker + workerOptions.BatchSize = 1 + workerOptions.Concurrency = 1 + replayApp := &App{ + mysqlRepo: repository, + walletEventTagMode: cfg.RocketMQ.WalletEventTagMode, + walletOutboxTopic: cfg.RocketMQ.WalletOutbox.Topic, + outboxWorkerCfg: workerOptions, + } + workerID := fmt.Sprintf("ops-wallet-outbox-%s-%d", validatedScope.AppCode, time.Now().UTC().UnixMilli()) + return outboxreplay.Run(ctx, validatedScope, func(runCtx context.Context) (int, error) { + return replayApp.replayOneWalletOutboxRecord(runCtx, workerID, workerOptions, producer, cfg.RocketMQ.WalletOutbox.Topic, validatedScope.AppCode, includeEventTypes) + }) +} + +func (a *App) replayOneWalletOutboxRecord(ctx context.Context, workerID string, options config.OutboxWorkerConfig, producer *rocketmqx.Producer, topic string, targetAppCode string, includeEventTypes []string) (int, error) { + if options.PublishTimeout <= 0 { + options.PublishTimeout = 3 * time.Second + } + claimCtx := appcode.WithContext(ctx, targetAppCode) + records, err := a.mysqlRepo.ClaimPendingWalletOutboxFiltered( + claimCtx, + workerID, + 1, + time.Now().UTC().Add(walletOutboxSingleRecordLease(options.PublishTimeout)).UnixMilli(), + mysqlstorage.WalletOutboxClaimFilter{IncludeEventTypes: includeEventTypes}, + ) + if err != nil || len(records) == 0 { + return 0, err + } + record := records[0] + if record.RetryCount >= options.MaxRetryCount { + markCtx, cancel := context.WithTimeout(appcode.WithContext(context.Background(), record.AppCode), options.PublishTimeout) + markErr := a.mysqlRepo.MarkWalletOutboxDead(markCtx, record.EventID, deadWalletOutboxReason(record)) + cancel() + if markErr != nil { + return 1, markErr + } + return 1, fmt.Errorf("wallet outbox event %s reached max retry count and was marked dead", record.EventID) + } + + publishCtx, cancel := context.WithTimeout(appcode.WithContext(context.Background(), record.AppCode), options.PublishTimeout) + publishErr := a.publishWalletOutboxRecord(publishCtx, producer, topic, record) + cancel() + markCtx, markCancel := context.WithTimeout(appcode.WithContext(context.Background(), record.AppCode), options.PublishTimeout) + defer markCancel() + if publishErr != nil { + nextRetryAtMS := time.Now().UTC().Add(walletOutboxBackoff(record.RetryCount+1, options)).UnixMilli() + if markErr := a.mysqlRepo.MarkWalletOutboxRetryable(markCtx, record.EventID, publishErr.Error(), nextRetryAtMS); markErr != nil { + return 1, errors.Join(publishErr, markErr) + } + // The row was safely released for a later retry, but this explicit run must + // fail instead of reporting a merely claimed row as a delivered replay. + return 1, publishErr + } + if err := a.mysqlRepo.MarkWalletOutboxDelivered(markCtx, record.EventID); err != nil { + return 1, err + } + return 1, nil +} + +// NormalizeWalletReplayEventTypes validates the exact MQ tags before an +// operator dry-run is promoted to an executing run. +func NormalizeWalletReplayEventTypes(values []string) ([]string, error) { + if len(values) > 32 { + return nil, errors.New("wallet event-type count cannot exceed 32") + } + normalized := make([]string, 0, len(values)) + seen := make(map[string]struct{}, len(values)) + for _, value := range values { + eventType, err := walletmq.EventTypeTag(strings.TrimSpace(value)) + if err != nil { + return nil, err + } + if _, exists := seen[eventType]; exists { + continue + } + seen[eventType] = struct{}{} + normalized = append(normalized, eventType) + } + return normalized, nil +} + +// ValidateWalletReplayEventTypes applies the lane boundary in both dry-run and +// execute modes. The normal-topic repair must reject realtime event types even +// while that worker or topic is temporarily disabled; pausing a lane does not +// transfer ownership of its durable rows to another topic. +func ValidateWalletReplayEventTypes(cfg config.Config, values []string) ([]string, error) { + normalized, err := NormalizeWalletReplayEventTypes(values) + if err != nil { + return nil, err + } + if len(normalized) == 0 { + // Wallet fanout has both high-volume balance notices and low-volume config + // facts. Requiring an explicit set prevents a small operational command + // from accidentally mixing stages merely because their timestamps overlap. + return nil, errors.New("at least one wallet event-type is required") + } + if err := rejectRealtimeReplayEventTypes(normalized, cfg.RealtimeOutboxWorker.EventTypes); err != nil { + return nil, err + } + return normalized, nil +} + +func rejectRealtimeReplayEventTypes(included []string, realtime []string) error { + realtimeSet := make(map[string]struct{}, len(realtime)) + for _, eventType := range realtime { + eventType = strings.TrimSpace(eventType) + if eventType != "" { + realtimeSet[eventType] = struct{}{} + } + } + for _, eventType := range included { + if _, exists := realtimeSet[eventType]; exists { + return fmt.Errorf("wallet event-type %s belongs to the realtime outbox lane", eventType) + } + } + return nil +} diff --git a/services/wallet-service/internal/app/outbox_replay_test.go b/services/wallet-service/internal/app/outbox_replay_test.go new file mode 100644 index 00000000..41d2a6fe --- /dev/null +++ b/services/wallet-service/internal/app/outbox_replay_test.go @@ -0,0 +1,43 @@ +package app + +import ( + "context" + "strings" + "testing" + + "hyapp/pkg/outboxreplay" + "hyapp/services/wallet-service/internal/config" +) + +func TestNormalizeWalletReplayEventTypes(t *testing.T) { + eventTypes, err := NormalizeWalletReplayEventTypes([]string{" GiftConfigChanged ", "WalletBalanceChanged", "GiftConfigChanged"}) + if err != nil { + t.Fatalf("NormalizeWalletReplayEventTypes failed: %v", err) + } + if len(eventTypes) != 2 || eventTypes[0] != "GiftConfigChanged" || eventTypes[1] != "WalletBalanceChanged" { + t.Fatalf("normalized event types mismatch: %v", eventTypes) + } + if _, err := NormalizeWalletReplayEventTypes([]string{"bad || tag"}); err == nil { + t.Fatal("expected invalid RocketMQ tag rejection") + } +} + +func TestRejectRealtimeReplayEventTypes(t *testing.T) { + if err := rejectRealtimeReplayEventTypes([]string{"GiftConfigChanged"}, []string{"WalletRedPacketCreated"}); err != nil { + t.Fatalf("normal event rejected: %v", err) + } + if err := rejectRealtimeReplayEventTypes([]string{"WalletRedPacketCreated"}, []string{"WalletRedPacketCreated"}); err == nil { + t.Fatal("expected realtime lane rejection") + } +} + +func TestWalletReplayRejectsRealtimeTypeWhenLaneIsDisabled(t *testing.T) { + cfg := config.Default() + cfg.RealtimeOutboxWorker.Enabled = false + cfg.RocketMQ.RealtimeOutbox.Enabled = false + cfg.RealtimeOutboxWorker.EventTypes = []string{"WalletRedPacketCreated"} + _, err := ReplayWalletOutboxOnce(context.Background(), cfg, outboxreplay.Scope{AppCode: "huwaa", Limit: 1}, []string{"WalletRedPacketCreated"}) + if err == nil || !strings.Contains(err.Error(), "realtime outbox lane") { + t.Fatalf("disabled realtime lane must retain event ownership, err=%v", err) + } +} diff --git a/services/wallet-service/internal/app/outbox_worker.go b/services/wallet-service/internal/app/outbox_worker.go index 40aa06da..408ddeec 100644 --- a/services/wallet-service/internal/app/outbox_worker.go +++ b/services/wallet-service/internal/app/outbox_worker.go @@ -10,13 +10,14 @@ import ( "hyapp/pkg/appcode" "hyapp/pkg/logx" + "hyapp/pkg/outboxpartition" "hyapp/pkg/rocketmqx" "hyapp/pkg/walletmq" "hyapp/services/wallet-service/internal/config" mysqlstorage "hyapp/services/wallet-service/internal/storage/mysql" ) -func (a *App) startWalletOutboxWorkers(ctx context.Context, workerName string, options config.OutboxWorkerConfig, producer *rocketmqx.Producer, topic string, includeEventTypes []string, excludeEventTypes []string) { +func (a *App) startWalletOutboxWorkers(ctx context.Context, workerName string, options config.OutboxWorkerConfig, partitions *outboxpartition.Partitions, producer *rocketmqx.Producer, topic string, includeEventTypes []string, excludeEventTypes []string) { if options.Concurrency <= 0 { options.Concurrency = 1 } @@ -25,16 +26,16 @@ func (a *App) startWalletOutboxWorkers(ctx context.Context, workerName string, o a.workers.Add(1) go func() { defer a.workers.Done() - a.runWalletOutboxWorker(ctx, workerID, options, producer, topic, includeEventTypes, excludeEventTypes) + a.runWalletOutboxWorker(ctx, workerID, options, partitions, producer, topic, includeEventTypes, excludeEventTypes) }() } } -func (a *App) runWalletOutboxWorker(ctx context.Context, workerID string, options config.OutboxWorkerConfig, producer *rocketmqx.Producer, topic string, includeEventTypes []string, excludeEventTypes []string) { +func (a *App) runWalletOutboxWorker(ctx context.Context, workerID string, options config.OutboxWorkerConfig, partitions *outboxpartition.Partitions, producer *rocketmqx.Producer, topic string, includeEventTypes []string, excludeEventTypes []string) { ticker := time.NewTicker(options.PollInterval) defer ticker.Stop() for { - processed, err := a.processWalletOutboxBatch(ctx, workerID, options, producer, topic, includeEventTypes, excludeEventTypes) + processed, err := a.processWalletOutboxBatch(ctx, workerID, options, partitions, producer, topic, includeEventTypes, excludeEventTypes) if err != nil && !errors.Is(err, context.Canceled) { logx.Error(ctx, "wallet_outbox_publish_failed", err, slog.String("worker_id", workerID)) } @@ -49,29 +50,51 @@ func (a *App) runWalletOutboxWorker(ctx context.Context, workerID string, option } } -func (a *App) processWalletOutboxBatch(ctx context.Context, workerID string, options config.OutboxWorkerConfig, producer *rocketmqx.Producer, topic string, includeEventTypes []string, excludeEventTypes []string) (int, error) { +func (a *App) processWalletOutboxBatch(ctx context.Context, workerID string, options config.OutboxWorkerConfig, partitions *outboxpartition.Partitions, producer *rocketmqx.Producer, topic string, includeEventTypes []string, excludeEventTypes []string) (int, error) { if a.mysqlRepo == nil || producer == nil { return 0, nil } - records, err := a.mysqlRepo.ClaimPendingWalletOutboxFiltered(ctx, workerID, options.BatchSize, time.Now().UTC().Add(options.PublishTimeout).UnixMilli(), mysqlstorage.WalletOutboxClaimFilter{ - IncludeEventTypes: includeEventTypes, - ExcludeEventTypes: excludeEventTypes, - }) - if err != nil { - return 0, err + appCodes, discoveryErr := partitions.Resolve(ctx, a.mysqlRepo.ListWalletOutboxAppCodes) + if discoveryErr != nil { + if len(appCodes) == 0 { + return 0, discoveryErr + } + // 已知 App 继续投递,刷新失败只影响新租户发现;缓存层会延后下一次查询,避免热循环打 MySQL。 + logx.Warn(ctx, "wallet_outbox_app_discovery_stale", slog.String("worker_id", workerID), slog.String("error", discoveryErr.Error())) } - for _, record := range records { + if len(appCodes) == 0 { + return 0, nil + } + processed := 0 + for processed < options.BatchSize { + // Sync publish is intentionally claimed one record at a time. Claiming an entire + // batch with one short lock lets later rows expire while earlier SendSync calls + // are still running, so another replica can publish the same tail concurrently. + records, err := outboxpartition.ClaimFair(ctx, partitions.Order(appCodes), 1, func(claimCtx context.Context, appCode string, limit int) ([]mysqlstorage.WalletOutboxRecord, error) { + return a.mysqlRepo.ClaimPendingWalletOutboxFiltered(appcode.WithContext(claimCtx, appCode), workerID, limit, time.Now().UTC().Add(walletOutboxSingleRecordLease(options.PublishTimeout)).UnixMilli(), mysqlstorage.WalletOutboxClaimFilter{ + IncludeEventTypes: includeEventTypes, + ExcludeEventTypes: excludeEventTypes, + }) + }) + if err != nil { + return processed, err + } + if len(records) == 0 { + return processed, nil + } + record := records[0] + processed++ if record.RetryCount >= options.MaxRetryCount { markCtx, cancel := context.WithTimeout(appcode.WithContext(context.Background(), record.AppCode), options.PublishTimeout) err := a.mysqlRepo.MarkWalletOutboxDead(markCtx, record.EventID, deadWalletOutboxReason(record)) cancel() if err != nil { - return 0, err + return processed, err } continue } publishCtx, cancel := context.WithTimeout(appcode.WithContext(context.Background(), record.AppCode), options.PublishTimeout) - err := a.publishWalletOutboxRecord(publishCtx, producer, topic, record) + err = a.publishWalletOutboxRecord(publishCtx, producer, topic, record) cancel() if err != nil { nextRetryAtMS := time.Now().UTC().Add(walletOutboxBackoff(record.RetryCount+1, options)).UnixMilli() @@ -79,7 +102,7 @@ func (a *App) processWalletOutboxBatch(ctx context.Context, workerID string, opt markErr := a.mysqlRepo.MarkWalletOutboxRetryable(markCtx, record.EventID, err.Error(), nextRetryAtMS) markCancel() if markErr != nil { - return 0, markErr + return processed, markErr } continue } @@ -87,18 +110,60 @@ func (a *App) processWalletOutboxBatch(ctx context.Context, workerID string, opt markErr := a.mysqlRepo.MarkWalletOutboxDelivered(markCtx, record.EventID) markCancel() if markErr != nil { - return 0, markErr + return processed, markErr } } - return len(records), nil + return processed, nil +} + +func walletOutboxSingleRecordLease(publishTimeout time.Duration) time.Duration { + if publishTimeout <= 0 { + publishTimeout = 3 * time.Second + } + // 一条事实最多经历一次 publish deadline 和一次状态落库 deadline;再保留 + // 一个 deadline 作为调度余量。进程崩溃后的接管时间仍与单条消息耗时有界, + // 不会随着 batch_size 放大成数十分钟。 + return 3 * publishTimeout } func (a *App) publishWalletOutboxRecord(ctx context.Context, producer *rocketmqx.Producer, topic string, record mysqlstorage.WalletOutboxRecord) error { + message, err := walletOutboxRocketMessage(topic, a.walletEventTagMode, record) + if err != nil { + return err + } + return producer.SendSync(ctx, message) +} + +// walletOutboxRocketMessage keeps the envelope event_type canonical in both phases. +// Phase A deliberately publishes the legacy Tag; Phase B uses that same event_type as +// the Broker routing Tag, avoiding any derived category that could drift from handlers. +func walletOutboxRocketMessage(topic string, tagMode config.WalletEventTagMode, record mysqlstorage.WalletOutboxRecord) (rocketmqx.Message, error) { + eventTag, err := walletmq.EventTypeTag(record.EventType) + if err != nil { + return rocketmqx.Message{}, err + } + publishTag := walletmq.TagWalletOutboxEvent + switch tagMode { + case "", config.WalletEventTagModeLegacy: + // 空值属于旧配置或测试构造,必须保持 legacy;只有显式 event_type 才能进入 Phase B。 + case config.WalletEventTagModeEventType: + publishTag = eventTag + default: + return rocketmqx.Message{}, fmt.Errorf("unsupported wallet event tag mode %q", tagMode) + } + transactionID := strings.TrimSpace(record.TransactionID) + if transactionID == "" && isHistoricalResourceOutboxRecord(record) { + // 2026-07-11 以前的资源事实没有金融 transaction_id;event_id 已由 + // event_type+command_id+user_id+resource_id 稳定派生。发布时使用它作为 + // correlation id 与当前写入逻辑一致,避免为了回放改写已提交的 payload, + // 同时严格限制为 RESOURCE 和已知资源事件,绝不替金融事实补造交易号。 + transactionID = strings.TrimSpace(record.EventID) + } body, err := walletmq.EncodeWalletOutboxMessage(walletmq.WalletOutboxMessage{ AppCode: record.AppCode, EventID: record.EventID, - EventType: record.EventType, - TransactionID: record.TransactionID, + EventType: eventTag, + TransactionID: transactionID, CommandID: record.CommandID, UserID: record.UserID, AssetType: record.AssetType, @@ -108,14 +173,34 @@ func (a *App) publishWalletOutboxRecord(ctx context.Context, producer *rocketmqx OccurredAtMS: record.CreatedAtMS, }) if err != nil { - return err + return rocketmqx.Message{}, err } - return producer.SendSync(ctx, rocketmqx.Message{ + return rocketmqx.Message{ Topic: topic, - Tag: walletmq.TagWalletOutboxEvent, - Keys: []string{record.EventID, record.TransactionID, record.CommandID}, + Tag: publishTag, + Keys: []string{record.EventID, transactionID, record.CommandID}, Body: body, - }) + }, nil +} + +func isHistoricalResourceOutboxRecord(record mysqlstorage.WalletOutboxRecord) bool { + if strings.TrimSpace(record.EventID) == "" || !strings.EqualFold(strings.TrimSpace(record.AssetType), "RESOURCE") { + return false + } + switch strings.TrimSpace(record.EventType) { + case "GiftConfigChanged", + "ResourceChanged", + "ResourceGrantRevoked", + "ResourceGranted", + "ResourceGroupChanged", + "ResourceGroupGranted", + "UserResourceChanged", + "VipEffectiveChanged", + "VipTrialCardGranted": + return true + default: + return false + } } func deadWalletOutboxReason(record mysqlstorage.WalletOutboxRecord) string { diff --git a/services/wallet-service/internal/app/outbox_worker_test.go b/services/wallet-service/internal/app/outbox_worker_test.go new file mode 100644 index 00000000..e32c5403 --- /dev/null +++ b/services/wallet-service/internal/app/outbox_worker_test.go @@ -0,0 +1,142 @@ +package app + +import ( + "testing" + "time" + + "hyapp/pkg/walletmq" + "hyapp/services/wallet-service/internal/config" + mysqlstorage "hyapp/services/wallet-service/internal/storage/mysql" +) + +func TestWalletOutboxSingleRecordLeaseCoversPublishAndStateWrite(t *testing.T) { + if got := walletOutboxSingleRecordLease(3 * time.Second); got != 9*time.Second { + t.Fatalf("single-record lease = %s, want 9s", got) + } + if got := walletOutboxSingleRecordLease(0); got != 9*time.Second { + t.Fatalf("default single-record lease = %s, want 9s", got) + } +} + +func TestWalletOutboxRocketMessageUsesConfiguredTagMode(t *testing.T) { + tests := []struct { + name string + mode config.WalletEventTagMode + wantTag string + }{ + { + name: "missing config remains legacy", + mode: "", + wantTag: walletmq.TagWalletOutboxEvent, + }, + { + name: "phase a publishes legacy", + mode: config.WalletEventTagModeLegacy, + wantTag: walletmq.TagWalletOutboxEvent, + }, + { + name: "phase b publishes event type", + mode: config.WalletEventTagModeEventType, + wantTag: walletmq.EventTypeWalletGiftDebited, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + message, err := walletOutboxRocketMessage("hyapp_wallet_outbox", test.mode, validWalletOutboxRecord()) + if err != nil { + t.Fatalf("walletOutboxRocketMessage failed: %v", err) + } + if message.Tag != test.wantTag { + t.Fatalf("message tag = %q, want %q", message.Tag, test.wantTag) + } + decoded, err := walletmq.DecodeWalletOutboxMessage(message.Body) + if err != nil { + t.Fatalf("DecodeWalletOutboxMessage failed: %v", err) + } + if decoded.EventType != walletmq.EventTypeWalletGiftDebited { + t.Fatalf("body event_type = %q", decoded.EventType) + } + if message.Topic != "hyapp_wallet_outbox" || len(message.Keys) != 3 { + t.Fatalf("message routing metadata mismatch: %+v", message) + } + }) + } +} + +func TestWalletOutboxRocketMessageRejectsUnsupportedTagMode(t *testing.T) { + _, err := walletOutboxRocketMessage("hyapp_wallet_outbox", config.WalletEventTagMode("all"), validWalletOutboxRecord()) + if err == nil { + t.Fatal("walletOutboxRocketMessage unexpectedly accepted an unsupported tag mode") + } +} + +func TestWalletOutboxRocketMessageRejectsUnsafeEventTypeTag(t *testing.T) { + record := validWalletOutboxRecord() + record.EventType = "WalletGiftDebited || *" + _, err := walletOutboxRocketMessage("hyapp_wallet_outbox", config.WalletEventTagModeLegacy, record) + if err == nil { + t.Fatal("walletOutboxRocketMessage unexpectedly accepted an unsafe event_type") + } +} + +func TestWalletOutboxRocketMessageRestoresHistoricalResourceCorrelationID(t *testing.T) { + for _, eventType := range []string{ + "GiftConfigChanged", + "ResourceChanged", + "ResourceGrantRevoked", + "ResourceGranted", + "ResourceGroupChanged", + "ResourceGroupGranted", + "UserResourceChanged", + "VipEffectiveChanged", + "VipTrialCardGranted", + } { + t.Run(eventType, func(t *testing.T) { + record := validWalletOutboxRecord() + record.EventID = "wev-historical-resource" + record.EventType = eventType + record.TransactionID = "" + record.AssetType = "RESOURCE" + + message, err := walletOutboxRocketMessage("hyapp_wallet_outbox", config.WalletEventTagModeEventType, record) + if err != nil { + t.Fatalf("historical resource fact was rejected: %v", err) + } + decoded, err := walletmq.DecodeWalletOutboxMessage(message.Body) + if err != nil { + t.Fatalf("DecodeWalletOutboxMessage failed: %v", err) + } + if decoded.TransactionID != record.EventID || message.Keys[1] != record.EventID { + t.Fatalf("correlation id mismatch: decoded=%q keys=%v", decoded.TransactionID, message.Keys) + } + }) + } +} + +func TestWalletOutboxRocketMessageDoesNotInventFinancialTransactionID(t *testing.T) { + record := validWalletOutboxRecord() + record.TransactionID = "" + if _, err := walletOutboxRocketMessage("hyapp_wallet_outbox", config.WalletEventTagModeEventType, record); err == nil { + t.Fatal("financial fact without transaction_id must remain invalid") + } + record.EventType = "UnknownResourceEvent" + record.AssetType = "RESOURCE" + if _, err := walletOutboxRocketMessage("hyapp_wallet_outbox", config.WalletEventTagModeEventType, record); err == nil { + t.Fatal("unknown resource fact must not receive a synthetic transaction_id") + } +} + +func validWalletOutboxRecord() mysqlstorage.WalletOutboxRecord { + return mysqlstorage.WalletOutboxRecord{ + AppCode: "lalu", + EventID: "event-1", + EventType: " " + walletmq.EventTypeWalletGiftDebited + " ", + TransactionID: "transaction-1", + CommandID: "command-1", + UserID: 1001, + AssetType: "COIN", + AvailableDelta: -100, + PayloadJSON: `{"gift_id":"gift-1"}`, + CreatedAtMS: 1_700_000_000_000, + } +} diff --git a/services/wallet-service/internal/client/binance/client_test.go b/services/wallet-service/internal/client/binance/client_test.go index e2c0c802..354048e9 100644 --- a/services/wallet-service/internal/client/binance/client_test.go +++ b/services/wallet-service/internal/client/binance/client_test.go @@ -148,6 +148,20 @@ func TestFindUSDTDepositMissingAccountReturnsConfiguredError(t *testing.T) { } } +func TestAccountForAppSupportsAllProductionApps(t *testing.T) { + client := New(config.BinanceConfig{ + Lalu: config.BinanceAccountConfig{APIKey: "api-lalu", APISecretKey: "secret-lalu"}, + Yumi: config.BinanceAccountConfig{APIKey: "api-yumi", APISecretKey: "secret-yumi"}, + Aslan: config.BinanceAccountConfig{APIKey: "api-aslan", APISecretKey: "secret-aslan"}, + }) + for _, appCode := range []string{"lalu", "yumi", "aslan"} { + account, ok := client.accountForApp(appCode) + if !ok || account.apiKey != "api-"+appCode || account.apiSecretKey != "secret-"+appCode { + t.Fatalf("Binance account mapping mismatch for %s: ok=%v account=%+v", appCode, ok, account) + } + } +} + func TestDepositTxIDMatchesOffchainVariants(t *testing.T) { if !depositTxIDMatches("Off-chain transfer 388611987194", "388611987194") { t.Fatalf("plain off-chain id should match Binance txId") diff --git a/services/wallet-service/internal/client/googleplay/client.go b/services/wallet-service/internal/client/googleplay/client.go index 8326d0c5..0b997abb 100644 --- a/services/wallet-service/internal/client/googleplay/client.go +++ b/services/wallet-service/internal/client/googleplay/client.go @@ -155,6 +155,62 @@ func (c *Client) GetOrder(ctx context.Context, packageName string, orderID strin if err := json.Unmarshal(body, &parsed); err != nil { return ledger.GooglePlayOrder{}, err } + return googlePlayOrderFromResource(parsed, string(body)), nil +} + +// GetOrders fetches up to 1000 paid-detail snapshots with one Orders batchGet request. +// Service code falls back to single gets when Google rejects a mixed batch because one ID is stale. +func (c *Client) GetOrders(ctx context.Context, packageName string, orderIDs []string) (map[string]ledger.GooglePlayOrder, error) { + if c == nil { + return nil, xerr.New(xerr.Unavailable, "google play client is not configured") + } + packageName = c.normalizePackageName(packageName) + cleaned := make([]string, 0, len(orderIDs)) + seen := make(map[string]struct{}, len(orderIDs)) + for _, orderID := range orderIDs { + orderID = strings.TrimSpace(orderID) + if orderID == "" { + continue + } + if _, ok := seen[orderID]; ok { + continue + } + seen[orderID] = struct{}{} + cleaned = append(cleaned, orderID) + } + if packageName == "" || len(cleaned) == 0 { + return map[string]ledger.GooglePlayOrder{}, nil + } + if len(cleaned) > 1000 { + return nil, xerr.New(xerr.InvalidArgument, "too many google order ids") + } + query := url.Values{} + for _, orderID := range cleaned { + query.Add("orderIds", orderID) + } + endpoint := fmt.Sprintf("%s/androidpublisher/v3/applications/%s/orders:batchGet?%s", + c.apiBaseURL, url.PathEscape(packageName), query.Encode()) + body, err := c.doJSON(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + var parsed struct { + Orders []orderResource `json:"orders"` + } + if err := json.Unmarshal(body, &parsed); err != nil { + return nil, err + } + orders := make(map[string]ledger.GooglePlayOrder, len(parsed.Orders)) + for _, item := range parsed.Orders { + order := googlePlayOrderFromResource(item, "") + if order.OrderID != "" { + orders[order.OrderID] = order + } + } + return orders, nil +} + +func googlePlayOrderFromResource(parsed orderResource, rawJSON string) ledger.GooglePlayOrder { order := ledger.GooglePlayOrder{ OrderID: parsed.OrderID, State: parsed.State, @@ -163,12 +219,12 @@ func (c *Client) GetOrder(ctx context.Context, packageName string, orderID strin TotalAmountMicro: parsed.Total.micro(), TaxAmountMicro: parsed.Tax.micro(), NetAmountMicro: parsed.DeveloperRevenueInBuyerCurrency.micro(), - RawJSON: string(body), + RawJSON: rawJSON, } if order.CurrencyCode == "" { order.CurrencyCode = parsed.Tax.CurrencyCode } - return order, nil + return order } func (c *Client) ConsumeProduct(ctx context.Context, packageName string, productID string, purchaseToken string) error { diff --git a/services/wallet-service/internal/config/config.go b/services/wallet-service/internal/config/config.go index eccfc2bf..991afd81 100644 --- a/services/wallet-service/internal/config/config.go +++ b/services/wallet-service/internal/config/config.go @@ -9,6 +9,7 @@ import ( "hyapp/pkg/configx" "hyapp/pkg/logx" + "hyapp/pkg/outboxpartition" ) // Config 描述 wallet-service 启动配置。 @@ -29,6 +30,8 @@ type Config struct { RocketMQ RocketMQConfig `yaml:"rocketmq"` // GooglePlay 控制 Google Play Developer API 购买校验。 GooglePlay GooglePlayConfig `yaml:"google_play"` + // GooglePaidSyncWorker controls durable paid-detail retries after Google purchase confirmation. + GooglePaidSyncWorker GooglePaidSyncWorkerConfig `yaml:"google_paid_sync_worker"` // MifaPay 控制 H5 MiFaPay 站外支付下单和回调验签。 MifaPay MifaPayConfig `yaml:"mifapay"` // V5Pay 控制 H5 V5Pay 站外支付下单、查询和回调验签。 @@ -63,20 +66,33 @@ type RedPacketExpiryWorkerConfig struct { // RocketMQConfig 描述 wallet-service 发布账务事实的 MQ 连接。 type RocketMQConfig struct { - Enabled bool `yaml:"enabled"` - NameServers []string `yaml:"name_servers"` - NameServerDomain string `yaml:"name_server_domain"` - AccessKey string `yaml:"access_key"` - SecretKey string `yaml:"secret_key"` - SecurityToken string `yaml:"security_token"` - Namespace string `yaml:"namespace"` - VIPChannel bool `yaml:"vip_channel"` - SendTimeout time.Duration `yaml:"send_timeout"` - Retry int `yaml:"retry"` - WalletOutbox WalletOutboxMQConfig `yaml:"wallet_outbox"` - RealtimeOutbox WalletOutboxMQConfig `yaml:"realtime_outbox"` + Enabled bool `yaml:"enabled"` + NameServers []string `yaml:"name_servers"` + NameServerDomain string `yaml:"name_server_domain"` + AccessKey string `yaml:"access_key"` + SecretKey string `yaml:"secret_key"` + SecurityToken string `yaml:"security_token"` + Namespace string `yaml:"namespace"` + VIPChannel bool `yaml:"vip_channel"` + SendTimeout time.Duration `yaml:"send_timeout"` + Retry int `yaml:"retry"` + // WalletEventTagMode 是 wallet topic producer 的两阶段发布开关: + // legacy 用统一 Tag 保证旧消费者可见,event_type 仅在所有消费者完成双订阅后启用。 + WalletEventTagMode WalletEventTagMode `yaml:"wallet_event_tag_mode"` + WalletOutbox WalletOutboxMQConfig `yaml:"wallet_outbox"` + RealtimeOutbox WalletOutboxMQConfig `yaml:"realtime_outbox"` } +// WalletEventTagMode 控制钱包事实的 Broker 路由 Tag,不改变消息体协议。 +type WalletEventTagMode string + +const ( + // WalletEventTagModeLegacy 是安全默认值;旧配置缺字段时不能因普通滚动发布提前切 typed Tag。 + WalletEventTagModeLegacy WalletEventTagMode = "legacy" + // WalletEventTagModeEventType 在 Phase B 使用消息 event_type 作为 Tag,让 Broker 精确过滤。 + WalletEventTagModeEventType WalletEventTagMode = "event_type" +) + // WalletOutboxMQConfig 保存 wallet_outbox topic 的生产者配置。 type WalletOutboxMQConfig struct { Enabled bool `yaml:"enabled"` @@ -86,7 +102,9 @@ type WalletOutboxMQConfig struct { // OutboxWorkerConfig 控制 wallet_outbox pending 事件的 MQ fanout。 type OutboxWorkerConfig struct { - Enabled bool `yaml:"enabled"` + Enabled bool `yaml:"enabled"` + // AppCodes 是可投递租户 allowlist;空值才自动发现,便于积压回放按 App 分阶段放量。 + AppCodes []string `yaml:"app_codes"` PollInterval time.Duration `yaml:"poll_interval"` BatchSize int `yaml:"batch_size"` Concurrency int `yaml:"concurrency"` @@ -119,6 +137,14 @@ type GooglePlayConfig struct { ConsumeEnabled bool `yaml:"consume_enabled"` } +// GooglePaidSyncWorkerConfig polls unsynchronized payment_orders independently from the request goroutine. +type GooglePaidSyncWorkerConfig struct { + Enabled bool `yaml:"enabled"` + PollInterval time.Duration `yaml:"poll_interval"` + BatchSize int `yaml:"batch_size"` + RequestTimeout time.Duration `yaml:"request_timeout"` +} + // MifaPayConfig 保存 MiFaPay 商户签名、验签和网关配置。 type MifaPayConfig struct { Enabled bool `yaml:"enabled"` @@ -270,6 +296,12 @@ func Default() Config { HTTPTimeout: 10 * time.Second, ConsumeEnabled: true, }, + GooglePaidSyncWorker: GooglePaidSyncWorkerConfig{ + Enabled: true, + PollInterval: 15 * time.Second, + BatchSize: 100, + RequestTimeout: 2 * time.Minute, + }, MifaPay: MifaPayConfig{ Enabled: true, APIBaseURL: "https://platformtest.xqdmipay.com", @@ -347,9 +379,10 @@ func Default() Config { func defaultRocketMQConfig() RocketMQConfig { return RocketMQConfig{ - Enabled: false, - SendTimeout: 3 * time.Second, - Retry: 2, + Enabled: false, + SendTimeout: 3 * time.Second, + Retry: 2, + WalletEventTagMode: WalletEventTagModeLegacy, WalletOutbox: WalletOutboxMQConfig{ Enabled: false, Topic: "hyapp_wallet_outbox", @@ -430,6 +463,18 @@ func Load(path string) (Config, error) { return Config{}, errors.New("google_play service account is required") } } + if cfg.GooglePaidSyncWorker.PollInterval <= 0 { + cfg.GooglePaidSyncWorker.PollInterval = Default().GooglePaidSyncWorker.PollInterval + } + if cfg.GooglePaidSyncWorker.BatchSize <= 0 { + cfg.GooglePaidSyncWorker.BatchSize = Default().GooglePaidSyncWorker.BatchSize + } + if cfg.GooglePaidSyncWorker.BatchSize > 1000 { + cfg.GooglePaidSyncWorker.BatchSize = 1000 + } + if cfg.GooglePaidSyncWorker.RequestTimeout <= 0 { + cfg.GooglePaidSyncWorker.RequestTimeout = Default().GooglePaidSyncWorker.RequestTimeout + } cfg.MifaPay = normalizeMifaPayConfig(cfg.MifaPay) // MiFaPay 功能开关默认打开;商户密钥属于环境敏感配置,允许本地缺省启动,启动装配时再按凭证完整性决定是否接入真实网关。 cfg.V5Pay = normalizeV5PayConfig(cfg.V5Pay) @@ -730,6 +775,16 @@ func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) { if cfg.Retry <= 0 { cfg.Retry = defaults.Retry } + cfg.WalletEventTagMode = WalletEventTagMode(strings.ToLower(strings.TrimSpace(string(cfg.WalletEventTagMode)))) + if cfg.WalletEventTagMode == "" { + // 配置升级必须显式进入 Phase B;历史线上 YAML 缺少新字段时始终继续发布 legacy Tag。 + cfg.WalletEventTagMode = defaults.WalletEventTagMode + } + switch cfg.WalletEventTagMode { + case WalletEventTagModeLegacy, WalletEventTagModeEventType: + default: + return RocketMQConfig{}, errors.New("rocketmq.wallet_event_tag_mode must be legacy or event_type") + } if cfg.WalletOutbox.Topic = strings.TrimSpace(cfg.WalletOutbox.Topic); cfg.WalletOutbox.Topic == "" { cfg.WalletOutbox.Topic = defaults.WalletOutbox.Topic } @@ -757,6 +812,7 @@ func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) { } func normalizeOutboxWorkerConfig(cfg OutboxWorkerConfig, defaults OutboxWorkerConfig) OutboxWorkerConfig { + cfg.AppCodes = outboxpartition.Normalize(cfg.AppCodes, false) if cfg.PollInterval <= 0 { cfg.PollInterval = defaults.PollInterval } diff --git a/services/wallet-service/internal/config/config_test.go b/services/wallet-service/internal/config/config_test.go index 3d95fe81..02e01b7f 100644 --- a/services/wallet-service/internal/config/config_test.go +++ b/services/wallet-service/internal/config/config_test.go @@ -4,6 +4,8 @@ import ( "crypto/rsa" "crypto/x509" "encoding/base64" + "os" + "path/filepath" "strings" "testing" ) @@ -17,6 +19,9 @@ func TestLoadLocalEnablesOutboxMQ(t *testing.T) { if !cfg.RocketMQ.Enabled || !cfg.RocketMQ.WalletOutbox.Enabled || !cfg.RocketMQ.RealtimeOutbox.Enabled { t.Fatalf("local config must enable wallet outbox MQ producers: %+v", cfg.RocketMQ) } + if cfg.RocketMQ.WalletEventTagMode != WalletEventTagModeLegacy { + t.Fatalf("Phase A config must publish legacy wallet tags: %q", cfg.RocketMQ.WalletEventTagMode) + } if !cfg.OutboxWorker.Enabled || !cfg.RealtimeOutboxWorker.Enabled || !cfg.ProjectionWorker.Enabled { t.Fatalf("local config must enable wallet outbox workers: outbox=%+v realtime=%+v projection=%+v", cfg.OutboxWorker, cfg.RealtimeOutboxWorker, cfg.ProjectionWorker) } @@ -26,11 +31,65 @@ func TestLoadLocalEnablesOutboxMQ(t *testing.T) { if cfg.OutboxWorker.Concurrency != 1 || cfg.RealtimeOutboxWorker.Concurrency != 2 { t.Fatalf("local outbox worker defaults mismatch: outbox=%+v realtime=%+v", cfg.OutboxWorker, cfg.RealtimeOutboxWorker) } + if len(cfg.OutboxWorker.AppCodes) != 0 || len(cfg.RealtimeOutboxWorker.AppCodes) != 0 { + t.Fatalf("empty app_codes must retain automatic discovery: outbox=%v realtime=%v", cfg.OutboxWorker.AppCodes, cfg.RealtimeOutboxWorker.AppCodes) + } if cfg.BSCUSDT.Enabled || cfg.ExternalRecharge.USDTBEP20Enabled { t.Fatalf("bsc usdt receipt verification must stay disabled by default: bsc=%+v external=%+v", cfg.BSCUSDT, cfg.ExternalRecharge) } } +func TestOutboxWorkerAppCodesNormalizeForStagedReplay(t *testing.T) { + cfg := normalizeOutboxWorkerConfig(OutboxWorkerConfig{AppCodes: []string{" LALU ", "fami", "HUWAA", "fami"}}, Default().OutboxWorker) + if got := strings.Join(cfg.AppCodes, ","); got != "lalu,fami,huwaa" { + t.Fatalf("normalized outbox app_codes = %q", got) + } +} + +func TestDefaultWalletEventTagModeIsLegacy(t *testing.T) { + if got := Default().RocketMQ.WalletEventTagMode; got != WalletEventTagModeLegacy { + t.Fatalf("default wallet event tag mode = %q, want %q", got, WalletEventTagModeLegacy) + } +} + +func TestWalletEventTagModeMissingConfigDefaultsToLegacy(t *testing.T) { + path := writeWalletConfig(t, "rocketmq:\n enabled: false\n") + cfg, err := Load(path) + if err != nil { + t.Fatalf("Load missing tag mode config failed: %v", err) + } + if cfg.RocketMQ.WalletEventTagMode != WalletEventTagModeLegacy { + t.Fatalf("missing wallet_event_tag_mode = %q, want %q", cfg.RocketMQ.WalletEventTagMode, WalletEventTagModeLegacy) + } +} + +func TestWalletEventTagModeParsesExplicitPhaseB(t *testing.T) { + path := writeWalletConfig(t, "rocketmq:\n enabled: false\n wallet_event_tag_mode: ' EVENT_TYPE '\n") + cfg, err := Load(path) + if err != nil { + t.Fatalf("Load Phase B tag mode config failed: %v", err) + } + if cfg.RocketMQ.WalletEventTagMode != WalletEventTagModeEventType { + t.Fatalf("wallet_event_tag_mode = %q, want %q", cfg.RocketMQ.WalletEventTagMode, WalletEventTagModeEventType) + } +} + +func TestWalletEventTagModeRejectsUnknownValue(t *testing.T) { + path := writeWalletConfig(t, "rocketmq:\n enabled: false\n wallet_event_tag_mode: wildcard\n") + if _, err := Load(path); err == nil || !strings.Contains(err.Error(), "wallet_event_tag_mode") { + t.Fatalf("Load unknown wallet_event_tag_mode error = %v", err) + } +} + +func writeWalletConfig(t *testing.T, body string) string { + t.Helper() + path := filepath.Join(t.TempDir(), "config.yaml") + if err := os.WriteFile(path, []byte(body), 0o600); err != nil { + t.Fatalf("write config: %v", err) + } + return path +} + func TestLoadTencentExampleSplitsRealtimeOutboxAndRaisesThroughput(t *testing.T) { cfg, err := Load("../../configs/config.tencent.example.yaml") if err != nil { diff --git a/services/wallet-service/internal/domain/ledger/agency_host_gift_stats.go b/services/wallet-service/internal/domain/ledger/agency_host_gift_stats.go new file mode 100644 index 00000000..b8cc4e28 --- /dev/null +++ b/services/wallet-service/internal/domain/ledger/agency_host_gift_stats.go @@ -0,0 +1,12 @@ +package ledger + +type AgencyHostGiftStats struct { + GiftIncome int64 + GiftedHostCount int64 +} + +type AgencyHostGiftStatsQuery struct { + HostUserIDs []int64 + StartAtMS int64 + EndAtMS int64 +} diff --git a/services/wallet-service/internal/domain/ledger/agency_point_share_stats.go b/services/wallet-service/internal/domain/ledger/agency_point_share_stats.go new file mode 100644 index 00000000..b4e484ca --- /dev/null +++ b/services/wallet-service/internal/domain/ledger/agency_point_share_stats.go @@ -0,0 +1,13 @@ +package ledger + +// AgencyPointShareStats 聚合送礼时已固化归属的 Agency POINT 分成。 +type AgencyPointShareStats struct { + ShareIncome int64 + GiftedHostCount int64 +} + +type AgencyPointShareStatsQuery struct { + AgencyOwnerUserID int64 + StartAtMS int64 + EndAtMS int64 +} diff --git a/services/wallet-service/internal/domain/ledger/gift.go b/services/wallet-service/internal/domain/ledger/gift.go index e531508c..1b3285b5 100644 --- a/services/wallet-service/internal/domain/ledger/gift.go +++ b/services/wallet-service/internal/domain/ledger/gift.go @@ -83,6 +83,8 @@ type Receipt struct { GiftEffectTypes []string PriceVersion string BalanceAfter int64 + // BalanceVersion 是 sender 被扣费资产在本次送礼全部账务分录完成后的版本;幂等重放必须返回首次版本。 + BalanceVersion int64 // HostPeriodDiamondAdded 是本次送礼写入主播工资周期账户的钻石数;非主播恒为 0。 HostPeriodDiamondAdded int64 // HostPeriodCycleKey 是工资周期键,当前按 UTC 月生成,后续结算按此键定位周期账户。 diff --git a/services/wallet-service/internal/domain/ledger/host_revenue_stats.go b/services/wallet-service/internal/domain/ledger/host_revenue_stats.go new file mode 100644 index 00000000..17885d06 --- /dev/null +++ b/services/wallet-service/internal/domain/ledger/host_revenue_stats.go @@ -0,0 +1,15 @@ +package ledger + +// HostRevenueStats 是主播中心按日期展示的 POINT 收益、已兑换数量和去重送礼人数。 +type HostRevenueStats struct { + DiamondEarnings int64 + DiamondExchanged int64 + GiftSenders int64 +} + +// HostRevenueStatsQuery 使用左闭右开 UTC 毫秒区间;调用方必须限制区间长度。 +type HostRevenueStatsQuery struct { + HostUserID int64 + StartAtMS int64 + EndAtMS int64 +} diff --git a/services/wallet-service/internal/domain/ledger/point_coin_seller.go b/services/wallet-service/internal/domain/ledger/point_coin_seller.go new file mode 100644 index 00000000..9f213497 --- /dev/null +++ b/services/wallet-service/internal/domain/ledger/point_coin_seller.go @@ -0,0 +1,48 @@ +package ledger + +// PointWithdrawalCoinSellerConfig 是 POINT 提现可选币商及其服务范围、兑换比例的运行快照。 +type PointWithdrawalCoinSellerConfig struct { + AppCode string + SellerUserID int64 + SortOrder int + PointAmount int64 + SellerCoinAmount int64 + ServiceCountryCodes []string + Status string + CreatedAtMS int64 + UpdatedAtMS int64 +} + +// PointToCoinSellerCommand 只接受用户、目标和金额;比例必须由 wallet 数据库的 active 白名单解析。 +type PointToCoinSellerCommand struct { + AppCode string + CommandID string + SourceUserID int64 + SellerUserID int64 + PointAmount int64 + SourceCountryCode string + Reason string +} + +// PointToCoinSellerReceipt 是 POINT 扣款和币商库存入账的双边稳定回执。 +type PointToCoinSellerReceipt struct { + TransactionID string + SourceUserID int64 + SellerUserID int64 + SourcePointBalanceAfter int64 + SellerBalanceAfter int64 + PointAmount int64 + SellerCoinAmount int64 + RatioPointAmount int64 + RatioSellerCoinAmount int64 + CreatedAtMS int64 +} + +// PointWithdrawalRuntimeConfig 是已发布政策中钱包页需要展示并用于冻结的参数。 +type PointWithdrawalRuntimeConfig struct { + Found bool + PointsPerUSD int64 + FeeBPS int64 + MinimumPoints int64 + PolicyInstanceCode string +} diff --git a/services/wallet-service/internal/domain/ledger/recharge.go b/services/wallet-service/internal/domain/ledger/recharge.go index ac3262b2..f4a6c3a0 100644 --- a/services/wallet-service/internal/domain/ledger/recharge.go +++ b/services/wallet-service/internal/domain/ledger/recharge.go @@ -110,11 +110,13 @@ type GooglePaymentPaidDetails struct { // GooglePaymentOrderRef 是按钱包交易反查 Google 支付订单、发起 Orders API 查询所需的最小定位信息。 type GooglePaymentOrderRef struct { - PaymentOrderID string - TransactionID string - PackageName string - ProviderOrderID string - PaidSyncedAtMS int64 + AppCode string + PaymentOrderID string + TransactionID string + PackageName string + ProviderOrderID string + PaidSyncedAtMS int64 + SyncAttemptCount int } // GooglePaymentReceipt 是 Google 支付确认接口返回给 App 的稳定入账回执。 diff --git a/services/wallet-service/internal/domain/ledger/vip.go b/services/wallet-service/internal/domain/ledger/vip.go index 18df3fd8..d173982b 100644 --- a/services/wallet-service/internal/domain/ledger/vip.go +++ b/services/wallet-service/internal/domain/ledger/vip.go @@ -4,6 +4,132 @@ import ( "strings" ) +const ( + // VipProgramTypeLegacyTimed 保持 Lalu 现有的剩余时间累加会员语义。 + VipProgramTypeLegacyTimed = "legacy_timed" + // VipProgramTypeTieredPrivilegeV1 是 Fami P1 的九级权益体系。 + VipProgramTypeTieredPrivilegeV1 = "tiered_privilege_v1" + // VipExpiryPolicyExtendRemaining 从当前有效截止时间继续累加购买时长。 + VipExpiryPolicyExtendRemaining = "extend_remaining" + // VipExpiryPolicyReplaceFromNow 表示高级购买立即丢弃低级剩余时间,并从当前时刻重新计时。 + VipExpiryPolicyReplaceFromNow = "replace_from_now" + // VipDowngradePurchasePolicyReject 禁止有效会员购买更低等级。 + VipDowngradePurchasePolicyReject = "reject" + // VipBenefitInheritanceTargetOnly 仅使用目标等级显式配置的权益集合。 + VipBenefitInheritanceTargetOnly = "target_only" + // VipGrantModeDirectMembership 将后台赠送直接写为真实会员。 + VipGrantModeDirectMembership = "direct_membership" + // VipGrantModeTrialCard 将后台赠送写入可自由佩戴的背包体验卡。 + VipGrantModeTrialCard = "trial_card" + // VipTrialCardResourceType 使体验卡复用通用背包和单选装备链路。 + VipTrialCardResourceType = "vip_trial_card" + // VipEffectiveSourceNone 表示没有有效付费会员或已佩戴体验卡。 + VipEffectiveSourceNone = "none" + // VipEffectiveSourcePaid 表示最终权益来自真实购买会员。 + VipEffectiveSourcePaid = "paid" + // VipEffectiveSourceTrial 表示最终权益来自当前佩戴体验卡。 + VipEffectiveSourceTrial = "trial" + // VipTrialCardStatusActive 表示卡片未被撤销;是否过期仍以 ExpiresAtMS 判定。 + VipTrialCardStatusActive = "active" + // VipTrialCardStatusRevoked 表示后台已撤销卡片及对应背包权益。 + VipTrialCardStatusRevoked = "revoked" + // VipBenefitTypeDecoration 表示需要下发或佩戴资源的视觉权益。 + VipBenefitTypeDecoration = "decoration" + // VipBenefitTypeFunction 表示由 owner service 执行的功能权限。 + VipBenefitTypeFunction = "function" + // VipExecutionScopeWallet 表示权益由 wallet-service 执行,例如金币返现。 + VipExecutionScopeWallet = "wallet" + // VipExecutionScopeRoom 表示权益由 Room Cell 所在 room-service 执行。 + VipExecutionScopeRoom = "room" + // VipExecutionScopeUser 表示权益由用户资料或访客 owner 执行。 + VipExecutionScopeUser = "user" + // VipExecutionScopeNotice 表示权益由通知投递链路执行。 + VipExecutionScopeNotice = "notice" + + // 以下编码是 P1 跨服务权限契约;数据库、钱包投影和各 owner service 必须使用同一稳定值。 + VipBenefitCodeBadgeIdentity = "vip_badge_identity" + VipBenefitCodeMedal = "vip_medal" + VipBenefitCodeAvatarFrame = "avatar_frame" + VipBenefitCodeTitle = "vip_title" + VipBenefitCodeChatBubble = "chat_bubble" + VipBenefitCodeGift = "vip_gift" + VipBenefitCodeEntryEffect = "entry_effect" + VipBenefitCodeVisitorHistory = "visitor_history" + VipBenefitCodeVoiceWave = "voice_wave" + VipBenefitCodeProfileCard = "profile_card" + VipBenefitCodeCustomRoomBackground = "custom_room_background" + VipBenefitCodeRoomImageMessage = "room_image_message" + VipBenefitCodeAnimatedAvatar = "animated_avatar" + VipBenefitCodeAnimatedRoomCover = "animated_room_cover" + VipBenefitCodeMicSkin = "mic_skin" + VipBenefitCodeRoomBorder = "room_border" + VipBenefitCodeVehicle = "vehicle" + VipBenefitCodeColoredRoomName = "colored_room_name" + VipBenefitCodeColoredNickname = "colored_nickname" + VipBenefitCodeColoredID = "colored_id" + VipBenefitCodeGiftTraySkin = "gift_tray_skin" + VipBenefitCodeHideProfileData = "hide_profile_data" + VipBenefitCodeCustomAvatarFrame = "custom_avatar_frame" + VipBenefitCodeLeaderboardInvisible = "leaderboard_invisible" + VipBenefitCodeDailyCoinRebate = "daily_coin_rebate" + VipBenefitCodeCustomProfileCard = "custom_profile_card" + VipBenefitCodeAnonymousProfileVisit = "anonymous_profile_visit" + VipBenefitCodeCustomGift = "custom_gift" + VipBenefitCodeRoomEntryNotice = "room_entry_notice" + VipBenefitCodeCustomPrettyID = "custom_pretty_id" + VipBenefitCodeCustomVehicle = "custom_vehicle" + VipBenefitCodeAntiKick = "anti_kick" + VipBenefitCodeAntiMute = "anti_mute" + VipBenefitCodeOnlineGlobalNotice = "online_global_notice" + + // VipDailyCoinRebateStatusClaimable 表示返现仍处于当天手动领取窗口。 + VipDailyCoinRebateStatusClaimable = "claimable" + // VipDailyCoinRebateStatusClaimed 表示金币已经原子入账,WalletTransactionID 必须非空。 + VipDailyCoinRebateStatusClaimed = "claimed" + // VipDailyCoinRebateStatusExpired 是查询层对超过 UTC 日终但未领取记录的只读投影。 + VipDailyCoinRebateStatusExpired = "expired" + // VipDailyCoinRebateRunStatusRunning 表示批任务仍需沿 last_user_id 继续分页。 + VipDailyCoinRebateRunStatusRunning = "running" + // VipDailyCoinRebateRunStatusCompleted 表示当日所有候选会员已经扫描完成。 + VipDailyCoinRebateRunStatusCompleted = "completed" +) + +// VipProgramConfig 是某 App 当前 VIP 状态机的完整规则,不允许调用方再按 app_code 猜规则。 +type VipProgramConfig struct { + AppCode string + ProgramType string + LevelCount int32 + SameLevelExpiryPolicy string + UpgradeExpiryPolicy string + DowngradePurchasePolicy string + BenefitInheritancePolicy string + GrantMode string + TrialCardEnabled bool + Status string + ConfigVersion int64 + UpdatedByAdminID int64 + CreatedAtMS int64 + UpdatedAtMS int64 +} + +// VipBenefit 是可跨服务投影和执行的权益事实;ResourceID=0 表示纯功能或尚未绑定资源。 +type VipBenefit struct { + BenefitCode string + Name string + BenefitType string + UnlockLevel int32 + Status string + TrialEnabled bool + ResourceID int64 + ResourceType string + ExecutionScope string + AutoEquip bool + SortOrder int32 + MetadataJSON string + CreatedAtMS int64 + UpdatedAtMS int64 +} + // VipRewardItem 是 VIP 资源组权益的轻量展示投影。 type VipRewardItem struct { ResourceID int64 @@ -34,17 +160,85 @@ type VipLevel struct { SortOrder int32 CreatedAtMS int64 UpdatedAtMS int64 + Benefits []VipBenefit + ConfigVersion int64 } // UserVip 是用户当前会员状态。是否有效以 ExpiresAtMS 和当前时间判断。 type UserVip struct { - UserID int64 - Level int32 - Name string - Active bool - StartedAtMS int64 - ExpiresAtMS int64 - UpdatedAtMS int64 + UserID int64 + Level int32 + Name string + Active bool + StartedAtMS int64 + ExpiresAtMS int64 + UpdatedAtMS int64 + ProgramType string + ConfigVersion int64 +} + +// VipTrialCard 把通用背包 entitlement 投影为 VIP 体验卡;装备切换只改变 Equipped, +// DurationMS、EffectiveAtMS 和 ExpiresAtMS 始终保留发卡时的绝对计时事实。 +type VipTrialCard struct { + TrialCardID string + EntitlementID string + ResourceID int64 + UserID int64 + Level int32 + Name string + Status string + Equipped bool + DurationMS int64 + EffectiveAtMS int64 + ExpiresAtMS int64 + RemainingDurationMS int64 + GrantSource string + SourceGrantID string + CreatedAtMS int64 + UpdatedAtMS int64 +} + +// VipUserSettings 是用户可关闭的 VIP 展示偏好。默认值必须由 wallet owner 统一构造, +// 不能让 room/gateway 因为数据库尚未物化记录而各自猜不同默认值。 +type VipUserSettings struct { + AppCode string + UserID int64 + RoomEntryNoticeEnabled bool + OnlineGlobalNoticeEnabled bool + UpdatedAtMS int64 +} + +// DefaultVipUserSettings 返回尚未物化时的产品默认:两项通知均开启,UpdatedAtMS=0。 +func DefaultVipUserSettings(appCode string, userID int64) VipUserSettings { + return VipUserSettings{ + AppCode: appCode, UserID: userID, + RoomEntryNoticeEnabled: true, OnlineGlobalNoticeEnabled: true, + } +} + +// AllowsBenefitSetting 只处理用户可关闭的两项权益;其它权益不受偏好表影响。 +func (settings VipUserSettings) AllowsBenefitSetting(benefitCode string) bool { + switch strings.ToLower(strings.TrimSpace(benefitCode)) { + case VipBenefitCodeRoomEntryNotice: + return settings.RoomEntryNoticeEnabled + case VipBenefitCodeOnlineGlobalNotice: + return settings.OnlineGlobalNoticeEnabled + default: + return true + } +} + +// VipState 是调用方唯一应消费的合并状态。EffectiveBenefits 已按真实/体验来源过滤, +// 尤其不会向体验卡返回 TrialEnabled=false 的金币返现资格;可关闭通知还需结合 UserSettings。 +type VipState struct { + PaidVip UserVip + EquippedTrialCard *VipTrialCard + EffectiveVip UserVip + EffectiveSource string + EffectiveBenefits []VipBenefit + EvaluatedAtMS int64 + ProgramConfig VipProgramConfig + UserSettings VipUserSettings } // PurchaseVipCommand 是 App 购买或升级 VIP 的账务命令。 @@ -63,6 +257,7 @@ type PurchaseVipReceipt struct { CoinSpent int64 CoinBalanceAfter int64 RewardItems []VipRewardItem + State VipState } // GrantVipCommand 是活动或后台发放 VIP 的统一入口命令。 @@ -81,6 +276,52 @@ type GrantVipReceipt struct { TransactionID string Vip UserVip RewardItems []VipRewardItem + State VipState +} + +// GrantVipTrialCardCommand 是直接按等级和绝对时长发放体验卡的幂等命令。 +// ResourceID=0 表示按 level 解析同 App 预置资源;非 0 时也必须匹配该等级的 vip_trial_card。 +type GrantVipTrialCardCommand struct { + AppCode string + CommandID string + TargetUserID int64 + Level int32 + DurationMS int64 + ResourceID int64 + GrantSource string + OperatorUserID int64 + Reason string +} + +// GrantVipTrialCardReceipt 返回新卡和重新计算后的有效 VIP 状态;发卡本身不自动佩戴。 +type GrantVipTrialCardReceipt struct { + TrialCard VipTrialCard + State VipState + ServerTimeMS int64 +} + +// EquipVipTrialCardCommand 以 entitlement 精确选择背包实例,避免同资源多卡时佩戴错对象。 +type EquipVipTrialCardCommand struct { + AppCode string + RequestID string + UserID int64 + EntitlementID string +} + +// UnequipVipTrialCardCommand 只清理 vip_trial_card 类型的装备事实,不回收或暂停任一卡片。 +type UnequipVipTrialCardCommand struct { + AppCode string + RequestID string + UserID int64 +} + +// CheckVipBenefitResult 是 owner service 执行单项特权时使用的服务端判断结果。 +type CheckVipBenefitResult struct { + Allowed bool + Benefit VipBenefit + State VipState + DenialReason string + EvaluatedAtMS int64 } // AdminVipLevelCommand 是后台保存 VIP 等级配置的完整事实输入。 @@ -93,6 +334,101 @@ type AdminVipLevelCommand struct { RewardResourceGroupID int64 RequiredRechargeCoinAmount int64 SortOrder int32 + Benefits []VipBenefit +} + +// AdminVipProgramConfigCommand 是后台保存 App 级 VIP 策略的审计输入。 +type AdminVipProgramConfigCommand struct { + Config VipProgramConfig + OperatorUserID int64 +} + +// UpdateVipUserSettingsCommand 使用指针表达 partial update;nil 字段必须保留数据库原值。 +type UpdateVipUserSettingsCommand struct { + AppCode string + UserID int64 + RoomEntryNoticeEnabled *bool + OnlineGlobalNoticeEnabled *bool +} + +// VipDailyCoinRebateRun 是单 App、单 UTC 日唯一的配置快照与分页游标。 +// LevelAmounts 只在创建 run 时从 active daily_coin_rebate 权益解析,后续分页禁止回读新配置。 +type VipDailyCoinRebateRun struct { + RunID string + TaskDay string + DayStartMS int64 + DayEndMS int64 + ConfigVersion int64 + LevelAmounts map[int32]int64 + LevelAmountsJSON string + Status string + LastUserID int64 + ScannedCount int64 + CreatedCount int64 + CreatedAtMS int64 + UpdatedAtMS int64 + CompletedAtMS int64 +} + +// VipDailyCoinRebate 是日切时生成、当天手动领取的付费 VIP 返现事实。 +type VipDailyCoinRebate struct { + RebateID string + UserID int64 + TaskDay string + VipLevel int32 + VipName string + CoinAmount int64 + Status string + AvailableAtMS int64 + ExpiresAtMS int64 + ConfigVersion int64 + ClaimedAtMS int64 + WalletTransactionID string + CreatedAtMS int64 + UpdatedAtMS int64 +} + +// ProcessVipDailyCoinRebateBatchCommand 由 cron 适配器构造;NowMS 仅由 wallet-service 注入, +// repository 不信任调度方的本地时钟。 +type ProcessVipDailyCoinRebateBatchCommand struct { + AppCode string + TaskDay string + BatchSize int + NowMS int64 +} + +// ProcessVipDailyCoinRebateBatchResult 返回本页计数与持久 run,调度方据 HasMore 继续触发。 +type ProcessVipDailyCoinRebateBatchResult struct { + Run VipDailyCoinRebateRun + ScannedCount int + CreatedCount int + HasMore bool +} + +// ListVipDailyCoinRebateStatusesQuery 是用户侧返现历史分页查询;状态在读取时按 NowMS 投影。 +type ListVipDailyCoinRebateStatusesQuery struct { + AppCode string + UserID int64 + Page int + PageSize int + RebateIDs []string + NowMS int64 +} + +// ClaimVipDailyCoinRebateCommand 只包含调用方可控主键;金额、等级和窗口全部从锁内返现行读取。 +type ClaimVipDailyCoinRebateCommand struct { + AppCode string + CommandID string + UserID int64 + RebateID string + NowMS int64 +} + +// ClaimVipDailyCoinRebateReceipt 是幂等稳定回执;同 command 重放必须返回同一交易和账后余额。 +type ClaimVipDailyCoinRebateReceipt struct { + Rebate VipDailyCoinRebate + TransactionID string + CoinBalance AssetBalance } func NormalizeVipGrantSource(source string) string { diff --git a/services/wallet-service/internal/domain/ledger/wallet_query.go b/services/wallet-service/internal/domain/ledger/wallet_query.go index ecc3e4c8..e037011a 100644 --- a/services/wallet-service/internal/domain/ledger/wallet_query.go +++ b/services/wallet-service/internal/domain/ledger/wallet_query.go @@ -53,6 +53,16 @@ type RechargeBillSummary struct { CoinSeller RechargeBillSummaryBucket } +// UserRechargeStats 是用户维度的累计充值读模型;字段直接对应 wallet_user_recharge_stats 聚合行。 +type UserRechargeStats struct { + UserID int64 + RechargeCount int64 + TotalCoinAmount int64 + TotalUSDMinorAmount int64 + FirstRechargedAtMS int64 + UpdatedAtMS int64 +} + // ListRechargeBillsQuery 是后台查询所有充值渠道账单的筛选条件。 type ListRechargeBillsQuery struct { AppCode string diff --git a/services/wallet-service/internal/domain/resource/constants.go b/services/wallet-service/internal/domain/resource/constants.go index 343d1137..36cc7f89 100644 --- a/services/wallet-service/internal/domain/resource/constants.go +++ b/services/wallet-service/internal/domain/resource/constants.go @@ -12,6 +12,9 @@ const ( TypeMicSeatIcon = "mic_seat_icon" TypeMicSeatAnimation = "mic_seat_animation" TypeEmojiPack = "emoji_pack" + // TypeVIPTrialCard 是可在背包中独立计时、单选佩戴的 VIP 体验卡。 + // VIP 等级和有效期事实仍由 user_vip_trial_cards 保存,资源目录只负责素材和通用背包展示。 + TypeVIPTrialCard = "vip_trial_card" StatusActive = "active" StatusDisabled = "disabled" diff --git a/services/wallet-service/internal/domain/resource/entitlement.go b/services/wallet-service/internal/domain/resource/entitlement.go index 8dd46f4c..fd8e908b 100644 --- a/services/wallet-service/internal/domain/resource/entitlement.go +++ b/services/wallet-service/internal/domain/resource/entitlement.go @@ -25,6 +25,9 @@ type ListUserResourcesQuery struct { } type EquipUserResourceCommand struct { + // RequestID 在通用背包命中 VIP 体验卡时作为专用状态机的 outbox 幂等事件键; + // 普通装扮仍不把 request_id 当业务幂等键。 + RequestID string AppCode string UserID int64 ResourceID int64 @@ -32,6 +35,8 @@ type EquipUserResourceCommand struct { } type UnequipUserResourceCommand struct { + // RequestID 仅在 vip_trial_card 委托专用卸下流程时用于发布完整 VIP 状态变更事件。 + RequestID string AppCode string UserID int64 ResourceType string diff --git a/services/wallet-service/internal/domain/resource/resource.go b/services/wallet-service/internal/domain/resource/resource.go index 36593fcd..3ee2be1b 100644 --- a/services/wallet-service/internal/domain/resource/resource.go +++ b/services/wallet-service/internal/domain/resource/resource.go @@ -101,7 +101,7 @@ func NormalizeGrantSource(value string) string { func ValidResourceType(value string) bool { switch strings.ToLower(strings.TrimSpace(value)) { - case TypeAvatarFrame, TypeProfileCard, TypeCoin, TypeVehicle, TypeChatBubble, TypeBadge, TypeFloatingScreen, TypeGift, TypeMicSeatIcon, TypeMicSeatAnimation, TypeEmojiPack: + case TypeAvatarFrame, TypeProfileCard, TypeCoin, TypeVehicle, TypeChatBubble, TypeBadge, TypeFloatingScreen, TypeGift, TypeMicSeatIcon, TypeMicSeatAnimation, TypeEmojiPack, TypeVIPTrialCard: return true default: return false diff --git a/services/wallet-service/internal/domain/resource/resource_test.go b/services/wallet-service/internal/domain/resource/resource_test.go index 59c5769e..b0f58380 100644 --- a/services/wallet-service/internal/domain/resource/resource_test.go +++ b/services/wallet-service/internal/domain/resource/resource_test.go @@ -15,3 +15,9 @@ func TestRoomAndEmojiAssetsAreValidResourceTypes(t *testing.T) { } } } + +func TestVIPTrialCardIsValidResourceType(t *testing.T) { + if !ValidResourceType(TypeVIPTrialCard) { + t.Fatalf("vip_trial_card must be accepted as a configured resource type") + } +} diff --git a/services/wallet-service/internal/service/wallet/agency_host_gift_stats.go b/services/wallet-service/internal/service/wallet/agency_host_gift_stats.go new file mode 100644 index 00000000..651afedd --- /dev/null +++ b/services/wallet-service/internal/service/wallet/agency_host_gift_stats.go @@ -0,0 +1,31 @@ +package wallet + +import ( + "context" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +const maxAgencyHostGiftStatsRange = 366 * 24 * time.Hour + +func (s *Service) GetAgencyHostGiftStats(ctx context.Context, appCode string, query ledger.AgencyHostGiftStatsQuery) (ledger.AgencyHostGiftStats, error) { + if s.repository == nil { + return ledger.AgencyHostGiftStats{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + if len(query.HostUserIDs) > 1000 || query.StartAtMS <= 0 || query.EndAtMS <= query.StartAtMS { + return ledger.AgencyHostGiftStats{}, xerr.New(xerr.InvalidArgument, "agency host gift stats range is invalid") + } + for _, userID := range query.HostUserIDs { + if userID <= 0 { + return ledger.AgencyHostGiftStats{}, xerr.New(xerr.InvalidArgument, "host_user_ids is invalid") + } + } + if query.EndAtMS-query.StartAtMS > int64(maxAgencyHostGiftStatsRange/time.Millisecond) { + return ledger.AgencyHostGiftStats{}, xerr.New(xerr.InvalidArgument, "agency host gift stats range is too large") + } + ctx = appcode.WithContext(ctx, appcode.Normalize(appCode)) + return s.repository.GetAgencyHostGiftStats(ctx, query) +} diff --git a/services/wallet-service/internal/service/wallet/agency_point_share_stats.go b/services/wallet-service/internal/service/wallet/agency_point_share_stats.go new file mode 100644 index 00000000..c1c673a9 --- /dev/null +++ b/services/wallet-service/internal/service/wallet/agency_point_share_stats.go @@ -0,0 +1,26 @@ +package wallet + +import ( + "context" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +const maxAgencyPointShareStatsRange = 366 * 24 * time.Hour + +func (s *Service) GetAgencyPointShareStats(ctx context.Context, appCode string, query ledger.AgencyPointShareStatsQuery) (ledger.AgencyPointShareStats, error) { + if s.repository == nil { + return ledger.AgencyPointShareStats{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + if query.AgencyOwnerUserID <= 0 || query.StartAtMS <= 0 || query.EndAtMS <= query.StartAtMS { + return ledger.AgencyPointShareStats{}, xerr.New(xerr.InvalidArgument, "agency point share stats range is invalid") + } + if query.EndAtMS-query.StartAtMS > int64(maxAgencyPointShareStatsRange/time.Millisecond) { + return ledger.AgencyPointShareStats{}, xerr.New(xerr.InvalidArgument, "agency point share stats range is too large") + } + ctx = appcode.WithContext(ctx, appcode.Normalize(appCode)) + return s.repository.GetAgencyPointShareStats(ctx, query) +} diff --git a/services/wallet-service/internal/service/wallet/external_recharge_order.go b/services/wallet-service/internal/service/wallet/external_recharge_order.go index 01841c0b..bfad3c80 100644 --- a/services/wallet-service/internal/service/wallet/external_recharge_order.go +++ b/services/wallet-service/internal/service/wallet/external_recharge_order.go @@ -6,6 +6,7 @@ import ( "hyapp/pkg/appcode" "hyapp/pkg/xerr" "hyapp/services/wallet-service/internal/domain/ledger" + "hyapp/services/wallet-service/internal/service/wallet/ports" "strings" ) @@ -178,7 +179,7 @@ func providerPayloadJSON(err error) string { return "" } -// SubmitH5RechargeTx 把用户提交的 tx_hash 交给 TRON 适配器校验,通过后立即入账。 +// SubmitH5RechargeTx 校验用户提交的 TRON 链上哈希或 Binance 站内转账号,通过后立即入账。 func (s *Service) SubmitH5RechargeTx(ctx context.Context, command ledger.SubmitExternalRechargeTxCommand) (ledger.ExternalRechargeOrder, error) { command.AppCode = appcode.Normalize(command.AppCode) command.OrderID = strings.TrimSpace(command.OrderID) @@ -200,18 +201,43 @@ func (s *Service) SubmitH5RechargeTx(ctx context.Context, command ledger.SubmitE if order.ProviderCode != ledger.PaymentProviderUSDTTRC20 || order.Status != ledger.ExternalRechargeStatusPending { return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Conflict, "external recharge order is not pending usdt order") } - if s.tronUSDT == nil { - return s.repository.AttachExternalRechargeTx(ctx, command, "") - } - rawJSON, err := s.tronUSDT.VerifyUSDTTransfer(ctx, command.TxHash, order.ReceiveAddress, order.USDMinorAmount) + providerOrderID, rawJSON, err := s.verifyH5USDTTransfer(ctx, command, order) if err != nil { + // 校验失败仍保留用户提交的编号和上游快照,便于后续重试与财务复核;这一步不会改变账务。 _, _ = s.repository.AttachExternalRechargeTx(ctx, command, rawJSON) return ledger.ExternalRechargeOrder{}, err } if _, err := s.repository.AttachExternalRechargeTx(ctx, command, rawJSON); err != nil { return ledger.ExternalRechargeOrder{}, err } - return s.repository.CreditExternalRechargeOrder(ctx, command.AppCode, command.OrderID, "", command.TxHash, rawJSON) + return s.repository.CreditExternalRechargeOrder(ctx, command.AppCode, command.OrderID, providerOrderID, command.TxHash, rawJSON) +} + +// verifyH5USDTTransfer 只根据凭证形态选择事实源:64 位十六进制哈希查 TRON,其他编号查当前 App 的 Binance 入金记录。 +// Binance 路径不信任付款方截图;FindUSDTDeposit 会同时核对币种、网络、收款地址、金额和成功状态。 +func (s *Service) verifyH5USDTTransfer(ctx context.Context, command ledger.SubmitExternalRechargeTxCommand, order ledger.ExternalRechargeOrder) (string, string, error) { + if isLikelyChainTransactionHash(command.TxHash) { + if s.tronUSDT == nil { + return "", "", xerr.New(xerr.Unavailable, "tron grid client is not configured") + } + rawJSON, err := s.tronUSDT.VerifyUSDTTransfer(ctx, command.TxHash, order.ReceiveAddress, order.USDMinorAmount) + return "", rawJSON, err + } + if s.binance == nil { + return "", "", xerr.New(xerr.Unavailable, "binance client is not configured") + } + record, err := s.binance.FindUSDTDeposit(ctx, ports.BinanceDepositLookupRequest{ + AppCode: command.AppCode, + ExternalID: command.TxHash, + Coin: ledger.PaidCurrencyUSDT, + Network: "TRX", + ToAddress: order.ReceiveAddress, + AmountMinor: order.USDMinorAmount, + }) + if err != nil { + return "", "", err + } + return record.TxID, strings.TrimSpace(record.RawJSON), nil } func (s *Service) GetH5RechargeOrder(ctx context.Context, appCode string, orderID string, targetUserID int64) (ledger.ExternalRechargeOrder, error) { diff --git a/services/wallet-service/internal/service/wallet/external_recharge_receipt_test.go b/services/wallet-service/internal/service/wallet/external_recharge_receipt_test.go index 21bfa2f6..0c323d53 100644 --- a/services/wallet-service/internal/service/wallet/external_recharge_receipt_test.go +++ b/services/wallet-service/internal/service/wallet/external_recharge_receipt_test.go @@ -592,6 +592,58 @@ func TestVerifyCoinSellerRechargeReceiptUSDTTRC20OffchainMissingBinanceAccount(t } } +func TestSubmitH5RechargeTxBinanceOffchainUsesAppScopedAccount(t *testing.T) { + for index, appCode := range []string{"lalu", "yumi", "aslan"} { + t.Run(appCode, func(t *testing.T) { + order := ledger.ExternalRechargeOrder{ + OrderID: "order-" + appCode, + AppCode: appCode, + TargetUserID: int64(81000 + index), + USDMinorAmount: 30000, + ProviderCode: ledger.PaymentProviderUSDTTRC20, + ReceiveAddress: "TReceiveAddress-" + appCode, + Status: ledger.ExternalRechargeStatusPending, + } + repository := &fakeH5USDTRepository{order: order} + binance := &fakeBinanceClient{record: walletservice.BinanceDepositRecord{ + TxID: "Off-chain transfer 390597460031", + AmountMinor: 30000, + Address: order.ReceiveAddress, + RawJSON: `{"txId":"Off-chain transfer 390597460031","status":1}`, + }} + tron := &fakeTronUSDTClient{err: xerr.New(xerr.Internal, "tron should not be called")} + svc := walletservice.New(repository) + svc.SetBinanceClient(binance) + svc.SetTronUSDTClient(tron) + + credited, err := svc.SubmitH5RechargeTx(context.Background(), ledger.SubmitExternalRechargeTxCommand{ + AppCode: appCode, + OrderID: order.OrderID, + TargetUserID: order.TargetUserID, + TxHash: "390597460031", + }) + if err != nil { + t.Fatalf("SubmitH5RechargeTx Binance off-chain failed: %v", err) + } + if credited.Status != ledger.ExternalRechargeStatusCredited { + t.Fatalf("Binance off-chain order was not credited: %+v", credited) + } + if binance.calls != 1 || binance.req.AppCode != appCode || binance.req.ExternalID != "390597460031" || binance.req.Coin != ledger.PaidCurrencyUSDT || binance.req.Network != "TRX" || binance.req.ToAddress != order.ReceiveAddress || binance.req.AmountMinor != 30000 { + t.Fatalf("Binance app-scoped lookup mismatch: calls=%d req=%+v", binance.calls, binance.req) + } + if tron.lastTxHash != "" { + t.Fatalf("Binance off-chain transfer must not query TronGrid: %+v", tron) + } + if repository.attachCommand.TxHash != "390597460031" || repository.attachPayload != binance.record.RawJSON { + t.Fatalf("submitted Binance evidence was not attached: command=%+v payload=%s", repository.attachCommand, repository.attachPayload) + } + if repository.creditProviderOrderID != binance.record.TxID || repository.creditTxHash != "390597460031" || repository.creditPayload != binance.record.RawJSON { + t.Fatalf("Binance credit evidence mismatch: provider_order_id=%s tx_hash=%s payload=%s", repository.creditProviderOrderID, repository.creditTxHash, repository.creditPayload) + } + }) + } +} + type fakeReceiptRepository struct { walletservice.Repository channels []ledger.ThirdPartyPaymentChannel @@ -600,6 +652,47 @@ type fakeReceiptRepository struct { querySeen bool } +type fakeH5USDTRepository struct { + walletservice.Repository + order ledger.ExternalRechargeOrder + attachCommand ledger.SubmitExternalRechargeTxCommand + attachPayload string + creditProviderOrderID string + creditTxHash string + creditPayload string +} + +func (r *fakeH5USDTRepository) GetExternalRechargeOrder(_ context.Context, appCode string, orderID string) (ledger.ExternalRechargeOrder, error) { + if appCode != r.order.AppCode || orderID != r.order.OrderID { + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.NotFound, "external recharge order not found") + } + return r.order, nil +} + +func (r *fakeH5USDTRepository) AttachExternalRechargeTx(_ context.Context, command ledger.SubmitExternalRechargeTxCommand, payloadJSON string) (ledger.ExternalRechargeOrder, error) { + // 测试仓储保留入账前的凭证快照,用于断言上游查单结果没有在 service 层丢失。 + r.attachCommand = command + r.attachPayload = payloadJSON + r.order.TxHash = command.TxHash + r.order.ProviderPayloadJSON = payloadJSON + return r.order, nil +} + +func (r *fakeH5USDTRepository) CreditExternalRechargeOrder(_ context.Context, appCode string, orderID string, providerOrderID string, txHash string, payloadJSON string) (ledger.ExternalRechargeOrder, error) { + if appCode != r.order.AppCode || orderID != r.order.OrderID { + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.NotFound, "external recharge order not found") + } + // 只有校验通过才会进入此方法;真实 MySQL 仓储仍负责唯一凭证和原子入账约束。 + r.creditProviderOrderID = providerOrderID + r.creditTxHash = txHash + r.creditPayload = payloadJSON + r.order.ProviderOrderID = providerOrderID + r.order.TxHash = txHash + r.order.ProviderPayloadJSON = payloadJSON + r.order.Status = ledger.ExternalRechargeStatusCredited + return r.order, nil +} + func (r *fakeReceiptRepository) ListThirdPartyPaymentChannels(_ context.Context, query ledger.ListThirdPartyPaymentChannelsQuery) ([]ledger.ThirdPartyPaymentChannel, error) { r.query = query r.querySeen = true diff --git a/services/wallet-service/internal/service/wallet/host_revenue_stats.go b/services/wallet-service/internal/service/wallet/host_revenue_stats.go new file mode 100644 index 00000000..d29a647a --- /dev/null +++ b/services/wallet-service/internal/service/wallet/host_revenue_stats.go @@ -0,0 +1,27 @@ +package wallet + +import ( + "context" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +const maxHostRevenueStatsRange = 366 * 24 * time.Hour + +// GetHostRevenueStats 聚合钱包 owner 内的 POINT 收益/兑换和去重送礼人,不让 gateway 扫账本。 +func (s *Service) GetHostRevenueStats(ctx context.Context, appCode string, query ledger.HostRevenueStatsQuery) (ledger.HostRevenueStats, error) { + if s.repository == nil { + return ledger.HostRevenueStats{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + if query.HostUserID <= 0 || query.StartAtMS <= 0 || query.EndAtMS <= query.StartAtMS { + return ledger.HostRevenueStats{}, xerr.New(xerr.InvalidArgument, "host revenue stats range is invalid") + } + if query.EndAtMS-query.StartAtMS > int64(maxHostRevenueStatsRange/time.Millisecond) { + return ledger.HostRevenueStats{}, xerr.New(xerr.InvalidArgument, "host revenue stats range is too large") + } + ctx = appcode.WithContext(ctx, appcode.Normalize(appCode)) + return s.repository.GetHostRevenueStats(ctx, query) +} diff --git a/services/wallet-service/internal/service/wallet/point_coin_seller.go b/services/wallet-service/internal/service/wallet/point_coin_seller.go new file mode 100644 index 00000000..8c735b43 --- /dev/null +++ b/services/wallet-service/internal/service/wallet/point_coin_seller.go @@ -0,0 +1,56 @@ +package wallet + +import ( + "context" + "strings" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +// ListPointWithdrawalCoinSellers 返回服务端过滤后的币商白名单;国家由可信用户资料提供而不是客户端表单。 +func (s *Service) ListPointWithdrawalCoinSellers(ctx context.Context, appCode string, countryCode string, includeDisabled bool) ([]ledger.PointWithdrawalCoinSellerConfig, error) { + if strings.TrimSpace(appCode) == "" { + return nil, xerr.New(xerr.InvalidArgument, "app_code is required") + } + if s.repository == nil { + return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + appCode = appcode.Normalize(appCode) + return s.repository.ListPointWithdrawalCoinSellers(appcode.WithContext(ctx, appCode), appCode, strings.ToUpper(strings.TrimSpace(countryCode)), includeDisabled) +} + +// TransferPointToCoinSeller 验证客户端允许提交的最小字段;兑换比例由 repository 事务内锁定配置解析。 +func (s *Service) TransferPointToCoinSeller(ctx context.Context, command ledger.PointToCoinSellerCommand) (ledger.PointToCoinSellerReceipt, error) { + if strings.TrimSpace(command.AppCode) == "" || strings.TrimSpace(command.CommandID) == "" || command.SourceUserID <= 0 || command.SellerUserID <= 0 || command.PointAmount <= 0 { + return ledger.PointToCoinSellerReceipt{}, xerr.New(xerr.InvalidArgument, "point coin seller transfer command is incomplete") + } + command.AppCode = appcode.Normalize(command.AppCode) + command.CommandID = strings.TrimSpace(command.CommandID) + command.SourceCountryCode = strings.ToUpper(strings.TrimSpace(command.SourceCountryCode)) + command.Reason = strings.TrimSpace(command.Reason) + if len(command.CommandID) > 128 || len(command.SourceCountryCode) > 16 || len(command.Reason) > 512 { + return ledger.PointToCoinSellerReceipt{}, xerr.New(xerr.InvalidArgument, "point coin seller transfer text fields are too long") + } + if s.repository == nil { + return ledger.PointToCoinSellerReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + return s.repository.TransferPointToCoinSeller(appcode.WithContext(ctx, command.AppCode), command) +} + +// GetPointWithdrawalConfig 读取已发布政策快照;nowMS 由服务端补齐,避免客户端选择历史政策。 +func (s *Service) GetPointWithdrawalConfig(ctx context.Context, appCode string, regionID int64, nowMS int64) (ledger.PointWithdrawalRuntimeConfig, error) { + if strings.TrimSpace(appCode) == "" || regionID < 0 { + return ledger.PointWithdrawalRuntimeConfig{}, xerr.New(xerr.InvalidArgument, "point withdrawal config query is invalid") + } + if s.repository == nil { + return ledger.PointWithdrawalRuntimeConfig{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + if nowMS <= 0 { + nowMS = time.Now().UnixMilli() + } + appCode = appcode.Normalize(appCode) + return s.repository.GetPointWithdrawalConfig(appcode.WithContext(ctx, appCode), appCode, regionID, nowMS) +} diff --git a/services/wallet-service/internal/service/wallet/point_withdrawal.go b/services/wallet-service/internal/service/wallet/point_withdrawal.go index b1732f50..eadf97e3 100644 --- a/services/wallet-service/internal/service/wallet/point_withdrawal.go +++ b/services/wallet-service/internal/service/wallet/point_withdrawal.go @@ -10,12 +10,11 @@ import ( ) const ( - defaultPointWithdrawalFeeBPS int64 = 500 defaultPointWithdrawalPointsPerUSD int64 = 100000 minPointWithdrawalGrossPoints int64 = 1000000 ) -// FreezePointWithdrawal 把 Huwaa 收益积分从 available 冻结到 frozen;它复用钱包单边冻结账本,但不开放旧工资资产。 +// FreezePointWithdrawal 把已启用 App 的收益积分从 available 冻结到 frozen;gateway 必须先读取已发布政策快照。 func (s *Service) FreezePointWithdrawal(ctx context.Context, command ledger.PointWithdrawalCommand) (ledger.PointWithdrawalReceipt, error) { if err := normalizePointWithdrawalCommand(&command, false); err != nil { return ledger.PointWithdrawalReceipt{}, err @@ -83,9 +82,13 @@ func normalizePointWithdrawalCommand(command *ledger.PointWithdrawalCommand, req return xerr.New(xerr.InvalidArgument, "operator_user_id is required") } command.CommandID = strings.TrimSpace(command.CommandID) + if strings.TrimSpace(command.AppCode) == "" { + return xerr.New(xerr.InvalidArgument, "app_code is required") + } command.AppCode = appcode.Normalize(command.AppCode) - if command.AppCode != "huwaa" { - return xerr.New(xerr.InvalidArgument, "point withdrawal is only enabled for huwaa") + if command.AppCode != "huwaa" && command.AppCode != "fami" { + // Lalu 继续使用旧公会工资提现链路;只有显式迁移到 POINT 钱包的 App 才能调用本接口。 + return xerr.New(xerr.InvalidArgument, "point withdrawal is not enabled for app") } command.AssetType = strings.ToUpper(strings.TrimSpace(command.AssetType)) if !ledger.ValidPointWithdrawalAssetType(command.AssetType) { @@ -97,9 +100,6 @@ func normalizePointWithdrawalCommand(command *ledger.PointWithdrawalCommand, req if command.PointsPerUSD <= 0 { command.PointsPerUSD = defaultPointWithdrawalPointsPerUSD } - if command.FeeBPS <= 0 { - command.FeeBPS = defaultPointWithdrawalFeeBPS - } if command.FeeBPS < 0 || command.FeeBPS > 10000 { return xerr.New(xerr.InvalidArgument, "point withdrawal fee_bps is invalid") } diff --git a/services/wallet-service/internal/service/wallet/ports/clients.go b/services/wallet-service/internal/service/wallet/ports/clients.go index 29fd3ef3..67b110ee 100644 --- a/services/wallet-service/internal/service/wallet/ports/clients.go +++ b/services/wallet-service/internal/service/wallet/ports/clients.go @@ -147,7 +147,7 @@ type V5PayProductTypesResponse struct { RawJSON string } -// BinanceDepositLookupRequest 是后台 USDT 币商充值使用的 Binance 入金查单条件。 +// BinanceDepositLookupRequest 是 H5 充值和后台 USDT 币商充值共用的 Binance 入金查单条件。 type BinanceDepositLookupRequest struct { AppCode string ExternalID string @@ -201,7 +201,7 @@ type BSCUSDTClient interface { VerifyUSDTTransfer(ctx context.Context, txHash string, toAddress string, amountMinor int64) (string, error) } -// BinanceClient 隔离 Binance signed API;service 只关心入金记录是否匹配后台订单。 +// BinanceClient 隔离 Binance signed API;service 只关心入金记录是否匹配当前 App 订单。 type BinanceClient interface { FindUSDTDeposit(ctx context.Context, req BinanceDepositLookupRequest) (BinanceDepositRecord, error) } diff --git a/services/wallet-service/internal/service/wallet/ports/repository.go b/services/wallet-service/internal/service/wallet/ports/repository.go index ea5798ef..36dda1af 100644 --- a/services/wallet-service/internal/service/wallet/ports/repository.go +++ b/services/wallet-service/internal/service/wallet/ports/repository.go @@ -55,6 +55,9 @@ type HostSalaryStore interface { GetActiveHostSalaryPolicy(ctx context.Context, regionID int64, settlementMode string, triggerMode string, nowMs int64) (ledger.HostSalaryPolicy, bool, error) GetHostSalaryProgress(ctx context.Context, userID int64, cycleKey string) (ledger.HostSalaryProgress, error) GetTeamHostSalaryStats(ctx context.Context, query ledger.TeamHostSalaryStatsQuery) ([]ledger.TeamHostSalaryCycleStat, error) + GetHostRevenueStats(ctx context.Context, query ledger.HostRevenueStatsQuery) (ledger.HostRevenueStats, error) + GetAgencyPointShareStats(ctx context.Context, query ledger.AgencyPointShareStatsQuery) (ledger.AgencyPointShareStats, error) + GetAgencyHostGiftStats(ctx context.Context, query ledger.AgencyHostGiftStatsQuery) (ledger.AgencyHostGiftStats, error) ProcessHostSalarySettlementBatch(ctx context.Context, command ledger.HostSalarySettlementBatchCommand) (ledger.HostSalarySettlementBatchResult, error) } @@ -66,6 +69,9 @@ type CoinSellerStore interface { ListCoinSellerSalaryExchangeRateTiers(ctx context.Context, appCode string, regionID int64, includeDisabled bool) ([]ledger.CoinSellerSalaryExchangeRateTier, error) ExchangeSalaryToCoin(ctx context.Context, command ledger.SalaryExchangeCommand) (ledger.SalaryExchangeReceipt, error) TransferSalaryToCoinSeller(ctx context.Context, command ledger.SalaryTransferToCoinSellerCommand) (ledger.SalaryTransferToCoinSellerReceipt, error) + ListPointWithdrawalCoinSellers(ctx context.Context, appCode string, countryCode string, includeDisabled bool) ([]ledger.PointWithdrawalCoinSellerConfig, error) + TransferPointToCoinSeller(ctx context.Context, command ledger.PointToCoinSellerCommand) (ledger.PointToCoinSellerReceipt, error) + GetPointWithdrawalConfig(ctx context.Context, appCode string, regionID int64, nowMS int64) (ledger.PointWithdrawalRuntimeConfig, error) FreezeSalaryWithdrawal(ctx context.Context, command ledger.SalaryWithdrawalCommand) (ledger.SalaryWithdrawalReceipt, error) SettleSalaryWithdrawal(ctx context.Context, command ledger.SalaryWithdrawalCommand) (ledger.SalaryWithdrawalReceipt, error) ReleaseSalaryWithdrawal(ctx context.Context, command ledger.SalaryWithdrawalCommand) (ledger.SalaryWithdrawalReceipt, error) @@ -97,9 +103,12 @@ type GameLedgerStore interface { type RechargeStore interface { ListRechargeBills(ctx context.Context, query ledger.ListRechargeBillsQuery) ([]ledger.RechargeBill, int64, error) SummarizeRechargeBills(ctx context.Context, query ledger.ListRechargeBillsQuery) (ledger.RechargeBillSummary, error) + BatchGetUserRechargeStats(ctx context.Context, userIDs []int64) ([]ledger.UserRechargeStats, error) GetRechargeBillOverview(ctx context.Context, query ledger.ListRechargeBillsQuery, tzOffsetMinutes int32) (ledger.RechargeBillOverview, error) ListGooglePaymentOrderRefs(ctx context.Context, appCode string, transactionIDs []string) (map[string]ledger.GooglePaymentOrderRef, error) + ListPendingGooglePaymentOrderRefs(ctx context.Context, limit int, nowMS int64) ([]ledger.GooglePaymentOrderRef, error) UpdateGooglePaymentPaidDetails(ctx context.Context, appCode string, paymentOrderID string, details ledger.GooglePaymentPaidDetails) error + MarkGooglePaymentPaidSyncFailure(ctx context.Context, ref ledger.GooglePaymentOrderRef, message string, nowMS int64) error ListRechargeProducts(ctx context.Context, userID int64, regionID int64, platform string, audienceType string) ([]ledger.RechargeProduct, []string, error) ListAdminRechargeProducts(ctx context.Context, query ledger.ListRechargeProductsQuery) ([]ledger.RechargeProduct, int64, error) GetRechargeProduct(ctx context.Context, appCode string, productID int64) (ledger.RechargeProduct, error) @@ -131,14 +140,31 @@ type ExternalRechargeStore interface { ListExternalRechargeOrdersForReconcile(ctx context.Context, appCode string, limit int) ([]ledger.ExternalRechargeOrder, error) } -// VIPStore 管理 VIP 购买、发放和后台等级配置。 +// VIPStore 管理 VIP 购买、发放、每日返现和后台等级配置。 type VIPStore interface { - ListVipPackages(ctx context.Context, userID int64) (ledger.UserVip, []ledger.VipLevel, error) + // ListVipPackages 在同一个数据库快照中返回最终 VIP 状态和套餐,避免配置切换时拼出跨版本响应。 + ListVipPackages(ctx context.Context, userID int64) (ledger.VipState, []ledger.VipLevel, error) GetMyVip(ctx context.Context, userID int64) (ledger.UserVip, error) + // GetVipProgramConfig 返回单 App 的显式状态机和权益目录,调用方不得按 app_code 复制规则分支。 + GetVipProgramConfig(ctx context.Context) (ledger.VipProgramConfig, []ledger.VipBenefit, error) + // GetVipState 是 paid membership、当前佩戴体验卡和最终权益的唯一合并读模型。 + GetVipState(ctx context.Context, userID int64) (ledger.VipState, error) + CheckVipBenefit(ctx context.Context, userID int64, benefitCode string) (ledger.CheckVipBenefitResult, error) PurchaseVip(ctx context.Context, command ledger.PurchaseVipCommand) (ledger.PurchaseVipReceipt, error) GrantVip(ctx context.Context, command ledger.GrantVipCommand) (ledger.GrantVipReceipt, error) - ListAdminVipLevels(ctx context.Context) ([]ledger.VipLevel, error) - UpdateAdminVipLevels(ctx context.Context, levels []ledger.AdminVipLevelCommand, operatorUserID int64) ([]ledger.VipLevel, error) + GrantVipTrialCard(ctx context.Context, command ledger.GrantVipTrialCardCommand) (ledger.GrantVipTrialCardReceipt, error) + EquipVipTrialCard(ctx context.Context, command ledger.EquipVipTrialCardCommand) (ledger.VipTrialCard, ledger.VipState, error) + UnequipVipTrialCard(ctx context.Context, command ledger.UnequipVipTrialCardCommand) (bool, ledger.VipState, error) + // ListAdminVipLevels 把 program 与完整等级配置作为一个不可拆分的后台读模型返回。 + ListAdminVipLevels(ctx context.Context) (ledger.VipProgramConfig, []ledger.VipLevel, error) + UpdateAdminVipLevels(ctx context.Context, levels []ledger.AdminVipLevelCommand, operatorUserID int64) (ledger.VipProgramConfig, []ledger.VipLevel, error) + UpdateAdminVipProgramConfig(ctx context.Context, command ledger.AdminVipProgramConfigCommand) (ledger.VipProgramConfig, error) + UpdateVipUserSettings(ctx context.Context, command ledger.UpdateVipUserSettingsCommand) (ledger.VipUserSettings, error) + // ProcessVipDailyCoinRebateBatch 创建或复用 UTC 日快照,并在同一事务推进唯一分页游标。 + ProcessVipDailyCoinRebateBatch(ctx context.Context, command ledger.ProcessVipDailyCoinRebateBatchCommand) (ledger.ProcessVipDailyCoinRebateBatchResult, error) + GetMyCurrentVipDailyCoinRebate(ctx context.Context, userID int64, nowMS int64) (ledger.VipDailyCoinRebate, bool, error) + ListMyVipDailyCoinRebateStatuses(ctx context.Context, query ledger.ListVipDailyCoinRebateStatusesQuery) ([]ledger.VipDailyCoinRebate, int64, error) + ClaimVipDailyCoinRebate(ctx context.Context, command ledger.ClaimVipDailyCoinRebateCommand) (ledger.ClaimVipDailyCoinRebateReceipt, error) } // RedPacketStore 管理红包配置、创建、领取、查询和退款补偿。 diff --git a/services/wallet-service/internal/service/wallet/projection.go b/services/wallet-service/internal/service/wallet/projection.go index 574900b1..234a153a 100644 --- a/services/wallet-service/internal/service/wallet/projection.go +++ b/services/wallet-service/internal/service/wallet/projection.go @@ -129,7 +129,7 @@ func isBadgeGrantWalletProjectionMessage(message walletmq.WalletOutboxMessage) b return false } switch message.EventType { - case "ResourceGranted", "ResourceGroupGranted": + case walletmq.EventTypeResourceGranted, walletmq.EventTypeResourceGroupGranted: return true default: return false diff --git a/services/wallet-service/internal/service/wallet/recharge.go b/services/wallet-service/internal/service/wallet/recharge.go index 9256d9e2..87a7fdd0 100644 --- a/services/wallet-service/internal/service/wallet/recharge.go +++ b/services/wallet-service/internal/service/wallet/recharge.go @@ -2,11 +2,15 @@ package wallet import ( "context" + "log/slog" + "sync" + "time" + "hyapp/pkg/appcode" + "hyapp/pkg/logx" "hyapp/pkg/xerr" "hyapp/services/wallet-service/internal/domain/ledger" "strings" - "time" ) // ListRechargeBills 返回充值账单只读列表;所有充值来源必须先在 wallet-service 落充值记录。 @@ -30,6 +34,16 @@ func (s *Service) GetRechargeBillSummary(ctx context.Context, query ledger.ListR return s.repository.SummarizeRechargeBills(ctx, query) } +// BatchGetUserRechargeStats 返回后台详情需要的累计充值聚合;缺失用户不返回占位行,由调用方按 0 展示。 +func (s *Service) BatchGetUserRechargeStats(ctx context.Context, appCode string, userIDs []int64) ([]ledger.UserRechargeStats, error) { + if s.repository == nil { + return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + appCode = appcode.Normalize(appCode) + ctx = appcode.WithContext(ctx, appCode) + return s.repository.BatchGetUserRechargeStats(ctx, userIDs) +} + // GetRechargeBillOverview 汇总财务概览的按日趋势、区域分布与谷歌实付覆盖度;筛选口径与账单列表一致。 func (s *Service) GetRechargeBillOverview(ctx context.Context, query ledger.ListRechargeBillsQuery, tzOffsetMinutes int32) (ledger.RechargeBillOverview, error) { if s.repository == nil { @@ -51,6 +65,16 @@ type GooglePaymentPriceResult struct { Err string } +type GooglePaymentPaidSyncBatchResult struct { + Claimed int + Succeeded int + Failed int +} + +type googlePlayOrdersBatchClient interface { + GetOrders(ctx context.Context, packageName string, orderIDs []string) (map[string]ledger.GooglePlayOrder, error) +} + const maxGooglePaymentPriceRefreshBatch = 100 // RefreshGooglePaymentPrices 用 Play Orders API 拉取账单的用户实付币种、总额、税额和净收入并写回 payment_orders。 @@ -92,16 +116,18 @@ func (s *Service) RefreshGooglePaymentPrices(ctx context.Context, appCode string } func (s *Service) refreshGooglePaymentPrice(ctx context.Context, appCode string, ref ledger.GooglePaymentOrderRef) GooglePaymentPriceResult { - result := GooglePaymentPriceResult{TransactionID: ref.TransactionID} if strings.TrimSpace(ref.ProviderOrderID) == "" { - result.Err = "google order id is missing" - return result + return s.failedGooglePaymentPrice(ctx, ref, "google order id is missing") } order, err := s.googlePlay.GetOrder(ctx, ref.PackageName, ref.ProviderOrderID) if err != nil { - result.Err = err.Error() - return result + return s.failedGooglePaymentPrice(ctx, ref, err.Error()) } + return s.refreshGooglePaymentPriceFromOrder(ctx, appCode, ref, order) +} + +func (s *Service) refreshGooglePaymentPriceFromOrder(ctx context.Context, appCode string, ref ledger.GooglePaymentOrderRef, order ledger.GooglePlayOrder) GooglePaymentPriceResult { + result := GooglePaymentPriceResult{TransactionID: ref.TransactionID} details := ledger.GooglePaymentPaidDetails{ CurrencyCode: order.CurrencyCode, PaidAmountMicro: order.TotalAmountMicro, @@ -110,8 +136,7 @@ func (s *Service) refreshGooglePaymentPrice(ctx context.Context, appCode string, SyncedAtMS: s.now().UnixMilli(), } if err := s.repository.UpdateGooglePaymentPaidDetails(ctx, appCode, ref.PaymentOrderID, details); err != nil { - result.Err = err.Error() - return result + return s.failedGooglePaymentPrice(ctx, ref, err.Error()) } result.CurrencyCode = details.CurrencyCode result.PaidAmountMicro = details.PaidAmountMicro @@ -121,6 +146,102 @@ func (s *Service) refreshGooglePaymentPrice(ctx context.Context, appCode string, return result } +func (s *Service) failedGooglePaymentPrice(ctx context.Context, ref ledger.GooglePaymentOrderRef, message string) GooglePaymentPriceResult { + result := GooglePaymentPriceResult{TransactionID: ref.TransactionID, Err: strings.TrimSpace(message)} + if err := s.repository.MarkGooglePaymentPaidSyncFailure(ctx, ref, result.Err, s.now().UnixMilli()); err != nil { + logx.Warn(ctx, "google_paid_sync_failure_persist_failed", + slog.String("app_code", ref.AppCode), + slog.String("payment_order_id", ref.PaymentOrderID), + slog.String("error", err.Error()), + ) + } + return result +} + +// ProcessGooglePaymentPaidSyncBatch is the durable worker entrypoint. It uses batchGet for the common +// path and bounded single-order fallback when Google rejects a batch containing one stale order ID. +func (s *Service) ProcessGooglePaymentPaidSyncBatch(ctx context.Context, batchSize int) (GooglePaymentPaidSyncBatchResult, error) { + if s.repository == nil { + return GooglePaymentPaidSyncBatchResult{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + if s.googlePlay == nil { + return GooglePaymentPaidSyncBatchResult{}, xerr.New(xerr.Unavailable, "google play client is not configured") + } + refs, err := s.repository.ListPendingGooglePaymentOrderRefs(ctx, batchSize, s.now().UnixMilli()) + if err != nil { + return GooglePaymentPaidSyncBatchResult{}, err + } + result := GooglePaymentPaidSyncBatchResult{Claimed: len(refs)} + if len(refs) == 0 { + return result, nil + } + prices := s.refreshGooglePaymentRefBatch(ctx, refs) + for _, price := range prices { + if price.Err == "" { + result.Succeeded++ + } else { + result.Failed++ + } + } + return result, nil +} + +func (s *Service) refreshGooglePaymentRefBatch(ctx context.Context, refs []ledger.GooglePaymentOrderRef) []GooglePaymentPriceResult { + results := make([]GooglePaymentPriceResult, len(refs)) + groups := make(map[string][]int) + for index, ref := range refs { + groups[strings.TrimSpace(ref.PackageName)] = append(groups[strings.TrimSpace(ref.PackageName)], index) + } + batchClient, supportsBatch := s.googlePlay.(googlePlayOrdersBatchClient) + for packageName, indexes := range groups { + if supportsBatch && packageName != "" { + orderIDs := make([]string, 0, len(indexes)) + for _, index := range indexes { + if orderID := strings.TrimSpace(refs[index].ProviderOrderID); orderID != "" { + orderIDs = append(orderIDs, orderID) + } + } + if len(orderIDs) == len(indexes) { + if orders, err := batchClient.GetOrders(ctx, packageName, orderIDs); err == nil { + for _, index := range indexes { + ref := refs[index] + order, ok := orders[ref.ProviderOrderID] + if !ok { + results[index] = s.failedGooglePaymentPrice(ctx, ref, "google order is not found") + continue + } + results[index] = s.refreshGooglePaymentPriceFromOrder(ctx, ref.AppCode, ref, order) + } + continue + } + } + } + s.refreshGooglePaymentRefSingles(ctx, refs, indexes, results) + } + return results +} + +func (s *Service) refreshGooglePaymentRefSingles(ctx context.Context, refs []ledger.GooglePaymentOrderRef, indexes []int, results []GooglePaymentPriceResult) { + const concurrency = 8 + semaphore := make(chan struct{}, concurrency) + var wait sync.WaitGroup + for _, index := range indexes { + wait.Add(1) + go func(index int) { + defer wait.Done() + select { + case semaphore <- struct{}{}: + defer func() { <-semaphore }() + case <-ctx.Done(): + results[index] = s.failedGooglePaymentPrice(ctx, refs[index], ctx.Err().Error()) + return + } + results[index] = s.refreshGooglePaymentPrice(ctx, refs[index].AppCode, refs[index]) + }(index) + } + wait.Wait() +} + // ListRechargeProducts 返回区域化充值档位;region_id 必须由 gateway 从 user-service 资料解析。 func (s *Service) ListRechargeProducts(ctx context.Context, userID int64, regionID int64, platform string, audienceType string) ([]ledger.RechargeProduct, []string, error) { if userID <= 0 || regionID <= 0 { @@ -274,7 +395,7 @@ func (s *Service) ConfirmGooglePayment(ctx context.Context, command ledger.Googl if err != nil { return ledger.GooglePaymentReceipt{}, err } - // 入账已完成,实付明细只影响财务展示:异步拉 Orders API,失败静默,后台可手动刷新补数。 + // 入账已完成,实付明细只影响财务展示:先即时异步拉取;失败会持久化并由 15 秒 worker 补偿。 s.scheduleGooglePaymentPriceRefresh(command.AppCode, receipt.PaymentOrderID, command.PackageName, purchase.OrderID) if receipt.ConsumeState == ledger.PaymentConsumeStateConsumed { return receipt, nil @@ -299,21 +420,22 @@ func (s *Service) scheduleGooglePaymentPriceRefresh(appCode string, paymentOrder strings.TrimSpace(paymentOrderID) == "" || strings.TrimSpace(orderID) == "" { return } - syncedAt := s.now().UnixMilli() go func() { ctx, cancel := context.WithTimeout(appcode.WithContext(context.Background(), appCode), 10*time.Second) defer cancel() - order, err := s.googlePlay.GetOrder(ctx, packageName, orderID) - if err != nil { - return - } - _ = s.repository.UpdateGooglePaymentPaidDetails(ctx, appCode, paymentOrderID, ledger.GooglePaymentPaidDetails{ - CurrencyCode: order.CurrencyCode, - PaidAmountMicro: order.TotalAmountMicro, - TaxAmountMicro: order.TaxAmountMicro, - NetAmountMicro: order.NetAmountMicro, - SyncedAtMS: syncedAt, + result := s.refreshGooglePaymentPrice(ctx, appCode, ledger.GooglePaymentOrderRef{ + AppCode: appCode, + PaymentOrderID: paymentOrderID, + PackageName: packageName, + ProviderOrderID: orderID, }) + if result.Err != "" { + logx.Warn(ctx, "google_paid_sync_immediate_failed", + slog.String("app_code", appCode), + slog.String("payment_order_id", paymentOrderID), + slog.String("error", result.Err), + ) + } }() } diff --git a/services/wallet-service/internal/service/wallet/resource.go b/services/wallet-service/internal/service/wallet/resource.go index f80d9ecb..43a57bd7 100644 --- a/services/wallet-service/internal/service/wallet/resource.go +++ b/services/wallet-service/internal/service/wallet/resource.go @@ -6,6 +6,7 @@ import ( "hyapp/pkg/appcode" "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" resourcedomain "hyapp/services/wallet-service/internal/domain/resource" ) @@ -261,6 +262,52 @@ func (s *Service) EquipUserResource(ctx context.Context, command resourcedomain. } command.AppCode = appcode.Normalize(command.AppCode) ctx = appcode.WithContext(ctx, command.AppCode) + if command.UserID <= 0 || command.ResourceID <= 0 { + return resourcedomain.UserResourceEntitlement{}, xerr.New(xerr.InvalidArgument, "user_id and resource_id are required") + } + resource, err := s.repository.GetResource(ctx, command.ResourceID) + if err != nil { + return resourcedomain.UserResourceEntitlement{}, err + } + if resourcedomain.NormalizeResourceType(resource.ResourceType) == resourcedomain.TypeVIPTrialCard { + // 体验卡在 UI 上仍是普通背包物品,但其装备动作会改变最终 VIP 权益。 + // 因此通用入口必须委托专用状态机,不能直接写 equipment 而绕过 App 开关和 VipEffectiveChanged outbox。 + command.RequestID = strings.TrimSpace(command.RequestID) + command.EntitlementID = strings.TrimSpace(command.EntitlementID) + if command.RequestID == "" || command.EntitlementID == "" { + return resourcedomain.UserResourceEntitlement{}, xerr.New(xerr.InvalidArgument, "request_id and entitlement_id are required for vip_trial_card") + } + items, listErr := s.repository.ListUserResources(ctx, resourcedomain.ListUserResourcesQuery{ + AppCode: command.AppCode, + UserID: command.UserID, + ResourceType: resourcedomain.TypeVIPTrialCard, + ActiveOnly: true, + }) + if listErr != nil { + return resourcedomain.UserResourceEntitlement{}, listErr + } + var selected *resourcedomain.UserResourceEntitlement + for index := range items { + if items[index].EntitlementID == command.EntitlementID && items[index].ResourceID == command.ResourceID { + selected = &items[index] + break + } + } + if selected == nil { + return resourcedomain.UserResourceEntitlement{}, xerr.New(xerr.NotFound, "vip trial card entitlement not found") + } + if _, _, err := s.EquipVipTrialCard(ctx, ledger.EquipVipTrialCardCommand{ + AppCode: command.AppCode, + RequestID: command.RequestID, + UserID: command.UserID, + EntitlementID: command.EntitlementID, + }); err != nil { + return resourcedomain.UserResourceEntitlement{}, err + } + // 专用事务已经再次校验 entitlement 并提交装备/outbox;这里复用预读完整资源投影满足通用 API 返回契约。 + selected.Equipped = true + return *selected, nil + } return s.repository.EquipUserResource(ctx, command) } @@ -270,6 +317,23 @@ func (s *Service) UnequipUserResource(ctx context.Context, command resourcedomai } command.AppCode = appcode.Normalize(command.AppCode) ctx = appcode.WithContext(ctx, command.AppCode) + command.ResourceType = resourcedomain.NormalizeResourceType(command.ResourceType) + if command.ResourceType == resourcedomain.TypeVIPTrialCard { + // 和装备相同,通用卸下必须发布 VIP 有效态变化,不能只删除通用装备指针。 + unequipped, state, err := s.UnequipVipTrialCard(ctx, ledger.UnequipVipTrialCardCommand{ + AppCode: command.AppCode, + RequestID: strings.TrimSpace(command.RequestID), + UserID: command.UserID, + }) + if err != nil { + return resourcedomain.UnequipUserResourceResult{}, err + } + return resourcedomain.UnequipUserResourceResult{ + ResourceType: command.ResourceType, + Unequipped: unequipped, + UpdatedAtMS: state.EvaluatedAtMS, + }, nil + } return s.repository.UnequipUserResource(ctx, command) } diff --git a/services/wallet-service/internal/service/wallet/service.go b/services/wallet-service/internal/service/wallet/service.go index 4a137f50..d5e2cf0e 100644 --- a/services/wallet-service/internal/service/wallet/service.go +++ b/services/wallet-service/internal/service/wallet/service.go @@ -63,7 +63,7 @@ func (s *Service) SetBSCUSDTClient(client ports.BSCUSDTClient) { s.bscUSDT = client } -// SetBinanceClient 注入 Binance 入金只读查询客户端;后台币商充值可用 off-chain 单号自动查账。 +// SetBinanceClient 注入 Binance 入金只读查询客户端;H5 充值和后台币商充值共用按 App 隔离的 off-chain 查账。 func (s *Service) SetBinanceClient(client ports.BinanceClient) { s.binance = client } diff --git a/services/wallet-service/internal/service/wallet/service_test.go b/services/wallet-service/internal/service/wallet/service_test.go index 832c9775..077ed43b 100644 --- a/services/wallet-service/internal/service/wallet/service_test.go +++ b/services/wallet-service/internal/service/wallet/service_test.go @@ -168,6 +168,44 @@ func TestHuwaaHostGiftPolicyCreditsPointWithoutCoinIncomeAndAllowsSelfGift(t *te } } +func TestFamiHostGiftPolicyCreditsAgencyShareWithHistoricalOwnershipSnapshot(t *testing.T) { + repository := mysqltest.NewRepository(t) + repository.SetBalanceForApp("fami", 32001, 1000) + repository.SetGiftPriceForApp("fami", "fami-rose", "v1", 100, 0, 100) + repository.SeedWalletPolicyInstanceWithAgency("fami", "fami-guild-live", 0, "active", 700000, 200000) + svc := walletservice.New(repository) + + receipt, err := svc.DebitGift(context.Background(), ledger.DebitGiftCommand{ + AppCode: "fami", CommandID: "cmd-fami-agency-share", RoomID: "room-fami", + SenderUserID: 32001, TargetUserID: 32002, TargetIsHost: true, TargetHostRegionID: 12, + TargetAgencyOwnerUserID: 32003, GiftID: "fami-rose", GiftCount: 1, PriceVersion: "v1", RegionID: 12, SenderRegionID: 12, + }) + if err != nil { + t.Fatalf("DebitGift with Fami agency policy failed: %v", err) + } + if receipt.HostPointAdded != 70 || receipt.HostPointBalanceAfter != 70 { + t.Fatalf("Fami host point receipt mismatch: %+v", receipt) + } + assertBalanceForApp(t, svc, "fami", 32002, ledger.AssetPoint, 70) + assertBalanceForApp(t, svc, "fami", 32003, ledger.AssetPoint, 20) + if got := repository.CountRows("agency_point_share_entries", "transaction_id = ? AND host_user_id = ? AND agency_owner_user_id = ? AND point_delta = ? AND ratio_ppm = ?", receipt.TransactionID, int64(32002), int64(32003), int64(20), int64(200000)); got != 1 { + t.Fatalf("Fami agency share snapshot mismatch, got %d", got) + } + if got := repository.CountRows("wallet_outbox", "transaction_id = ? AND event_type = ?", receipt.TransactionID, "AgencyPointCredited"); got != 1 { + t.Fatalf("Fami agency share must publish one event, got %d", got) + } + + replayed, err := svc.DebitGift(context.Background(), ledger.DebitGiftCommand{ + AppCode: "fami", CommandID: "cmd-fami-agency-share", RoomID: "room-fami", + SenderUserID: 32001, TargetUserID: 32002, TargetIsHost: true, TargetHostRegionID: 12, + TargetAgencyOwnerUserID: 32003, GiftID: "fami-rose", GiftCount: 1, PriceVersion: "v1", RegionID: 12, SenderRegionID: 12, + }) + if err != nil || replayed.TransactionID != receipt.TransactionID { + t.Fatalf("Fami agency share replay mismatch: receipt=%+v err=%v", replayed, err) + } + assertBalanceForApp(t, svc, "fami", 32003, ledger.AssetPoint, 20) +} + func TestHostGiftWithoutActiveHuwaaPolicyKeepsLegacyCoinIncome(t *testing.T) { repository := mysqltest.NewRepository(t) repository.SetBalance(31011, 1000) @@ -2187,6 +2225,7 @@ func TestPointWithdrawalAppGateWithoutMySQL(t *testing.T) { UserID: 23012, AssetType: ledger.AssetPoint, GrossPointAmount: 1_000_000, + FeeBPS: 500, }) if err != nil { t.Fatalf("Huwaa POINT withdrawal should reach repository: %v", err) @@ -2203,6 +2242,21 @@ func TestPointWithdrawalAppGateWithoutMySQL(t *testing.T) { } } +func TestFamiPointWithdrawalAllowsAdminConfiguredZeroFee(t *testing.T) { + repository := &fakePointGateRepository{} + svc := walletservice.New(repository) + receipt, err := svc.FreezePointWithdrawal(context.Background(), ledger.PointWithdrawalCommand{ + AppCode: "fami", CommandID: "cmd-fami-zero-fee", UserID: 23013, + AssetType: ledger.AssetPoint, GrossPointAmount: 1_000_000, PointsPerUSD: 200_000, FeeBPS: 0, + }) + if err != nil { + t.Fatalf("zero-fee Fami point withdrawal failed: %v", err) + } + if receipt.FeePointAmount != 0 || receipt.NetPointAmount != 1_000_000 || receipt.PointsPerUSD != 200_000 || receipt.FeeBPS != 0 { + t.Fatalf("zero-fee policy snapshot mismatch: %+v", receipt) + } +} + func TestPointWithdrawalFreezeSettleReleaseIsHuwaaOnly(t *testing.T) { repository := mysqltest.NewRepository(t) repository.SetAssetBalanceForApp("huwaa", 22001, ledger.AssetPoint, 2_000_000) @@ -2227,6 +2281,7 @@ func TestPointWithdrawalFreezeSettleReleaseIsHuwaaOnly(t *testing.T) { UserID: 22001, AssetType: ledger.AssetPoint, GrossPointAmount: 1_000_000, + FeeBPS: 500, }) if err != nil { t.Fatalf("FreezePointWithdrawal failed: %v", err) @@ -2242,6 +2297,7 @@ func TestPointWithdrawalFreezeSettleReleaseIsHuwaaOnly(t *testing.T) { UserID: 22001, AssetType: ledger.AssetPoint, GrossPointAmount: 1_000_000, + FeeBPS: 500, OperatorUserID: 90001, WithdrawalApplicationID: "77", }) @@ -2259,6 +2315,7 @@ func TestPointWithdrawalFreezeSettleReleaseIsHuwaaOnly(t *testing.T) { UserID: 22002, AssetType: ledger.AssetPoint, GrossPointAmount: 1_000_000, + FeeBPS: 500, }); err != nil { t.Fatalf("FreezePointWithdrawal for release failed: %v", err) } @@ -2268,6 +2325,7 @@ func TestPointWithdrawalFreezeSettleReleaseIsHuwaaOnly(t *testing.T) { UserID: 22002, AssetType: ledger.AssetPoint, GrossPointAmount: 1_000_000, + FeeBPS: 500, WithdrawalApplicationID: "78", }) if err != nil { @@ -2279,6 +2337,52 @@ func TestPointWithdrawalFreezeSettleReleaseIsHuwaaOnly(t *testing.T) { requireWalletBalanceForApp(t, svc, "huwaa", 22002, ledger.AssetPoint, 2_000_000, 0) } +// TestTransferPointToCoinSellerUsesServerWhitelist 验证 H5 不能自报比例,wallet 会按 App、国家和 active 配置原子扣 POINT/加币商库存。 +func TestTransferPointToCoinSellerUsesServerWhitelist(t *testing.T) { + repository := mysqltest.NewRepository(t) + repository.SetAssetBalanceForApp("fami", 22501, ledger.AssetPoint, 2_000_000) + repository.SetPointWithdrawalCoinSellerConfig("fami", 22502, 100_000, 92_000, []string{"SA"}, "active", 1) + svc := walletservice.New(repository) + + sellers, err := svc.ListPointWithdrawalCoinSellers(context.Background(), "fami", "SA", false) + if err != nil || len(sellers) != 1 || sellers[0].SellerUserID != 22502 { + t.Fatalf("eligible point withdrawal sellers mismatch: sellers=%+v err=%v", sellers, err) + } + if blocked, err := svc.ListPointWithdrawalCoinSellers(context.Background(), "fami", "TR", false); err != nil || len(blocked) != 0 { + t.Fatalf("country-filtered seller must be hidden: sellers=%+v err=%v", blocked, err) + } + + command := ledger.PointToCoinSellerCommand{ + AppCode: "fami", CommandID: "cmd-fami-point-seller-1", SourceUserID: 22501, + SellerUserID: 22502, PointAmount: 1_000_000, SourceCountryCode: "SA", Reason: "withdraw to seller", + } + receipt, err := svc.TransferPointToCoinSeller(context.Background(), command) + if err != nil { + t.Fatalf("TransferPointToCoinSeller failed: %v", err) + } + if receipt.SellerCoinAmount != 920_000 || receipt.RatioPointAmount != 100_000 || receipt.RatioSellerCoinAmount != 92_000 || receipt.SourcePointBalanceAfter != 1_000_000 || receipt.SellerBalanceAfter != 920_000 { + t.Fatalf("point seller transfer receipt mismatch: %+v", receipt) + } + replayed, err := svc.TransferPointToCoinSeller(context.Background(), command) + if err != nil || replayed.TransactionID != receipt.TransactionID || replayed.SellerCoinAmount != receipt.SellerCoinAmount { + t.Fatalf("point seller transfer replay mismatch: replay=%+v err=%v", replayed, err) + } + requireWalletBalanceForApp(t, svc, "fami", 22501, ledger.AssetPoint, 1_000_000, 0) + requireWalletBalanceForApp(t, svc, "fami", 22502, ledger.AssetCoinSellerCoin, 920_000, 0) + if got := repository.CountRows("wallet_transactions", "app_code = ? AND biz_type = ?", "fami", "point_transfer_to_coin_seller"); got != 1 { + t.Fatalf("idempotent point seller transfer must write one transaction, got %d", got) + } + + _, err = svc.TransferPointToCoinSeller(context.Background(), ledger.PointToCoinSellerCommand{ + AppCode: "fami", CommandID: "cmd-fami-point-seller-country-denied", SourceUserID: 22501, + SellerUserID: 22502, PointAmount: 100_000, SourceCountryCode: "TR", + }) + if err == nil { + t.Fatal("seller country restriction must reject transfer") + } + requireWalletBalanceForApp(t, svc, "fami", 22501, ledger.AssetPoint, 1_000_000, 0) +} + // TestCreditLuckyGiftRewardIsIdempotent 验证幸运礼物返奖只按 draw_id 派一次,并产出余额通知事实。 func TestCreditLuckyGiftRewardIsIdempotent(t *testing.T) { repository := mysqltest.NewRepository(t) @@ -2321,6 +2425,45 @@ func TestCreditLuckyGiftRewardIsIdempotent(t *testing.T) { } } +// TestCreditWheelRewardReconciliationReturnsExistingTransaction 验证 Activity 补偿重复提交稳定 draw command 时, +// 钱包先按 command_id + request hash 找回既有交易和原回执,不会再次增加余额或重复生成 outbox。 +func TestCreditWheelRewardReconciliationReturnsExistingTransaction(t *testing.T) { + repository := mysqltest.NewRepository(t) + svc := walletservice.New(repository) + command := ledger.WheelRewardCommand{ + AppCode: "fami", + CommandID: "wheel_reward:wheel_draw_reconcile_1", + TargetUserID: 21102, + Amount: 288, + DrawID: "wheel_draw_reconcile_1", + WheelID: "classic", + SelectedTierID: "classic-coin-288", + VisibleRegionID: 10, + Reason: "wheel_reward", + } + + first, err := svc.CreditWheelReward(context.Background(), command) + if err != nil { + t.Fatalf("CreditWheelReward failed: %v", err) + } + second, err := svc.CreditWheelReward(context.Background(), command) + if err != nil { + t.Fatalf("CreditWheelReward reconciliation retry failed: %v", err) + } + if first.TransactionID == "" || first.TransactionID != second.TransactionID || second.Balance.AvailableAmount != 288 { + t.Fatalf("wheel reward reconciliation receipt mismatch: first=%+v second=%+v", first, second) + } + if got := repository.CountRows("wallet_transactions", "app_code = ? AND command_id = ? AND biz_type = ?", "fami", command.CommandID, "wheel_reward"); got != 1 { + t.Fatalf("wheel reward reconciliation must keep one transaction, got %d", got) + } + if got := repository.CountRows("wallet_outbox", "app_code = ? AND transaction_id = ? AND event_type = ?", "fami", first.TransactionID, "WalletBalanceChanged"); got != 1 { + t.Fatalf("wheel reward reconciliation must keep one balance event, got %d", got) + } + if got := repository.CountRows("wallet_outbox", "app_code = ? AND transaction_id = ? AND event_type = ?", "fami", first.TransactionID, "WalletWheelRewardCredited"); got != 1 { + t.Fatalf("wheel reward reconciliation must keep one reward fact, got %d", got) + } +} + // TestTransferCoinFromSellerMovesDedicatedAssetToPlayerCoin 验证币商专用金币和玩家普通金币不会混账。 func TestTransferCoinFromSellerMovesDedicatedAssetToPlayerCoin(t *testing.T) { repository := mysqltest.NewRepository(t) @@ -3504,7 +3647,7 @@ func TestH5USDTRechargeCreditsNormalUserCoinWallet(t *testing.T) { AppCode: appcode.Default, OrderID: order.OrderID, TargetUserID: 43001, - TxHash: "tx-normal-usdt", + TxHash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", }) if err != nil { t.Fatalf("SubmitH5RechargeTx normal failed: %v", err) @@ -3512,7 +3655,7 @@ func TestH5USDTRechargeCreditsNormalUserCoinWallet(t *testing.T) { if credited.Status != ledger.ExternalRechargeStatusCredited || credited.TransactionID == "" { t.Fatalf("normal usdt credited order mismatch: %+v", credited) } - if tron.lastTxHash != "tx-normal-usdt" || tron.lastToAddress != "TPlatformReceiveAddress" || tron.lastAmountMinor != 150 { + if tron.lastTxHash != "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" || tron.lastToAddress != "TPlatformReceiveAddress" || tron.lastAmountMinor != 150 { t.Fatalf("tron verification input mismatch: %+v", tron) } assertBalance(t, svc, 43001, ledger.AssetCoin, 120000) @@ -3556,7 +3699,7 @@ func TestH5USDTRechargeCreditsCoinSellerWalletOnly(t *testing.T) { AppCode: appcode.Default, OrderID: order.OrderID, TargetUserID: 44001, - TxHash: "tx-seller-usdt", + TxHash: "1123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", }) if err != nil { t.Fatalf("SubmitH5RechargeTx seller failed: %v", err) @@ -6293,6 +6436,17 @@ func TestPurchaseVipRenewalAlignsRewardEntitlementExpiry(t *testing.T) { // TestGrantVipUsesUnifiedActivationAndNoticeOutbox 验证后台和活动赠送 VIP 走统一激活入口并写 IM outbox。 func TestGrantVipUsesUnifiedActivationAndNoticeOutbox(t *testing.T) { repository := mysqltest.NewRepository(t) + // initdb 不为 Lalu 猜测默认礼包资源;本用例要验证“发放 VIP 同时发放礼包”, + // 因此必须显式构造资源组,不依赖历史测试库残留或运营配置。 + repository.SeedVIPRewardGroup(1, 71003, []mysqltest.VipRewardResourceSeed{ + { + ResourceID: 7100301, + ResourceCode: "vip_grant_test_frame", + ResourceType: resourcedomain.TypeAvatarFrame, + Name: "VIP Grant Test Frame", + GrantStrategy: resourcedomain.GrantStrategyExtendExpiry, + }, + }) svc := walletservice.New(repository) adminGrant, err := svc.GrantVip(context.Background(), ledger.GrantVipCommand{ @@ -6376,7 +6530,7 @@ func TestVipLevelSixIgnoresLegacyRechargeThreshold(t *testing.T) { repository.SetBalance(51002, 50000) svc := walletservice.New(repository) - currentLevels, err := svc.ListAdminVipLevels(context.Background()) + _, currentLevels, err := svc.ListAdminVipLevels(context.Background()) if err != nil { t.Fatalf("ListAdminVipLevels failed: %v", err) } @@ -6404,7 +6558,7 @@ func TestVipLevelSixIgnoresLegacyRechargeThreshold(t *testing.T) { } commands = append(commands, command) } - if _, err := svc.UpdateAdminVipLevels(context.Background(), commands, 9001); err != nil { + if _, _, err := svc.UpdateAdminVipLevels(context.Background(), commands, 9001); err != nil { t.Fatalf("UpdateAdminVipLevels failed: %v", err) } repository.SetVIPLevelRechargeThreshold(6, 5000) diff --git a/services/wallet-service/internal/service/wallet/usecase/gift/service.go b/services/wallet-service/internal/service/wallet/usecase/gift/service.go index daf2619f..c794f2ab 100644 --- a/services/wallet-service/internal/service/wallet/usecase/gift/service.go +++ b/services/wallet-service/internal/service/wallet/usecase/gift/service.go @@ -5,6 +5,7 @@ import ( "context" "hyapp/pkg/appcode" + "hyapp/pkg/giftlimits" "hyapp/pkg/xerr" "hyapp/services/wallet-service/internal/domain/ledger" "hyapp/services/wallet-service/internal/service/wallet/ports" @@ -38,13 +39,13 @@ func (s *Service) DebitRobotGift(ctx context.Context, command ledger.DebitGiftCo func (s *Service) debitGift(ctx context.Context, command ledger.DebitGiftCommand) (ledger.Receipt, error) { roomIDRequired := !command.DirectGift - if command.CommandID == "" || (roomIDRequired && command.RoomID == "") || command.SenderUserID <= 0 || command.TargetUserID <= 0 || command.GiftID == "" { + if !giftlimits.ValidCommandID(command.CommandID) || (roomIDRequired && command.RoomID == "") || command.SenderUserID <= 0 || command.TargetUserID <= 0 || command.GiftID == "" { // 房间送礼必须带 RoomID,让 Room Cell、账务分录和房间 outbox 能共享同一个业务边界。 // 私聊送礼没有房间状态 owner;DirectGift 使用独立 biz_type 和 request hash 隔离幂等,因此允许 RoomID 为空。 return ledger.Receipt{}, xerr.New(xerr.InvalidArgument, "billing command is incomplete") } - if command.GiftCount <= 0 { - return ledger.Receipt{}, xerr.New(xerr.InvalidArgument, "gift_count must be positive") + if _, ok := giftlimits.WorkUnits(int64(command.GiftCount), 1); !ok { + return ledger.Receipt{}, xerr.New(xerr.InvalidArgument, "gift_count exceeds gift command limit") } if command.TargetIsHost && command.TargetHostRegionID <= 0 { // 主播周期钻石后续要按区域工资政策结算;只有 active host profile 且带区域时才允许入账。 @@ -61,18 +62,15 @@ func (s *Service) debitGift(ctx context.Context, command ledger.DebitGiftCommand // BatchDebitGift 校验多目标送礼扣费命令,并要求 repository 在同一事务中完成全部目标结算。 func (s *Service) BatchDebitGift(ctx context.Context, command ledger.BatchDebitGiftCommand) (ledger.BatchGiftReceipt, error) { - if command.CommandID == "" || command.RoomID == "" || command.SenderUserID <= 0 || command.GiftID == "" { + if !giftlimits.ValidCommandID(command.CommandID) || command.RoomID == "" || command.SenderUserID <= 0 || command.GiftID == "" { return ledger.BatchGiftReceipt{}, xerr.New(xerr.InvalidArgument, "billing command is incomplete") } - if command.GiftCount <= 0 { - return ledger.BatchGiftReceipt{}, xerr.New(xerr.InvalidArgument, "gift_count must be positive") - } if len(command.Targets) == 0 { return ledger.BatchGiftReceipt{}, xerr.New(xerr.InvalidArgument, "gift targets are required") } seen := make(map[int64]struct{}, len(command.Targets)) for _, target := range command.Targets { - if target.CommandID == "" || target.TargetUserID <= 0 { + if !giftlimits.ValidCommandID(target.CommandID) || target.TargetUserID <= 0 { return ledger.BatchGiftReceipt{}, xerr.New(xerr.InvalidArgument, "gift target is incomplete") } if _, exists := seen[target.TargetUserID]; exists { @@ -84,6 +82,11 @@ func (s *Service) BatchDebitGift(ctx context.Context, command ledger.BatchDebitG return ledger.BatchGiftReceipt{}, xerr.New(xerr.InvalidArgument, "target_host_region_id is required") } } + if _, ok := giftlimits.WorkUnits(int64(command.GiftCount), len(seen)); !ok { + // Wallet repeats the fan-out ceiling after target deduplication; gateway is not a trusted + // security boundary and internal callers must not bypass the same workload contract. + return ledger.BatchGiftReceipt{}, xerr.New(xerr.InvalidArgument, "gift_count and targets exceed gift command limit") + } if s == nil || s.repository == nil { return ledger.BatchGiftReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") } diff --git a/services/wallet-service/internal/service/wallet/usecase/gift/service_test.go b/services/wallet-service/internal/service/wallet/usecase/gift/service_test.go index 67dab08d..c141ae4a 100644 --- a/services/wallet-service/internal/service/wallet/usecase/gift/service_test.go +++ b/services/wallet-service/internal/service/wallet/usecase/gift/service_test.go @@ -2,6 +2,7 @@ package gift import ( "context" + "strings" "testing" "hyapp/pkg/xerr" @@ -10,6 +11,7 @@ import ( type fakeGiftLedgerStore struct { debitCalls int + batchCalls int lastDebit ledger.DebitGiftCommand } @@ -20,6 +22,7 @@ func (f *fakeGiftLedgerStore) DebitGift(_ context.Context, command ledger.DebitG } func (f *fakeGiftLedgerStore) BatchDebitGift(_ context.Context, _ ledger.BatchDebitGiftCommand) (ledger.BatchGiftReceipt, error) { + f.batchCalls++ return ledger.BatchGiftReceipt{}, nil } @@ -44,6 +47,38 @@ func TestDebitGiftAllowsDirectGiftWithoutRoomID(t *testing.T) { } } +func TestDebitGiftRejectsOversizedCountAndCommandIDBeforeRepository(t *testing.T) { + for _, command := range []ledger.DebitGiftCommand{ + {AppCode: "lalu", CommandID: "cmd-too-many", RoomID: "room-1", SenderUserID: 1, TargetUserID: 2, GiftID: "rose", GiftCount: 1_000}, + {AppCode: "lalu", CommandID: strings.Repeat("x", 129), RoomID: "room-1", SenderUserID: 1, TargetUserID: 2, GiftID: "rose", GiftCount: 1}, + } { + store := &fakeGiftLedgerStore{} + if _, err := New(store).DebitGift(context.Background(), command); !xerr.IsCode(err, xerr.InvalidArgument) || store.debitCalls != 0 { + t.Fatalf("invalid command reached wallet repository: command=%+v err=%v calls=%d", command, err, store.debitCalls) + } + } +} + +func TestBatchDebitGiftRejectsCountTimesTargetsAboveHardLimit(t *testing.T) { + targets := make([]ledger.DebitGiftTargetCommand, 0, 6) + for userID := int64(1); userID <= 6; userID++ { + targets = append(targets, ledger.DebitGiftTargetCommand{CommandID: "target-" + string(rune('0'+userID)), TargetUserID: userID}) + } + store := &fakeGiftLedgerStore{} + _, err := New(store).BatchDebitGift(context.Background(), ledger.BatchDebitGiftCommand{ + AppCode: "lalu", + CommandID: "batch-too-large", + RoomID: "room-1", + SenderUserID: 100, + GiftID: "rose", + GiftCount: 999, + Targets: targets, + }) + if !xerr.IsCode(err, xerr.InvalidArgument) || store.batchCalls != 0 { + t.Fatalf("oversized batch reached wallet repository: err=%v calls=%d", err, store.batchCalls) + } +} + func TestDebitGiftRequiresRoomIDForRoomGift(t *testing.T) { store := &fakeGiftLedgerStore{} svc := New(store) diff --git a/services/wallet-service/internal/service/wallet/vip.go b/services/wallet-service/internal/service/wallet/vip.go index c2995c45..751fe8d4 100644 --- a/services/wallet-service/internal/service/wallet/vip.go +++ b/services/wallet-service/internal/service/wallet/vip.go @@ -8,13 +8,13 @@ import ( "strings" ) -// ListVipPackages 返回可购买 VIP 包和当前会员状态。 -func (s *Service) ListVipPackages(ctx context.Context, userID int64) (ledger.UserVip, []ledger.VipLevel, error) { +// ListVipPackages 返回同一配置快照下的最终 VIP 状态和可购买套餐。 +func (s *Service) ListVipPackages(ctx context.Context, userID int64) (ledger.VipState, []ledger.VipLevel, error) { if userID <= 0 { - return ledger.UserVip{}, nil, xerr.New(xerr.InvalidArgument, "user_id is required") + return ledger.VipState{}, nil, xerr.New(xerr.InvalidArgument, "user_id is required") } if s.repository == nil { - return ledger.UserVip{}, nil, xerr.New(xerr.Unavailable, "wallet repository is not configured") + return ledger.VipState{}, nil, xerr.New(xerr.Unavailable, "wallet repository is not configured") } ctx = appcode.WithContext(ctx, appcode.FromContext(ctx)) return s.repository.ListVipPackages(ctx, userID) @@ -32,11 +32,50 @@ func (s *Service) GetMyVip(ctx context.Context, userID int64) (ledger.UserVip, e return s.repository.GetMyVip(ctx, userID) } +// GetVipProgramConfig 返回当前 App 的完整 VIP 规则和权益目录;不存在配置时直接失败, +// 避免业务在缺少迁移或错误 app_code 时悄悄回退到另一套体系。 +func (s *Service) GetVipProgramConfig(ctx context.Context) (ledger.VipProgramConfig, []ledger.VipBenefit, error) { + if s.repository == nil { + return ledger.VipProgramConfig{}, nil, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + ctx = appcode.WithContext(ctx, appcode.FromContext(ctx)) + return s.repository.GetVipProgramConfig(ctx) +} + +// GetVipState 统一返回付费、体验卡和最终生效状态,客户端与 owner service 都不能自行按等级合并。 +func (s *Service) GetVipState(ctx context.Context, userID int64) (ledger.VipState, error) { + if userID <= 0 { + return ledger.VipState{}, xerr.New(xerr.InvalidArgument, "user_id is required") + } + if s.repository == nil { + return ledger.VipState{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + ctx = appcode.WithContext(ctx, appcode.FromContext(ctx)) + return s.repository.GetVipState(ctx, userID) +} + +// CheckVipBenefit 是房间、用户资料和通知 owner 执行单项权益时的服务端判定入口。 +func (s *Service) CheckVipBenefit(ctx context.Context, userID int64, benefitCode string) (ledger.CheckVipBenefitResult, error) { + benefitCode = strings.ToLower(strings.TrimSpace(benefitCode)) + if userID <= 0 || benefitCode == "" { + return ledger.CheckVipBenefitResult{}, xerr.New(xerr.InvalidArgument, "user_id and benefit_code are required") + } + if s.repository == nil { + return ledger.CheckVipBenefitResult{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + ctx = appcode.WithContext(ctx, appcode.FromContext(ctx)) + return s.repository.CheckVipBenefit(ctx, userID, benefitCode) +} + // PurchaseVip 执行 VIP 购买、升级或续期;降级由 repository 在锁内按当前会员状态拒绝。 func (s *Service) PurchaseVip(ctx context.Context, command ledger.PurchaseVipCommand) (ledger.PurchaseVipReceipt, error) { + command.CommandID = strings.TrimSpace(command.CommandID) if command.CommandID == "" || command.UserID <= 0 || command.Level <= 0 { return ledger.PurchaseVipReceipt{}, xerr.New(xerr.InvalidArgument, "vip purchase command is incomplete") } + if len(command.CommandID) > 128 { + return ledger.PurchaseVipReceipt{}, xerr.New(xerr.InvalidArgument, "command_id is too long") + } if s.repository == nil { return ledger.PurchaseVipReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") } @@ -47,6 +86,7 @@ func (s *Service) PurchaseVip(ctx context.Context, command ledger.PurchaseVipCom // GrantVip 是活动和后台赠送 VIP 的统一入口;购买仍走 PurchaseVip 完成扣费。 func (s *Service) GrantVip(ctx context.Context, command ledger.GrantVipCommand) (ledger.GrantVipReceipt, error) { + command.CommandID = strings.TrimSpace(command.CommandID) if command.CommandID == "" || command.TargetUserID <= 0 || command.Level <= 0 { return ledger.GrantVipReceipt{}, xerr.New(xerr.InvalidArgument, "vip grant command is incomplete") } @@ -70,7 +110,7 @@ func (s *Service) GrantVip(ctx context.Context, command ledger.GrantVipCommand) if command.Reason == "" { command.Reason = command.GrantSource } - if len(command.Reason) > 512 { + if len(command.Reason) > 256 { return ledger.GrantVipReceipt{}, xerr.New(xerr.InvalidArgument, "reason is too long") } if s.repository == nil { @@ -81,22 +121,91 @@ func (s *Service) GrantVip(ctx context.Context, command ledger.GrantVipCommand) return s.repository.GrantVip(ctx, command) } -// ListAdminVipLevels 返回后台 VIP 配置页的 10 级配置快照。 -func (s *Service) ListAdminVipLevels(ctx context.Context) ([]ledger.VipLevel, error) { +// GrantVipTrialCard 发放独立绝对计时的背包卡片;发卡不自动佩戴,避免后台操作改变用户当前展示。 +func (s *Service) GrantVipTrialCard(ctx context.Context, command ledger.GrantVipTrialCardCommand) (ledger.GrantVipTrialCardReceipt, error) { + command.CommandID = strings.TrimSpace(command.CommandID) + command.Reason = strings.TrimSpace(command.Reason) + command.GrantSource = ledger.NormalizeVipGrantSource(command.GrantSource) + if command.CommandID == "" || command.TargetUserID <= 0 || command.Level <= 0 || command.DurationMS <= 0 { + return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.InvalidArgument, "vip trial card command is incomplete") + } + if len(command.CommandID) > 128 || len(command.Reason) > 256 || command.ResourceID < 0 { + return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.InvalidArgument, "vip trial card command is invalid") + } + switch command.GrantSource { + case ledger.VipGrantSourceAdmin, ledger.VipGrantSourceManagerCenter: + if command.OperatorUserID <= 0 { + return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.InvalidArgument, "operator_user_id is required") + } + case ledger.VipGrantSourceActivity: + if command.OperatorUserID < 0 { + return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.InvalidArgument, "operator_user_id is invalid") + } + default: + return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.InvalidArgument, "vip grant_source is invalid") + } + if command.Reason == "" { + command.Reason = command.GrantSource + } if s.repository == nil { - return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured") + return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + command.AppCode = appcode.Normalize(command.AppCode) + ctx = appcode.WithContext(ctx, command.AppCode) + return s.repository.GrantVipTrialCard(ctx, command) +} + +// EquipVipTrialCard 只切换通用装备指针,任一卡片的 effective/expires 时间都不会变化。 +func (s *Service) EquipVipTrialCard(ctx context.Context, command ledger.EquipVipTrialCardCommand) (ledger.VipTrialCard, ledger.VipState, error) { + command.RequestID = strings.TrimSpace(command.RequestID) + command.EntitlementID = strings.TrimSpace(command.EntitlementID) + if command.RequestID == "" || command.UserID <= 0 || command.EntitlementID == "" { + return ledger.VipTrialCard{}, ledger.VipState{}, xerr.New(xerr.InvalidArgument, "vip trial equip command is incomplete") + } + if len(command.RequestID) > 128 || len(command.EntitlementID) > 96 { + return ledger.VipTrialCard{}, ledger.VipState{}, xerr.New(xerr.InvalidArgument, "vip trial equip command is invalid") + } + if s.repository == nil { + return ledger.VipTrialCard{}, ledger.VipState{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + command.AppCode = appcode.Normalize(command.AppCode) + ctx = appcode.WithContext(ctx, command.AppCode) + return s.repository.EquipVipTrialCard(ctx, command) +} + +// UnequipVipTrialCard 清理当前体验卡装备记录,最终状态立即回落到仍有效的付费会员。 +func (s *Service) UnequipVipTrialCard(ctx context.Context, command ledger.UnequipVipTrialCardCommand) (bool, ledger.VipState, error) { + command.RequestID = strings.TrimSpace(command.RequestID) + if command.RequestID == "" || command.UserID <= 0 { + return false, ledger.VipState{}, xerr.New(xerr.InvalidArgument, "vip trial unequip command is incomplete") + } + if len(command.RequestID) > 128 { + return false, ledger.VipState{}, xerr.New(xerr.InvalidArgument, "request_id is too long") + } + if s.repository == nil { + return false, ledger.VipState{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + command.AppCode = appcode.Normalize(command.AppCode) + ctx = appcode.WithContext(ctx, command.AppCode) + return s.repository.UnequipVipTrialCard(ctx, command) +} + +// ListAdminVipLevels 返回后台 VIP program 与动态等级的同一数据库快照。 +func (s *Service) ListAdminVipLevels(ctx context.Context) (ledger.VipProgramConfig, []ledger.VipLevel, error) { + if s.repository == nil { + return ledger.VipProgramConfig{}, nil, xerr.New(xerr.Unavailable, "wallet repository is not configured") } ctx = appcode.WithContext(ctx, appcode.FromContext(ctx)) return s.repository.ListAdminVipLevels(ctx) } // UpdateAdminVipLevels 保存后台 VIP 等级配置;完整校验下沉到 repository,保证和购买事务一致。 -func (s *Service) UpdateAdminVipLevels(ctx context.Context, levels []ledger.AdminVipLevelCommand, operatorUserID int64) ([]ledger.VipLevel, error) { +func (s *Service) UpdateAdminVipLevels(ctx context.Context, levels []ledger.AdminVipLevelCommand, operatorUserID int64) (ledger.VipProgramConfig, []ledger.VipLevel, error) { if operatorUserID <= 0 { - return nil, xerr.New(xerr.InvalidArgument, "operator_user_id is required") + return ledger.VipProgramConfig{}, nil, xerr.New(xerr.InvalidArgument, "operator_user_id is required") } if s.repository == nil { - return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured") + return ledger.VipProgramConfig{}, nil, xerr.New(xerr.Unavailable, "wallet repository is not configured") } normalized := make([]ledger.AdminVipLevelCommand, 0, len(levels)) for _, level := range levels { @@ -107,3 +216,30 @@ func (s *Service) UpdateAdminVipLevels(ctx context.Context, levels []ledger.Admi ctx = appcode.WithContext(ctx, appcode.FromContext(ctx)) return s.repository.UpdateAdminVipLevels(ctx, normalized, operatorUserID) } + +// UpdateAdminVipProgramConfig 保存 App 级状态机配置;版本递增与字段白名单由 repository 在锁内处理。 +func (s *Service) UpdateAdminVipProgramConfig(ctx context.Context, command ledger.AdminVipProgramConfigCommand) (ledger.VipProgramConfig, error) { + if command.OperatorUserID <= 0 { + return ledger.VipProgramConfig{}, xerr.New(xerr.InvalidArgument, "operator_user_id is required") + } + if s.repository == nil { + return ledger.VipProgramConfig{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + command.Config.AppCode = appcode.Normalize(command.Config.AppCode) + ctx = appcode.WithContext(ctx, command.Config.AppCode) + return s.repository.UpdateAdminVipProgramConfig(ctx, command) +} + +// UpdateVipUserSettings 保存用户可关闭的 VIP 通知偏好。用户当前没有 VIP 也允许保存, +// 但偏好不会授予权益;实际执行仍由 CheckVipBenefit 同时校验 effective benefit。 +func (s *Service) UpdateVipUserSettings(ctx context.Context, command ledger.UpdateVipUserSettingsCommand) (ledger.VipUserSettings, error) { + if command.UserID <= 0 || (command.RoomEntryNoticeEnabled == nil && command.OnlineGlobalNoticeEnabled == nil) { + return ledger.VipUserSettings{}, xerr.New(xerr.InvalidArgument, "vip user settings update is incomplete") + } + if s.repository == nil { + return ledger.VipUserSettings{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + command.AppCode = appcode.Normalize(command.AppCode) + ctx = appcode.WithContext(ctx, command.AppCode) + return s.repository.UpdateVipUserSettings(ctx, command) +} diff --git a/services/wallet-service/internal/service/wallet/vip_configurable_integration_test.go b/services/wallet-service/internal/service/wallet/vip_configurable_integration_test.go new file mode 100644 index 00000000..016f9d05 --- /dev/null +++ b/services/wallet-service/internal/service/wallet/vip_configurable_integration_test.go @@ -0,0 +1,405 @@ +package wallet_test + +import ( + "context" + "strings" + "testing" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" + resourcedomain "hyapp/services/wallet-service/internal/domain/resource" + walletservice "hyapp/services/wallet-service/internal/service/wallet" + "hyapp/services/wallet-service/internal/testutil/mysqltest" +) + +// TestFamiVIPReplaceAndTrialCardSwitch 使用真实 MySQL repository 验证 P1 最容易回归的三条状态边界: +// 高级购买丢弃剩余时间、体验卡可跨等级自由切换且旧卡不改期、体验卡排除金币返现。 +func TestFamiVIPReplaceAndTrialCardSwitch(t *testing.T) { + repository := mysqltest.NewRepository(t) + service := walletservice.New(repository) + ctx := appcode.WithContext(context.Background(), "fami") + _, levels, err := service.ListAdminVipLevels(ctx) + if err != nil { + t.Fatalf("list Fami VIP levels failed: %v", err) + } + if len(levels) != 9 { + t.Fatalf("Fami must expose 9 configurable levels, got %d", len(levels)) + } + durationMS := int64(30 * 24 * time.Hour / time.Millisecond) + commands := make([]ledger.AdminVipLevelCommand, 0, len(levels)) + for _, level := range levels { + for index := range level.Benefits { + if level.Benefits[index].BenefitCode == ledger.VipBenefitCodeDailyCoinRebate { + // 模拟后台误把体验返现打开;保存与运行时都必须把这项硬规则收敛为 false。 + level.Benefits[index].TrialEnabled = true + level.Benefits[index].Status = ledger.VipStatusActive + level.Benefits[index].MetadataJSON = `{"coin_amount":100}` + } + } + commands = append(commands, ledger.AdminVipLevelCommand{ + Level: level.Level, + Name: level.Name, + Status: ledger.VipStatusActive, + PriceCoin: 100, + DurationMS: durationMS, + RewardResourceGroupID: 0, + SortOrder: level.SortOrder, + Benefits: level.Benefits, + }) + } + if _, _, err := service.UpdateAdminVipLevels(ctx, commands, 9001); err != nil { + t.Fatalf("enable Fami VIP levels failed: %v", err) + } + + const userID int64 = 880001 + repository.SetBalanceForApp("fami", userID, 10_000) + first, err := service.PurchaseVip(ctx, ledger.PurchaseVipCommand{AppCode: "fami", CommandID: "fami-vip-3", UserID: userID, Level: 3}) + if err != nil { + t.Fatalf("purchase Fami VIP3 failed: %v", err) + } + // command_id 与 user_id 只在 app_code 内幂等;Lalu 使用相同值必须形成独立订单和状态。 + laluCtx := appcode.WithContext(context.Background(), "lalu") + repository.SetBalanceForApp("lalu", userID, 10_000) + laluPurchase, err := service.PurchaseVip(laluCtx, ledger.PurchaseVipCommand{AppCode: "lalu", CommandID: "fami-vip-3", UserID: userID, Level: 3}) + if err != nil { + t.Fatalf("same command in Lalu app scope failed: %v", err) + } + if laluPurchase.State.ProgramConfig.ProgramType != ledger.VipProgramTypeLegacyTimed || laluPurchase.OrderID == first.OrderID { + t.Fatalf("VIP purchase must stay app-scoped: fami=%+v lalu=%+v", first, laluPurchase) + } + upgrade, err := service.PurchaseVip(ctx, ledger.PurchaseVipCommand{AppCode: "fami", CommandID: "fami-vip-4", UserID: userID, Level: 4}) + if err != nil { + t.Fatalf("upgrade Fami VIP4 failed: %v", err) + } + if upgrade.Vip.ExpiresAtMS >= first.Vip.ExpiresAtMS+durationMS/2 { + t.Fatalf("Fami upgrade incorrectly retained VIP3 remainder: first=%d upgrade=%d", first.Vip.ExpiresAtMS, upgrade.Vip.ExpiresAtMS) + } + if got := upgrade.Vip.ExpiresAtMS - upgrade.Vip.UpdatedAtMS; got < durationMS-2_000 || got > durationMS+2_000 { + t.Fatalf("Fami upgrade must reset to one configured duration, got %d want %d", got, durationMS) + } + replayedFirst, err := service.PurchaseVip(ctx, ledger.PurchaseVipCommand{AppCode: "fami", CommandID: "fami-vip-3", UserID: userID, Level: 3}) + if err != nil { + t.Fatalf("replay original VIP3 purchase failed: %v", err) + } + if replayedFirst.Vip.Level != 3 || replayedFirst.Vip.ExpiresAtMS != first.Vip.ExpiresAtMS || replayedFirst.State.PaidVip.Level != 4 { + t.Fatalf("idempotent receipt must keep original order snapshot and current state separately: first=%+v replay=%+v", first, replayedFirst) + } + paid, err := service.PurchaseVip(ctx, ledger.PurchaseVipCommand{AppCode: "fami", CommandID: "fami-vip-6", UserID: userID, Level: 6}) + if err != nil { + t.Fatalf("upgrade Fami VIP6 failed: %v", err) + } + if paid.State.PaidVip.Level != 6 { + t.Fatalf("paid VIP state mismatch: %+v", paid.State) + } + + card3, err := service.GrantVipTrialCard(ctx, ledger.GrantVipTrialCardCommand{ + AppCode: "fami", CommandID: "trial-vip-3", TargetUserID: userID, Level: 3, + DurationMS: int64(14 * 24 * time.Hour / time.Millisecond), GrantSource: ledger.VipGrantSourceAdmin, + OperatorUserID: 9001, Reason: "test trial 3", + }) + if err != nil { + t.Fatalf("grant VIP3 trial card failed: %v", err) + } + card6, err := service.GrantVipTrialCard(ctx, ledger.GrantVipTrialCardCommand{ + AppCode: "fami", CommandID: "trial-vip-6", TargetUserID: userID, Level: 6, + DurationMS: int64(20 * 24 * time.Hour / time.Millisecond), GrantSource: ledger.VipGrantSourceAdmin, + OperatorUserID: 9001, Reason: "test trial 6", + }) + if err != nil { + t.Fatalf("grant VIP6 trial card failed: %v", err) + } + if _, err := service.GrantResource(ctx, resourcedomain.GrantResourceCommand{ + AppCode: "fami", CommandID: "generic-trial-bypass", TargetUserID: userID, + ResourceID: card6.TrialCard.ResourceID, Quantity: 1, DurationMS: durationMS, + Reason: "must use dedicated vip trial grant", OperatorUserID: 9001, GrantSource: resourcedomain.GrantSourceAdmin, + }); !xerr.IsCode(err, xerr.InvalidArgument) { + t.Fatalf("generic resource grant must not bypass VIP trial metadata, got %v", err) + } + if card3.State.EffectiveSource != ledger.VipEffectiveSourcePaid || card6.State.EffectiveSource != ledger.VipEffectiveSourcePaid { + t.Fatalf("granting a card must not auto-equip it: card3=%+v card6=%+v", card3.State, card6.State) + } + + // repository 仍保留通用 equipment 能力供专用状态机内部复用,但必须拒绝外层直接调用,避免绕过 program 与 VIP outbox。 + if _, err := repository.EquipUserResource(ctx, resourcedomain.EquipUserResourceCommand{ + AppCode: "fami", RequestID: "raw-equip-trial-3", UserID: userID, + ResourceID: card3.TrialCard.ResourceID, EntitlementID: card3.TrialCard.EntitlementID, + }); !xerr.IsCode(err, xerr.InvalidArgument) || !strings.Contains(err.Error(), "VIP trial-card state machine") { + t.Fatalf("raw repository equip must reject vip_trial_card bypass, got %v", err) + } + if _, err := repository.UnequipUserResource(ctx, resourcedomain.UnequipUserResourceCommand{ + AppCode: "fami", RequestID: "raw-unequip-trial", UserID: userID, ResourceType: resourcedomain.TypeVIPTrialCard, + }); !xerr.IsCode(err, xerr.InvalidArgument) || !strings.Contains(err.Error(), "VIP trial-card state machine") { + t.Fatalf("raw repository unequip must reject vip_trial_card bypass, got %v", err) + } + + // 通用背包入口必须委托专用状态机,因此体验卡开关关闭时不能偷偷写入 equipment。 + disabledProgram := card3.State.ProgramConfig + disabledProgram.GrantMode = ledger.VipGrantModeDirectMembership + disabledProgram.TrialCardEnabled = false + if _, err := service.UpdateAdminVipProgramConfig(ctx, ledger.AdminVipProgramConfigCommand{Config: disabledProgram, OperatorUserID: 9001}); err != nil { + t.Fatalf("disable trial cards before generic equip failed: %v", err) + } + if _, err := service.EquipUserResource(ctx, resourcedomain.EquipUserResourceCommand{ + AppCode: "fami", RequestID: "generic-equip-disabled", UserID: userID, + ResourceID: card3.TrialCard.ResourceID, EntitlementID: card3.TrialCard.EntitlementID, + }); !xerr.IsCode(err, xerr.Conflict) { + t.Fatalf("generic backpack equip must preserve trial_card_enabled validation, got %v", err) + } + if got := repository.CountRows("user_resource_equipment", "app_code = ? AND user_id = ? AND resource_type = ?", "fami", userID, resourcedomain.TypeVIPTrialCard); got != 0 { + t.Fatalf("disabled generic trial equip must not mutate equipment, got %d rows", got) + } + disabledProgram.GrantMode = ledger.VipGrantModeTrialCard + disabledProgram.TrialCardEnabled = true + if _, err := service.UpdateAdminVipProgramConfig(ctx, ledger.AdminVipProgramConfigCommand{Config: disabledProgram, OperatorUserID: 9001}); err != nil { + t.Fatalf("restore trial cards before generic equip failed: %v", err) + } + + resourceEventsBefore := repository.CountRows("wallet_outbox", "app_code = ? AND user_id = ? AND event_type = ?", "fami", userID, "UserResourceChanged") + vipEventsBefore := repository.CountRows("wallet_outbox", "app_code = ? AND user_id = ? AND event_type = ?", "fami", userID, "VipEffectiveChanged") + genericCard3, err := service.EquipUserResource(ctx, resourcedomain.EquipUserResourceCommand{ + AppCode: "fami", RequestID: "generic-equip-trial-3", UserID: userID, + ResourceID: card3.TrialCard.ResourceID, EntitlementID: card3.TrialCard.EntitlementID, + }) + if err != nil { + t.Fatalf("generic backpack equip VIP3 trial card failed: %v", err) + } + if !genericCard3.Equipped || genericCard3.EntitlementID != card3.TrialCard.EntitlementID { + t.Fatalf("generic backpack response must return the selected equipped entitlement: %+v", genericCard3) + } + if got := repository.CountRows("wallet_outbox", "app_code = ? AND user_id = ? AND event_type = ?", "fami", userID, "UserResourceChanged"); got != resourceEventsBefore+1 { + t.Fatalf("generic trial equip must publish UserResourceChanged exactly once: before=%d after=%d", resourceEventsBefore, got) + } + if got := repository.CountRows("wallet_outbox", "app_code = ? AND user_id = ? AND event_type = ?", "fami", userID, "VipEffectiveChanged"); got != vipEventsBefore+1 { + t.Fatalf("generic trial equip must publish VipEffectiveChanged exactly once: before=%d after=%d", vipEventsBefore, got) + } + state3, err := service.GetVipState(ctx, userID) + if err != nil { + t.Fatalf("get state after generic VIP3 trial equip failed: %v", err) + } + if state3.EffectiveSource != ledger.VipEffectiveSourceTrial || state3.EffectiveVip.Level != 3 || state3.PaidVip.Level != 6 { + t.Fatalf("lower trial card must freely overlay paid VIP without destroying it: %+v", state3) + } + _, err = service.EquipUserResource(ctx, resourcedomain.EquipUserResourceCommand{ + AppCode: "fami", RequestID: "generic-equip-trial-6", UserID: userID, + ResourceID: card6.TrialCard.ResourceID, EntitlementID: card6.TrialCard.EntitlementID, + }) + if err != nil { + t.Fatalf("generic backpack switch to VIP6 trial card failed: %v", err) + } + state6, err := service.GetVipState(ctx, userID) + if err != nil { + t.Fatalf("get state after generic VIP6 trial equip failed: %v", err) + } + if state6.EffectiveVip.Level != 6 || state6.EquippedTrialCard == nil || state6.EquippedTrialCard.EntitlementID != card6.TrialCard.EntitlementID { + t.Fatalf("trial card switch mismatch: %+v", state6) + } + card3Again, _, err := service.EquipVipTrialCard(ctx, ledger.EquipVipTrialCardCommand{ + AppCode: "fami", RequestID: "equip-trial-3-again", UserID: userID, EntitlementID: card3.TrialCard.EntitlementID, + }) + if err != nil { + t.Fatalf("re-equip VIP3 trial card failed: %v", err) + } + if card3Again.ExpiresAtMS != card3.TrialCard.ExpiresAtMS { + t.Fatalf("switching cards must preserve the old card absolute expiry: first=%d again=%d", card3.TrialCard.ExpiresAtMS, card3Again.ExpiresAtMS) + } + if _, _, err := service.EquipVipTrialCard(ctx, ledger.EquipVipTrialCardCommand{ + AppCode: "fami", RequestID: "equip-trial-6-again", UserID: userID, EntitlementID: card6.TrialCard.EntitlementID, + }); err != nil { + t.Fatalf("re-equip VIP6 trial card failed: %v", err) + } + trialRebate, err := service.CheckVipBenefit(ctx, userID, ledger.VipBenefitCodeDailyCoinRebate) + if err != nil { + t.Fatalf("check trial rebate failed: %v", err) + } + if trialRebate.Allowed { + t.Fatalf("trial card must not receive daily coin rebate: %+v", trialRebate) + } + // App 级体验卡开关只控制是否参与 effective 合并,不能删除用户背包事实;关闭后应立即回落付费 VIP。 + trialProgram := trialRebate.State.ProgramConfig + trialProgram.GrantMode = ledger.VipGrantModeDirectMembership + trialProgram.TrialCardEnabled = false + if _, err := service.UpdateAdminVipProgramConfig(ctx, ledger.AdminVipProgramConfigCommand{Config: trialProgram, OperatorUserID: 9001}); err != nil { + t.Fatalf("disable Fami trial-card program failed: %v", err) + } + trialOffState, err := service.GetVipState(ctx, userID) + if err != nil { + t.Fatalf("get VIP state with trial cards disabled failed: %v", err) + } + if trialOffState.EquippedTrialCard == nil || trialOffState.EffectiveSource != ledger.VipEffectiveSourcePaid || trialOffState.EffectiveVip.Level != 6 { + t.Fatalf("disabled trial cards must remain raw inventory and fall back to paid VIP: %+v", trialOffState) + } + trialProgram.GrantMode = ledger.VipGrantModeTrialCard + trialProgram.TrialCardEnabled = true + if _, err := service.UpdateAdminVipProgramConfig(ctx, ledger.AdminVipProgramConfigCommand{Config: trialProgram, OperatorUserID: 9001}); err != nil { + t.Fatalf("restore Fami trial-card program failed: %v", err) + } + restoredTrialState, err := service.GetVipState(ctx, userID) + if err != nil || restoredTrialState.EffectiveSource != ledger.VipEffectiveSourceTrial || restoredTrialState.EquippedTrialCard == nil { + t.Fatalf("restored trial-card switch must reuse the original equipped card: state=%+v err=%v", restoredTrialState, err) + } + programOff := restoredTrialState.ProgramConfig + programOff.Status = ledger.VipStatusDisabled + if _, err := service.UpdateAdminVipProgramConfig(ctx, ledger.AdminVipProgramConfigCommand{Config: programOff, OperatorUserID: 9001}); err != nil { + t.Fatalf("disable Fami VIP program failed: %v", err) + } + disabledState, err := service.GetVipState(ctx, userID) + if err != nil || disabledState.PaidVip.Level != 6 || disabledState.EquippedTrialCard == nil || disabledState.EffectiveSource != ledger.VipEffectiveSourceNone || disabledState.EffectiveVip.Active { + t.Fatalf("disabled program must expose raw facts without authorizing VIP: state=%+v err=%v", disabledState, err) + } + programOff.Status = ledger.VipStatusActive + if _, err := service.UpdateAdminVipProgramConfig(ctx, ledger.AdminVipProgramConfigCommand{Config: programOff, OperatorUserID: 9001}); err != nil { + t.Fatalf("restore Fami VIP program failed: %v", err) + } + unequipResult, err := service.UnequipUserResource(ctx, resourcedomain.UnequipUserResourceCommand{ + AppCode: "fami", RequestID: "generic-unequip-trial", UserID: userID, ResourceType: resourcedomain.TypeVIPTrialCard, + }) + unequipped := unequipResult.Unequipped + if err != nil || !unequipped { + t.Fatalf("generic backpack unequip trial card failed: result=%+v err=%v", unequipResult, err) + } + paidState, err := service.GetVipState(ctx, userID) + if err != nil { + t.Fatalf("get state after generic trial unequip failed: %v", err) + } + if paidState.EffectiveSource != ledger.VipEffectiveSourcePaid || paidState.EffectiveVip.Level != 6 { + t.Fatalf("unequip must fall back to paid VIP6: %+v", paidState) + } + paidRebate, err := service.CheckVipBenefit(ctx, userID, ledger.VipBenefitCodeDailyCoinRebate) + if err != nil || !paidRebate.Allowed { + t.Fatalf("paid VIP6 must retain configured rebate benefit: allowed=%v err=%v result=%+v", paidRebate.Allowed, err, paidRebate) + } +} + +// TestVipPurchaseConcurrentReplayWithExactBalance 验证幂等守卫发生在账户/会员锁之前: +// 两个并发首发即使余额只够一单,也都返回同一成功回执,而不是让后到请求误报余额不足。 +func TestVipPurchaseConcurrentReplayWithExactBalance(t *testing.T) { + repository := mysqltest.NewRepository(t) + service := walletservice.New(repository) + ctx := appcode.WithContext(context.Background(), "lalu") + const userID int64 = 880003 + const commandID = "vip-concurrent-exact-balance" + repository.SetBalanceForApp("lalu", userID, 1_000) + + type purchaseResult struct { + receipt ledger.PurchaseVipReceipt + err error + } + start := make(chan struct{}) + results := make(chan purchaseResult, 2) + for range 2 { + go func() { + <-start + receipt, err := service.PurchaseVip(ctx, ledger.PurchaseVipCommand{ + AppCode: "lalu", CommandID: commandID, UserID: userID, Level: 1, + }) + results <- purchaseResult{receipt: receipt, err: err} + }() + } + close(start) + first := <-results + second := <-results + if first.err != nil || second.err != nil { + t.Fatalf("concurrent VIP replay must return two successes: first=%v second=%v", first.err, second.err) + } + if first.receipt.OrderID == "" || first.receipt.OrderID != second.receipt.OrderID || first.receipt.TransactionID != second.receipt.TransactionID { + t.Fatalf("concurrent VIP replay returned different receipts: first=%+v second=%+v", first.receipt, second.receipt) + } + if first.receipt.CoinBalanceAfter != 0 || second.receipt.CoinBalanceAfter != 0 { + t.Fatalf("idempotent purchase must debit exact balance once: first=%d second=%d", first.receipt.CoinBalanceAfter, second.receipt.CoinBalanceAfter) + } + if got := repository.CountRows("vip_purchase_orders", "app_code = ? AND command_id = ?", "lalu", commandID); got != 1 { + t.Fatalf("concurrent replay persisted %d VIP orders, want 1", got) + } + if got := repository.CountRows("vip_command_locks", "app_code = ? AND command_id = ?", "lalu", commandID); got != 1 { + t.Fatalf("concurrent replay persisted %d VIP command guards, want 1", got) + } +} + +// TestVipConfigurationListsDoNotExhaustPool 覆盖曾经的连接池自锁:主 levels rows 未关闭时, +// 每个请求再从 r.db 借连接查权益,20 个并发会占满池后相互等待。当前实现先关闭游标并在原事务内装配。 +func TestVipConfigurationListsDoNotExhaustPool(t *testing.T) { + repository := mysqltest.NewRepository(t) + service := walletservice.New(repository) + base := appcode.WithContext(context.Background(), "fami") + ctx, cancel := context.WithTimeout(base, 5*time.Second) + defer cancel() + + start := make(chan struct{}) + results := make(chan error, 24) + for index := range 24 { + go func(index int) { + <-start + if index%2 == 0 { + _, _, err := service.ListAdminVipLevels(ctx) + results <- err + return + } + _, _, err := service.ListVipPackages(ctx, int64(990000+index)) + results <- err + }(index) + } + close(start) + for range 24 { + if err := <-results; err != nil { + t.Fatalf("concurrent VIP list failed before pool drain: %v", err) + } + } +} + +func TestVipProgramCanCreateAndResizeAppLevels(t *testing.T) { + repository := mysqltest.NewRepository(t) + service := walletservice.New(repository) + ctx := appcode.WithContext(context.Background(), "future_app") + base := ledger.VipProgramConfig{ + AppCode: "future_app", + ProgramType: ledger.VipProgramTypeTieredPrivilegeV1, + LevelCount: 2, + SameLevelExpiryPolicy: ledger.VipExpiryPolicyExtendRemaining, + UpgradeExpiryPolicy: ledger.VipExpiryPolicyReplaceFromNow, + DowngradePurchasePolicy: ledger.VipDowngradePurchasePolicyReject, + BenefitInheritancePolicy: ledger.VipBenefitInheritanceTargetOnly, + GrantMode: ledger.VipGrantModeTrialCard, + TrialCardEnabled: true, + Status: ledger.VipStatusActive, + } + created, err := service.UpdateAdminVipProgramConfig(ctx, ledger.AdminVipProgramConfigCommand{Config: base, OperatorUserID: 9001}) + if err != nil { + t.Fatalf("create VIP program for a new app failed: %v", err) + } + if created.ConfigVersion != 1 { + t.Fatalf("new VIP program version mismatch: %+v", created) + } + _, levels, err := service.ListAdminVipLevels(ctx) + if err != nil || len(levels) != 2 || levels[0].Status != ledger.VipStatusDisabled || levels[1].DurationMS <= 0 { + t.Fatalf("new program must materialize disabled structural levels: levels=%+v err=%v", levels, err) + } + base.LevelCount = 3 + if _, err := service.UpdateAdminVipProgramConfig(ctx, ledger.AdminVipProgramConfigCommand{Config: base, OperatorUserID: 9001}); err != nil { + t.Fatalf("grow VIP program level_count failed: %v", err) + } + _, levels, err = service.ListAdminVipLevels(ctx) + if err != nil || len(levels) != 3 || levels[2].Level != 3 || levels[2].Status != ledger.VipStatusDisabled { + t.Fatalf("grown program missing disabled placeholder: levels=%+v err=%v", levels, err) + } + commands := make([]ledger.AdminVipLevelCommand, 0, 3) + for _, level := range levels { + commands = append(commands, ledger.AdminVipLevelCommand{ + Level: level.Level, Name: level.Name, Status: ledger.VipStatusActive, + PriceCoin: 100, DurationMS: level.DurationMS, SortOrder: level.SortOrder, + }) + } + if _, _, err := service.UpdateAdminVipLevels(ctx, commands, 9001); err != nil { + t.Fatalf("activate grown VIP levels failed: %v", err) + } + base.LevelCount = 2 + if _, err := service.UpdateAdminVipProgramConfig(ctx, ledger.AdminVipProgramConfigCommand{Config: base, OperatorUserID: 9001}); err != nil { + t.Fatalf("shrink VIP program level_count failed: %v", err) + } + repository.SetBalanceForApp("future_app", 880002, 1_000) + _, err = service.PurchaseVip(ctx, ledger.PurchaseVipCommand{AppCode: "future_app", CommandID: "buy-hidden-level", UserID: 880002, Level: 3}) + if !xerr.IsCode(err, xerr.VIPLevelNotFound) { + t.Fatalf("level above current level_count must be unreachable, got %v", err) + } +} diff --git a/services/wallet-service/internal/service/wallet/vip_daily_coin_rebate.go b/services/wallet-service/internal/service/wallet/vip_daily_coin_rebate.go new file mode 100644 index 00000000..d243233b --- /dev/null +++ b/services/wallet-service/internal/service/wallet/vip_daily_coin_rebate.go @@ -0,0 +1,116 @@ +package wallet + +import ( + "context" + "strings" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +const vipDailyCoinRebateTaskDayLayout = "2006-01-02" + +// ProcessVipDailyCoinRebateBatch 只接受 UTC 日期,不接受时区偏移。空 task_day 使用 wallet owner +// 当前 UTC 日期,使调度方无需也不能自行决定业务切日。 +func (s *Service) ProcessVipDailyCoinRebateBatch(ctx context.Context, command ledger.ProcessVipDailyCoinRebateBatchCommand) (ledger.ProcessVipDailyCoinRebateBatchResult, error) { + if s.repository == nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + command.AppCode = appcode.Normalize(command.AppCode) + now := s.now().UTC() + command.NowMS = now.UnixMilli() + command.TaskDay = strings.TrimSpace(command.TaskDay) + if command.TaskDay == "" { + command.TaskDay = now.Format(vipDailyCoinRebateTaskDayLayout) + } + dayStart, err := time.ParseInLocation(vipDailyCoinRebateTaskDayLayout, command.TaskDay, time.UTC) + if err != nil || dayStart.Format(vipDailyCoinRebateTaskDayLayout) != command.TaskDay { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, xerr.New(xerr.InvalidArgument, "task_day must be an UTC date in YYYY-MM-DD") + } + if dayStart.After(now) { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, xerr.New(xerr.InvalidArgument, "future vip rebate task_day is not allowed") + } + if command.BatchSize <= 0 { + command.BatchSize = 100 + } + if command.BatchSize > 500 { + command.BatchSize = 500 + } + ctx = appcode.WithContext(ctx, command.AppCode) + return s.repository.ProcessVipDailyCoinRebateBatch(ctx, command) +} + +// GetMyCurrentVipDailyCoinRebate 查询当前 UTC 日返现;没有记录是正常状态,以 found=false 表达。 +func (s *Service) GetMyCurrentVipDailyCoinRebate(ctx context.Context, userID int64) (ledger.VipDailyCoinRebate, bool, error) { + if userID <= 0 { + return ledger.VipDailyCoinRebate{}, false, xerr.New(xerr.InvalidArgument, "user_id is required") + } + if s.repository == nil { + return ledger.VipDailyCoinRebate{}, false, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + nowMS := s.now().UTC().UnixMilli() + ctx = appcode.WithContext(ctx, appcode.FromContext(ctx)) + return s.repository.GetMyCurrentVipDailyCoinRebate(ctx, userID, nowMS) +} + +// ListMyVipDailyCoinRebateStatuses 返回用户历史记录;expired 由 wallet owner 使用当前 UTC 时钟投影。 +func (s *Service) ListMyVipDailyCoinRebateStatuses(ctx context.Context, query ledger.ListVipDailyCoinRebateStatusesQuery) ([]ledger.VipDailyCoinRebate, int64, error) { + if query.UserID <= 0 { + return nil, 0, xerr.New(xerr.InvalidArgument, "user_id is required") + } + if s.repository == nil { + return nil, 0, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + query.AppCode = appcode.Normalize(query.AppCode) + if query.Page <= 0 { + query.Page = 1 + } + if query.PageSize <= 0 { + query.PageSize = 20 + } + if query.PageSize > 100 { + query.PageSize = 100 + } + if len(query.RebateIDs) > 100 { + return nil, 0, xerr.New(xerr.InvalidArgument, "rebate_ids cannot contain more than 100 items") + } + seenRebateIDs := make(map[string]struct{}, len(query.RebateIDs)) + normalizedRebateIDs := make([]string, 0, len(query.RebateIDs)) + for _, rebateID := range query.RebateIDs { + rebateID = strings.TrimSpace(rebateID) + if rebateID == "" || len(rebateID) > 96 { + return nil, 0, xerr.New(xerr.InvalidArgument, "rebate_ids contains an invalid id") + } + if _, exists := seenRebateIDs[rebateID]; exists { + continue + } + seenRebateIDs[rebateID] = struct{}{} + normalizedRebateIDs = append(normalizedRebateIDs, rebateID) + } + query.RebateIDs = normalizedRebateIDs + query.NowMS = s.now().UTC().UnixMilli() + ctx = appcode.WithContext(ctx, query.AppCode) + return s.repository.ListMyVipDailyCoinRebateStatuses(ctx, query) +} + +// ClaimVipDailyCoinRebate 只校验命令形状;可领取窗口、金额、付费 VIP 快照和余额变更均在 +// repository 的同一行锁事务内裁决,客户端无法提交金额或延长过期时间。 +func (s *Service) ClaimVipDailyCoinRebate(ctx context.Context, command ledger.ClaimVipDailyCoinRebateCommand) (ledger.ClaimVipDailyCoinRebateReceipt, error) { + command.AppCode = appcode.Normalize(command.AppCode) + command.CommandID = strings.TrimSpace(command.CommandID) + command.RebateID = strings.TrimSpace(command.RebateID) + if command.CommandID == "" || command.UserID <= 0 || command.RebateID == "" { + return ledger.ClaimVipDailyCoinRebateReceipt{}, xerr.New(xerr.InvalidArgument, "vip daily coin rebate claim command is incomplete") + } + if len(command.CommandID) > 128 || len(command.RebateID) > 96 { + return ledger.ClaimVipDailyCoinRebateReceipt{}, xerr.New(xerr.InvalidArgument, "vip daily coin rebate claim command is invalid") + } + if s.repository == nil { + return ledger.ClaimVipDailyCoinRebateReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + command.NowMS = s.now().UTC().UnixMilli() + ctx = appcode.WithContext(ctx, command.AppCode) + return s.repository.ClaimVipDailyCoinRebate(ctx, command) +} diff --git a/services/wallet-service/internal/service/wallet/vip_daily_coin_rebate_integration_test.go b/services/wallet-service/internal/service/wallet/vip_daily_coin_rebate_integration_test.go new file mode 100644 index 00000000..c01c2f3e --- /dev/null +++ b/services/wallet-service/internal/service/wallet/vip_daily_coin_rebate_integration_test.go @@ -0,0 +1,421 @@ +package wallet_test + +import ( + "context" + "encoding/json" + "strings" + "testing" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" + walletservice "hyapp/services/wallet-service/internal/service/wallet" + "hyapp/services/wallet-service/internal/testutil/mysqltest" +) + +// TestVipDailyCoinRebateUTCWindowSnapshotAndClaim 覆盖返现最关键的不可变边界: +// UTC 日切资格、run 配置快照、分页重放、App 隔离、available outbox 和并发领取仅入账一次。 +func TestVipDailyCoinRebateUTCWindowSnapshotAndClaim(t *testing.T) { + repository := mysqltest.NewRepository(t) + service := walletservice.New(repository) + now := time.Now().UTC() + dayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC) + dayStartMS := dayStart.UnixMilli() + dayEndMS := dayStart.Add(24 * time.Hour).UnixMilli() + taskDay := dayStart.Format("2006-01-02") + famiCtx := appcode.WithContext(context.Background(), "fami") + + repository.SetVIPDailyCoinRebateAmount("fami", 6, 60, ledger.VipStatusActive) + repository.SetVIPDailyCoinRebateAmount("fami", 7, 70, ledger.VipStatusActive) + // started==day_start 与 expires>day_start 均为有效;开始晚 1ms、到期恰好落在 day_start 均无资格。 + repository.SeedPaidVIPAtWindow("fami", 910001, 6, dayStartMS, dayEndMS) + repository.SeedPaidVIPAtWindow("fami", 910002, 6, dayStartMS+1, dayEndMS) + repository.SeedPaidVIPAtWindow("fami", 910003, 6, dayStartMS-1000, dayStartMS) + repository.SeedPaidVIPAtWindow("fami", 910004, 7, dayStartMS-1000, dayStartMS+1) + + firstPage, err := service.ProcessVipDailyCoinRebateBatch(famiCtx, ledger.ProcessVipDailyCoinRebateBatchCommand{ + AppCode: "fami", TaskDay: taskDay, BatchSize: 1, + }) + if err != nil { + t.Fatalf("process first VIP rebate page failed: %v", err) + } + if firstPage.CreatedCount != 1 || !firstPage.HasMore || firstPage.Run.LevelAmounts[6] != 60 || firstPage.Run.LevelAmounts[7] != 70 { + t.Fatalf("first VIP rebate page snapshot mismatch: %+v", firstPage) + } + initialConfigVersion := firstPage.Run.ConfigVersion + // run 创建后改价不能污染剩余页;VIP7 仍必须按 70 生成,而不是新值 700。 + repository.SetVIPDailyCoinRebateAmount("fami", 7, 700, ledger.VipStatusActive) + var completed ledger.ProcessVipDailyCoinRebateBatchResult + createdAfterConfigChange := 0 + for page := 0; page < 10; page++ { + current, pageErr := service.ProcessVipDailyCoinRebateBatch(famiCtx, ledger.ProcessVipDailyCoinRebateBatchCommand{ + AppCode: "fami", TaskDay: taskDay, BatchSize: 1, + }) + if pageErr != nil { + t.Fatalf("process remaining VIP rebate page failed: %v", pageErr) + } + if current.Run.ConfigVersion != initialConfigVersion || current.Run.LevelAmounts[7] != 70 { + t.Fatalf("existing run must keep original config snapshot: first=%+v current=%+v", firstPage.Run, current.Run) + } + createdAfterConfigChange += current.CreatedCount + completed = current + if !current.HasMore { + break + } + } + if createdAfterConfigChange != 1 || completed.HasMore || completed.Run.Status != ledger.VipDailyCoinRebateRunStatusCompleted { + t.Fatalf("complete VIP rebate run failed: created=%d result=%+v", createdAfterConfigChange, completed) + } + // 完成后任意重放都只返回已有 run,不重复返现或 outbox。 + replayed, err := service.ProcessVipDailyCoinRebateBatch(famiCtx, ledger.ProcessVipDailyCoinRebateBatchCommand{ + AppCode: "fami", TaskDay: taskDay, BatchSize: 500, + }) + if err != nil || replayed.CreatedCount != 0 || replayed.HasMore { + t.Fatalf("completed VIP rebate replay must be a no-op: result=%+v err=%v", replayed, err) + } + if got := repository.CountRows("vip_daily_coin_rebates", "app_code = ? AND task_day = ?", "fami", taskDay); got != 2 { + t.Fatalf("UTC boundary must create exactly two Fami rebates, got %d", got) + } + if got := repository.CountRows("wallet_outbox", "app_code = ? AND event_type = ?", "fami", "VipDailyCoinRebateAvailable"); got != 2 { + t.Fatalf("available outbox must be one-to-one with rebates, got %d", got) + } + + rebate6, found, err := service.GetMyCurrentVipDailyCoinRebate(famiCtx, 910001) + if err != nil || !found || rebate6.CoinAmount != 60 || rebate6.AvailableAtMS != dayStartMS || rebate6.ExpiresAtMS != dayEndMS { + t.Fatalf("current VIP6 rebate mismatch: found=%v rebate=%+v err=%v", found, rebate6, err) + } + rebate7, found, err := service.GetMyCurrentVipDailyCoinRebate(famiCtx, 910004) + if err != nil || !found || rebate7.CoinAmount != 70 { + t.Fatalf("current VIP7 rebate must use run snapshot: found=%v rebate=%+v err=%v", found, rebate7, err) + } + if _, found, err := service.GetMyCurrentVipDailyCoinRebate(famiCtx, 910002); err != nil || found { + t.Fatalf("membership starting after day_start must not receive rebate: found=%v err=%v", found, err) + } + + var availablePayload map[string]any + if err := json.Unmarshal([]byte(repository.WalletOutboxPayload("fami", "VipDailyCoinRebateAvailable", 910001)), &availablePayload); err != nil { + t.Fatalf("decode available outbox payload failed: %v", err) + } + for _, key := range []string{"rebate_id", "user_id", "task_day", "vip_level", "vip_name", "coin_amount", "available_at_ms", "expires_at_ms", "config_version", "created_at_ms"} { + if _, exists := availablePayload[key]; !exists { + t.Fatalf("available outbox payload missing %s: %+v", key, availablePayload) + } + } + + statuses, total, err := service.ListMyVipDailyCoinRebateStatuses(famiCtx, ledger.ListVipDailyCoinRebateStatusesQuery{ + AppCode: "fami", UserID: 910001, RebateIDs: []string{rebate6.RebateID, rebate7.RebateID}, Page: 1, PageSize: 20, + }) + if err != nil || total != 1 || len(statuses) != 1 || statuses[0].RebateID != rebate6.RebateID { + t.Fatalf("rebate_ids status lookup must remain user scoped: items=%+v total=%d err=%v", statuses, total, err) + } + + type claimResult struct { + receipt ledger.ClaimVipDailyCoinRebateReceipt + err error + } + start := make(chan struct{}) + results := make(chan claimResult, 2) + for range 2 { + go func() { + <-start + receipt, claimErr := service.ClaimVipDailyCoinRebate(famiCtx, ledger.ClaimVipDailyCoinRebateCommand{ + AppCode: "fami", CommandID: "claim-vip-rebate-concurrent", UserID: 910001, RebateID: rebate6.RebateID, + }) + results <- claimResult{receipt: receipt, err: claimErr} + }() + } + close(start) + left, right := <-results, <-results + if left.err != nil || right.err != nil || left.receipt.TransactionID == "" || left.receipt.TransactionID != right.receipt.TransactionID { + t.Fatalf("concurrent claim replay must return same success: left=%+v right=%+v", left, right) + } + if left.receipt.CoinBalance.AvailableAmount != 60 || right.receipt.CoinBalance.AvailableAmount != 60 { + t.Fatalf("concurrent claim must credit once: left=%+v right=%+v", left.receipt.CoinBalance, right.receipt.CoinBalance) + } + if got := repository.CountRows("wallet_transactions", "app_code = ? AND biz_type = ? AND external_ref = ?", "fami", "vip_daily_coin_rebate", rebate6.RebateID); got != 1 { + t.Fatalf("concurrent claim created %d wallet transactions, want 1", got) + } + if got := repository.CountRows("wallet_entries", "app_code = ? AND transaction_id = ?", "fami", left.receipt.TransactionID); got != 1 { + t.Fatalf("concurrent claim created %d wallet entries, want 1", got) + } + // 不同 command 的重复点击仍返回原交易;但已使用 command 改投另一 rebate 必须是稳定幂等冲突。 + retry, err := service.ClaimVipDailyCoinRebate(famiCtx, ledger.ClaimVipDailyCoinRebateCommand{ + AppCode: "fami", CommandID: "claim-vip-rebate-second-click", UserID: 910001, RebateID: rebate6.RebateID, + }) + if err != nil || retry.TransactionID != left.receipt.TransactionID || retry.CoinBalance.AvailableAmount != 60 { + t.Fatalf("different-command repeat click must return original receipt: receipt=%+v err=%v", retry, err) + } + _, err = service.ClaimVipDailyCoinRebate(famiCtx, ledger.ClaimVipDailyCoinRebateCommand{ + AppCode: "fami", CommandID: "claim-vip-rebate-second-click", UserID: 910004, RebateID: rebate7.RebateID, + }) + if !xerr.IsCode(err, xerr.IdempotencyConflict) { + t.Fatalf("successful repeat-click command must be consumed for its original rebate, got %v", err) + } + _, err = service.ClaimVipDailyCoinRebate(famiCtx, ledger.ClaimVipDailyCoinRebateCommand{ + AppCode: "fami", CommandID: "claim-vip-rebate-concurrent", UserID: 910004, RebateID: rebate7.RebateID, + }) + if !xerr.IsCode(err, xerr.IdempotencyConflict) { + t.Fatalf("same command with another rebate must return IDEMPOTENCY_CONFLICT, got %v", err) + } + + // 相同 user/task_day 在另一个 App 形成独立 run、rebate 和账户,不共享任何幂等事实。 + laluCtx := appcode.WithContext(context.Background(), "lalu") + repository.SetVIPDailyCoinRebateAmount("lalu", 1, 11, ledger.VipStatusActive) + repository.SeedPaidVIPAtWindow("lalu", 910001, 1, dayStartMS, dayEndMS) + laluRun, err := service.ProcessVipDailyCoinRebateBatch(laluCtx, ledger.ProcessVipDailyCoinRebateBatchCommand{ + AppCode: "lalu", TaskDay: taskDay, BatchSize: 100, + }) + if err != nil || laluRun.CreatedCount != 1 { + t.Fatalf("process Lalu isolated rebate failed: result=%+v err=%v", laluRun, err) + } + laluRebate, found, err := service.GetMyCurrentVipDailyCoinRebate(laluCtx, 910001) + if err != nil || !found || laluRebate.CoinAmount != 11 || laluRebate.RebateID == rebate6.RebateID { + t.Fatalf("VIP rebate must stay App isolated: fami=%+v lalu=%+v found=%v err=%v", rebate6, laluRebate, found, err) + } +} + +// TestVipDailyCoinRebateLegacyZeroExpiryHistoryUsesMigrationBaseline 固定 060 老库兼容边界: +// ALTER 产生的 0 有效期 history 不能覆盖会员主表事实;基线只补无完整快照用户且可重入。 +func TestVipDailyCoinRebateLegacyZeroExpiryHistoryUsesMigrationBaseline(t *testing.T) { + repository := mysqltest.NewRepository(t) + service := walletservice.New(repository) + ctx := appcode.WithContext(context.Background(), "fami") + now := time.Now().UTC() + dayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC) + dayStartMS := dayStart.UnixMilli() + dayEndMS := dayStart.Add(24 * time.Hour).UnixMilli() + + repository.SetVIPDailyCoinRebateAmount("fami", 6, 606, ledger.VipStatusActive) + // 940101 只有旧 0 有效期 history,应被基线补齐;940102 已有新格式快照,迁移不得再插行。 + repository.SeedLegacyPaidVIPAtWindow("fami", 940101, 6, dayStartMS-1_000, dayEndMS) + repository.SeedPaidVIPAtWindow("fami", 940102, 6, dayStartMS-1_000, dayEndMS) + repository.ApplyVIPHistoryMigrationBaseline(dayStartMS - 500) + repository.ApplyVIPHistoryMigrationBaseline(dayStartMS - 500) + if got := repository.CountRows("user_vip_history", "app_code = ? AND order_id = ?", "fami", "vip_configurable_060_baseline"); got != 1 { + t.Fatalf("migration baseline must be idempotent and skip complete histories, got %d rows", got) + } + if got := repository.CountRows("user_vip_history", "app_code = ? AND user_id = ?", "fami", 940102); got != 1 { + t.Fatalf("complete new-format history must remain untouched, got %d rows", got) + } + if got := repository.CountRows("user_vip_history", "app_code = ? AND user_id = ? AND new_expires_at_ms = 0", "fami", 940101); got != 1 { + t.Fatalf("migration must not rewrite legacy zero-expiry history, got %d rows", got) + } + + // 基线执行后再造一条旧数据,单独验证 repository 忽略 0 快照并回退 current membership。 + repository.SeedLegacyPaidVIPAtWindow("fami", 940103, 6, dayStartMS-1_000, dayEndMS) + result, err := service.ProcessVipDailyCoinRebateBatch(ctx, ledger.ProcessVipDailyCoinRebateBatchCommand{ + AppCode: "fami", TaskDay: dayStart.Format("2006-01-02"), BatchSize: 100, + }) + if err != nil || result.CreatedCount != 3 || result.HasMore { + t.Fatalf("legacy VIP rebate recovery mismatch: result=%+v err=%v", result, err) + } + for _, userID := range []int64{940101, 940102, 940103} { + rebate, found, queryErr := service.GetMyCurrentVipDailyCoinRebate(ctx, userID) + if queryErr != nil || !found || rebate.VipLevel != 6 || rebate.CoinAmount != 606 { + t.Fatalf("recovered VIP rebate mismatch for user %d: found=%v rebate=%+v err=%v", userID, found, rebate, queryErr) + } + } +} + +// TestVipDailyCoinRebateLatestLegacyHistoryDoesNotReviveOlderSnapshot 验证排序与有效性判定的先后顺序: +// 边界前最新行是迁移零值时,不得越过它选中更旧但完整的 VIP 快照。 +func TestVipDailyCoinRebateLatestLegacyHistoryDoesNotReviveOlderSnapshot(t *testing.T) { + repository := mysqltest.NewRepository(t) + service := walletservice.New(repository) + ctx := appcode.WithContext(context.Background(), "fami") + now := time.Now().UTC() + dayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC) + dayStartMS := dayStart.UnixMilli() + dayEndMS := dayStart.Add(24 * time.Hour).UnixMilli() + + repository.SetVIPDailyCoinRebateAmount("fami", 6, 606, ledger.VipStatusActive) + // 较旧完整行看似有效到日终;较新旧格式行的有效期未知,当前会员于日切恰好到期。 + repository.SeedPaidVIPAtWindow("fami", 940201, 6, dayStartMS-2_000, dayEndMS) + repository.SeedLegacyPaidVIPAtWindow("fami", 940201, 6, dayStartMS-1_000, dayStartMS) + result, err := service.ProcessVipDailyCoinRebateBatch(ctx, ledger.ProcessVipDailyCoinRebateBatchCommand{ + AppCode: "fami", TaskDay: dayStart.Format("2006-01-02"), BatchSize: 100, + }) + if err != nil || result.CreatedCount != 0 || result.HasMore { + t.Fatalf("newer incomplete history must block older VIP snapshot revival: result=%+v err=%v", result, err) + } + if _, found, queryErr := service.GetMyCurrentVipDailyCoinRebate(ctx, 940201); queryErr != nil || found { + t.Fatalf("stale older VIP snapshot unexpectedly created rebate: found=%v err=%v", found, queryErr) + } +} + +// TestVipDailyCoinRebateExpiredAndNotFoundCodes 固定客户端依赖的 404/409 原因码。 +func TestVipDailyCoinRebateExpiredAndNotFoundCodes(t *testing.T) { + repository := mysqltest.NewRepository(t) + service := walletservice.New(repository) + ctx := appcode.WithContext(context.Background(), "fami") + now := time.Now().UTC() + todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC) + yesterdayStart := todayStart.Add(-24 * time.Hour) + repository.SetVIPDailyCoinRebateAmount("fami", 6, 66, ledger.VipStatusActive) + repository.SeedPaidVIPAtWindow("fami", 920001, 6, yesterdayStart.UnixMilli(), todayStart.UnixMilli()) + result, err := service.ProcessVipDailyCoinRebateBatch(ctx, ledger.ProcessVipDailyCoinRebateBatchCommand{ + AppCode: "fami", TaskDay: yesterdayStart.Format("2006-01-02"), BatchSize: 100, + }) + if err != nil || result.CreatedCount != 1 { + t.Fatalf("create expired rebate fixture failed: result=%+v err=%v", result, err) + } + items, _, err := service.ListMyVipDailyCoinRebateStatuses(ctx, ledger.ListVipDailyCoinRebateStatusesQuery{ + AppCode: "fami", UserID: 920001, Page: 1, PageSize: 20, + }) + if err != nil || len(items) != 1 || items[0].Status != ledger.VipDailyCoinRebateStatusExpired { + t.Fatalf("expired status projection mismatch: items=%+v err=%v", items, err) + } + _, err = service.ClaimVipDailyCoinRebate(ctx, ledger.ClaimVipDailyCoinRebateCommand{ + AppCode: "fami", CommandID: "claim-expired-vip-rebate", UserID: 920001, RebateID: items[0].RebateID, + }) + if !xerr.IsCode(err, xerr.VIPCoinRebateExpired) { + t.Fatalf("expired claim must return VIP_COIN_REBATE_EXPIRED, got %v", err) + } + _, err = service.ClaimVipDailyCoinRebate(ctx, ledger.ClaimVipDailyCoinRebateCommand{ + AppCode: "fami", CommandID: "claim-missing-vip-rebate", UserID: 920001, RebateID: "vip_rebate_missing", + }) + if !xerr.IsCode(err, xerr.VIPCoinRebateNotFound) { + t.Fatalf("missing claim must return VIP_COIN_REBATE_NOT_FOUND, got %v", err) + } +} + +// TestVipDailyCoinRebateConcurrentRunCreation 覆盖两个 cron worker 同时首次触发同一天: +// 等待 program 锁的后到事务必须 current-read 复用已提交 run,不能在旧 RR 快照上撞唯一键。 +func TestVipDailyCoinRebateConcurrentRunCreation(t *testing.T) { + repository := mysqltest.NewRepository(t) + service := walletservice.New(repository) + ctx := appcode.WithContext(context.Background(), "fami") + now := time.Now().UTC() + dayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC) + taskDay := dayStart.Format("2006-01-02") + repository.SetVIPDailyCoinRebateAmount("fami", 6, 88, ledger.VipStatusActive) + repository.SeedPaidVIPAtWindow("fami", 930001, 6, dayStart.UnixMilli(), dayStart.Add(24*time.Hour).UnixMilli()) + + type batchResult struct { + result ledger.ProcessVipDailyCoinRebateBatchResult + err error + } + start := make(chan struct{}) + results := make(chan batchResult, 2) + for range 2 { + go func() { + <-start + result, err := service.ProcessVipDailyCoinRebateBatch(ctx, ledger.ProcessVipDailyCoinRebateBatchCommand{ + AppCode: "fami", TaskDay: taskDay, BatchSize: 100, + }) + results <- batchResult{result: result, err: err} + }() + } + close(start) + left, right := <-results, <-results + if left.err != nil || right.err != nil || left.result.Run.RunID == "" || left.result.Run.RunID != right.result.Run.RunID { + t.Fatalf("concurrent run creation must return one stable run: left=%+v right=%+v", left, right) + } + if got := repository.CountRows("vip_daily_coin_rebate_runs", "app_code = ? AND task_day = ?", "fami", taskDay); got != 1 { + t.Fatalf("concurrent run creation persisted %d runs, want 1", got) + } + if got := repository.CountRows("vip_daily_coin_rebates", "app_code = ? AND task_day = ?", "fami", taskDay); got != 1 { + t.Fatalf("concurrent run creation persisted %d rebates, want 1", got) + } + if got := repository.CountRows("wallet_outbox", "app_code = ? AND event_type = ?", "fami", "VipDailyCoinRebateAvailable"); got != 1 { + t.Fatalf("concurrent run creation persisted %d available events, want 1", got) + } +} + +// TestVipDailyCoinRebateDisabledEmptyMetadataCanBeSaved 确保后台可先关闭权益并提交空对象, +// 但一旦启用就必须配置正 int64 coin_amount。 +func TestVipDailyCoinRebateDisabledEmptyMetadataCanBeSaved(t *testing.T) { + repository := mysqltest.NewRepository(t) + service := walletservice.New(repository) + ctx := appcode.WithContext(context.Background(), "fami") + _, levels, err := service.ListAdminVipLevels(ctx) + if err != nil { + t.Fatalf("list VIP levels failed: %v", err) + } + commands := make([]ledger.AdminVipLevelCommand, 0, len(levels)) + for _, level := range levels { + for index := range level.Benefits { + if level.Benefits[index].BenefitCode == ledger.VipBenefitCodeDailyCoinRebate { + level.Benefits[index].Status = ledger.VipStatusDisabled + level.Benefits[index].MetadataJSON = `{}` + } + } + commands = append(commands, ledger.AdminVipLevelCommand{ + Level: level.Level, Name: level.Name, Status: level.Status, PriceCoin: level.PriceCoin, + DurationMS: level.DurationMS, RewardResourceGroupID: level.RewardResourceGroupID, + SortOrder: level.SortOrder, Benefits: level.Benefits, + }) + } + if _, _, err := service.UpdateAdminVipLevels(ctx, commands, 9001); err != nil { + t.Fatalf("disabled daily rebate with empty metadata object must save: %v", err) + } + for commandIndex := range commands { + for benefitIndex := range commands[commandIndex].Benefits { + benefit := &commands[commandIndex].Benefits[benefitIndex] + if benefit.BenefitCode == ledger.VipBenefitCodeDailyCoinRebate { + benefit.Status = ledger.VipStatusActive + benefit.MetadataJSON = `{}` + _, _, err = service.UpdateAdminVipLevels(ctx, commands, 9001) + if !xerr.IsCode(err, xerr.InvalidArgument) { + t.Fatalf("active daily rebate without coin_amount must be invalid, got %v", err) + } + return + } + } + } + t.Fatal("Fami VIP config missing daily_coin_rebate fixture") +} + +// TestVipOnlineGlobalNoticeMetadataValidation 固定 notice owner 契约:message 可省略,存在时必须 +// 是 1..256 个非空字符,且 execution_scope 不能被后台改到 room/user。 +func TestVipOnlineGlobalNoticeMetadataValidation(t *testing.T) { + repository := mysqltest.NewRepository(t) + service := walletservice.New(repository) + ctx := appcode.WithContext(context.Background(), "fami") + _, levels, err := service.ListAdminVipLevels(ctx) + if err != nil { + t.Fatalf("list VIP levels failed: %v", err) + } + commands := make([]ledger.AdminVipLevelCommand, 0, len(levels)) + for _, level := range levels { + for index := range level.Benefits { + benefit := &level.Benefits[index] + if benefit.BenefitCode == ledger.VipBenefitCodeOnlineGlobalNotice { + benefit.MetadataJSON = `{}` + } + } + commands = append(commands, ledger.AdminVipLevelCommand{ + Level: level.Level, Name: level.Name, Status: level.Status, PriceCoin: level.PriceCoin, + DurationMS: level.DurationMS, RewardResourceGroupID: level.RewardResourceGroupID, + SortOrder: level.SortOrder, Benefits: level.Benefits, + }) + } + if _, _, err := service.UpdateAdminVipLevels(ctx, commands, 9001); err != nil { + t.Fatalf("online notice metadata without message must save: %v", err) + } + for commandIndex := range commands { + for benefitIndex := range commands[commandIndex].Benefits { + benefit := &commands[commandIndex].Benefits[benefitIndex] + if benefit.BenefitCode != ledger.VipBenefitCodeOnlineGlobalNotice { + continue + } + benefit.MetadataJSON = `{"message":" "}` + if _, _, err := service.UpdateAdminVipLevels(ctx, commands, 9001); !xerr.IsCode(err, xerr.InvalidArgument) { + t.Fatalf("blank online notice message must be invalid, got %v", err) + } + payload, _ := json.Marshal(map[string]string{"message": strings.Repeat("好", 257)}) + benefit.MetadataJSON = string(payload) + if _, _, err := service.UpdateAdminVipLevels(ctx, commands, 9001); !xerr.IsCode(err, xerr.InvalidArgument) { + t.Fatalf("257-character online notice message must be invalid, got %v", err) + } + benefit.MetadataJSON = `{"message":"hello"}` + benefit.ExecutionScope = ledger.VipExecutionScopeRoom + if _, _, err := service.UpdateAdminVipLevels(ctx, commands, 9001); !xerr.IsCode(err, xerr.InvalidArgument) { + t.Fatalf("online notice non-notice scope must be invalid, got %v", err) + } + return + } + } + t.Fatal("Fami VIP config missing online_global_notice fixture") +} diff --git a/services/wallet-service/internal/service/wallet/vip_user_settings_integration_test.go b/services/wallet-service/internal/service/wallet/vip_user_settings_integration_test.go new file mode 100644 index 00000000..96168131 --- /dev/null +++ b/services/wallet-service/internal/service/wallet/vip_user_settings_integration_test.go @@ -0,0 +1,148 @@ +package wallet_test + +import ( + "context" + "testing" + "time" + + "hyapp/pkg/appcode" + "hyapp/services/wallet-service/internal/domain/ledger" + walletservice "hyapp/services/wallet-service/internal/service/wallet" + "hyapp/services/wallet-service/internal/testutil/mysqltest" +) + +// TestVipUserSettingsDefaultPartialUpdateAndAppScope 验证设置不授予/删除权益,且无行默认、 +// partial PATCH、付费/体验来源和 App 隔离都由 wallet owner 使用同一规则处理。 +func TestVipUserSettingsDefaultPartialUpdateAndAppScope(t *testing.T) { + repository := mysqltest.NewRepository(t) + service := walletservice.New(repository) + famiCtx := appcode.WithContext(context.Background(), "fami") + laluCtx := appcode.WithContext(context.Background(), "lalu") + const paidUserID int64 = 940001 + + defaultState, err := service.GetVipState(famiCtx, paidUserID) + if err != nil { + t.Fatalf("get default VIP settings failed: %v", err) + } + if !defaultState.UserSettings.RoomEntryNoticeEnabled || !defaultState.UserSettings.OnlineGlobalNoticeEnabled || defaultState.UserSettings.UpdatedAtMS != 0 { + t.Fatalf("missing settings row must project default enabled: %+v", defaultState.UserSettings) + } + if got := repository.CountRows("user_vip_settings", "app_code = ? AND user_id = ?", "fami", paidUserID); got != 0 { + t.Fatalf("read must not materialize default settings row, got %d", got) + } + + roomOff := false + settings, err := service.UpdateVipUserSettings(famiCtx, ledger.UpdateVipUserSettingsCommand{ + AppCode: "fami", UserID: paidUserID, RoomEntryNoticeEnabled: &roomOff, + }) + if err != nil || settings.RoomEntryNoticeEnabled || !settings.OnlineGlobalNoticeEnabled || settings.UpdatedAtMS <= 0 { + t.Fatalf("first partial settings update mismatch: settings=%+v err=%v", settings, err) + } + onlineOff := false + settings, err = service.UpdateVipUserSettings(famiCtx, ledger.UpdateVipUserSettingsCommand{ + AppCode: "fami", UserID: paidUserID, OnlineGlobalNoticeEnabled: &onlineOff, + }) + if err != nil || settings.RoomEntryNoticeEnabled || settings.OnlineGlobalNoticeEnabled { + t.Fatalf("second partial update must preserve room switch: settings=%+v err=%v", settings, err) + } + laluState, err := service.GetVipState(laluCtx, paidUserID) + if err != nil || !laluState.UserSettings.RoomEntryNoticeEnabled || !laluState.UserSettings.OnlineGlobalNoticeEnabled || laluState.UserSettings.UpdatedAtMS != 0 { + t.Fatalf("same user settings must remain App scoped: state=%+v err=%v", laluState.UserSettings, err) + } + + // 两个客户端并发修改不同字段时,partial upsert 必须串行保留双方结果。 + const concurrentUserID int64 = 940003 + startUpdates := make(chan struct{}) + updateErrors := make(chan error, 2) + go func() { + <-startUpdates + _, updateErr := service.UpdateVipUserSettings(famiCtx, ledger.UpdateVipUserSettingsCommand{ + AppCode: "fami", UserID: concurrentUserID, RoomEntryNoticeEnabled: &roomOff, + }) + updateErrors <- updateErr + }() + go func() { + <-startUpdates + _, updateErr := service.UpdateVipUserSettings(famiCtx, ledger.UpdateVipUserSettingsCommand{ + AppCode: "fami", UserID: concurrentUserID, OnlineGlobalNoticeEnabled: &onlineOff, + }) + updateErrors <- updateErr + }() + close(startUpdates) + if leftErr, rightErr := <-updateErrors, <-updateErrors; leftErr != nil || rightErr != nil { + t.Fatalf("concurrent partial settings update failed: left=%v right=%v", leftErr, rightErr) + } + concurrentState, err := service.GetVipState(famiCtx, concurrentUserID) + if err != nil || concurrentState.UserSettings.RoomEntryNoticeEnabled || concurrentState.UserSettings.OnlineGlobalNoticeEnabled { + t.Fatalf("concurrent partial updates overwrote each other: settings=%+v err=%v", concurrentState.UserSettings, err) + } + + // 设置可在无 VIP 时保存,但 CheckVipBenefit 仍优先返回 vip_inactive,不能让偏好变成授权。 + inactiveCheck, err := service.CheckVipBenefit(famiCtx, paidUserID, ledger.VipBenefitCodeRoomEntryNotice) + if err != nil || inactiveCheck.Allowed || inactiveCheck.DenialReason != "vip_inactive" { + t.Fatalf("settings must not grant VIP benefit: result=%+v err=%v", inactiveCheck, err) + } + + now := time.Now().UTC() + repository.SeedPaidVIPAtWindow("fami", paidUserID, 9, now.Add(-time.Hour).UnixMilli(), now.Add(time.Hour).UnixMilli()) + paidState, err := service.GetVipState(famiCtx, paidUserID) + if err != nil || paidState.EffectiveSource != ledger.VipEffectiveSourcePaid { + t.Fatalf("get paid VIP state failed: state=%+v err=%v", paidState, err) + } + if !containsVipBenefit(paidState.EffectiveBenefits, ledger.VipBenefitCodeRoomEntryNotice) || !containsVipBenefit(paidState.EffectiveBenefits, ledger.VipBenefitCodeOnlineGlobalNotice) { + t.Fatalf("disabled user settings must not delete owned effective benefits: %+v", paidState.EffectiveBenefits) + } + for _, code := range []string{ledger.VipBenefitCodeRoomEntryNotice, ledger.VipBenefitCodeOnlineGlobalNotice} { + result, checkErr := service.CheckVipBenefit(famiCtx, paidUserID, code) + if checkErr != nil || result.Allowed || result.DenialReason != "user_setting_disabled" || result.Benefit.BenefitCode != code { + t.Fatalf("paid VIP user setting gate mismatch for %s: result=%+v err=%v", code, result, checkErr) + } + } + roomOn := true + settings, err = service.UpdateVipUserSettings(famiCtx, ledger.UpdateVipUserSettingsCommand{ + AppCode: "fami", UserID: paidUserID, RoomEntryNoticeEnabled: &roomOn, + }) + if err != nil || !settings.RoomEntryNoticeEnabled || settings.OnlineGlobalNoticeEnabled { + t.Fatalf("partial re-enable must preserve online=false: settings=%+v err=%v", settings, err) + } + roomCheck, err := service.CheckVipBenefit(famiCtx, paidUserID, ledger.VipBenefitCodeRoomEntryNotice) + if err != nil || !roomCheck.Allowed { + t.Fatalf("enabled paid room entry benefit must be allowed: result=%+v err=%v", roomCheck, err) + } + + // 体验卡与付费 VIP 共用同一设置门禁;佩戴后 effective_benefits 仍保留资格,执行结果为 disabled。 + const trialUserID int64 = 940002 + repository.SetVIPLevelStatusForApp("fami", 8, ledger.VipStatusActive, 100) + grant, err := service.GrantVipTrialCard(famiCtx, ledger.GrantVipTrialCardCommand{ + AppCode: "fami", CommandID: "settings-trial-grant", TargetUserID: trialUserID, + Level: 8, DurationMS: int64(24 * time.Hour / time.Millisecond), + GrantSource: ledger.VipGrantSourceAdmin, OperatorUserID: 9001, Reason: "settings test", + }) + if err != nil { + t.Fatalf("grant trial VIP for settings test failed: %v", err) + } + if _, _, err := service.EquipVipTrialCard(famiCtx, ledger.EquipVipTrialCardCommand{ + AppCode: "fami", RequestID: "settings-trial-equip", UserID: trialUserID, + EntitlementID: grant.TrialCard.EntitlementID, + }); err != nil { + t.Fatalf("equip trial VIP for settings test failed: %v", err) + } + if _, err := service.UpdateVipUserSettings(famiCtx, ledger.UpdateVipUserSettingsCommand{ + AppCode: "fami", UserID: trialUserID, RoomEntryNoticeEnabled: &roomOff, + }); err != nil { + t.Fatalf("disable trial user room entry setting failed: %v", err) + } + trialCheck, err := service.CheckVipBenefit(famiCtx, trialUserID, ledger.VipBenefitCodeRoomEntryNotice) + if err != nil || trialCheck.Allowed || trialCheck.DenialReason != "user_setting_disabled" || trialCheck.State.EffectiveSource != ledger.VipEffectiveSourceTrial || !containsVipBenefit(trialCheck.State.EffectiveBenefits, ledger.VipBenefitCodeRoomEntryNotice) { + t.Fatalf("trial VIP user setting gate mismatch: result=%+v err=%v", trialCheck, err) + } +} + +func containsVipBenefit(items []ledger.VipBenefit, code string) bool { + for _, item := range items { + if item.BenefitCode == code { + return true + } + } + return false +} diff --git a/services/wallet-service/internal/service/wallet/vip_validation_test.go b/services/wallet-service/internal/service/wallet/vip_validation_test.go new file mode 100644 index 00000000..ad7d1944 --- /dev/null +++ b/services/wallet-service/internal/service/wallet/vip_validation_test.go @@ -0,0 +1,93 @@ +package wallet + +import ( + "context" + "strings" + "testing" + + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +// TestVipCommandStorageLengthsFailBeforeRepository keeps external strings within the VARCHAR +// boundaries used by transactions, outbox and entitlement identifiers; violations must be 4xx, +// never a MySQL data-too-long error surfaced as 5xx. +func TestVipCommandStorageLengthsFailBeforeRepository(t *testing.T) { + service := New(nil) + testCases := []struct { + name string + run func() error + }{ + { + name: "purchase command id", + run: func() error { + _, err := service.PurchaseVip(context.Background(), ledger.PurchaseVipCommand{ + CommandID: strings.Repeat("x", 129), UserID: 1, Level: 1, + }) + return err + }, + }, + { + name: "direct grant command id", + run: func() error { + _, err := service.GrantVip(context.Background(), ledger.GrantVipCommand{ + CommandID: strings.Repeat("x", 129), TargetUserID: 1, Level: 1, + GrantSource: ledger.VipGrantSourceAdmin, OperatorUserID: 1, + }) + return err + }, + }, + { + name: "equip request id", + run: func() error { + _, _, err := service.EquipVipTrialCard(context.Background(), ledger.EquipVipTrialCardCommand{ + RequestID: strings.Repeat("x", 129), UserID: 1, EntitlementID: "ent-1", + }) + return err + }, + }, + { + name: "equip entitlement id", + run: func() error { + _, _, err := service.EquipVipTrialCard(context.Background(), ledger.EquipVipTrialCardCommand{ + RequestID: "req-1", UserID: 1, EntitlementID: strings.Repeat("x", 97), + }) + return err + }, + }, + { + name: "unequip request id", + run: func() error { + _, _, err := service.UnequipVipTrialCard(context.Background(), ledger.UnequipVipTrialCardCommand{ + RequestID: strings.Repeat("x", 129), UserID: 1, + }) + return err + }, + }, + { + name: "rebate claim command id", + run: func() error { + _, err := service.ClaimVipDailyCoinRebate(context.Background(), ledger.ClaimVipDailyCoinRebateCommand{ + CommandID: strings.Repeat("x", 129), UserID: 1, RebateID: "vip_rebate_1", + }) + return err + }, + }, + { + name: "rebate id", + run: func() error { + _, err := service.ClaimVipDailyCoinRebate(context.Background(), ledger.ClaimVipDailyCoinRebateCommand{ + CommandID: "claim-rebate", UserID: 1, RebateID: strings.Repeat("x", 97), + }) + return err + }, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + if err := testCase.run(); !xerr.IsCode(err, xerr.InvalidArgument) { + t.Fatalf("oversized VIP identifier must be invalid argument, got %v", err) + } + }) + } +} diff --git a/services/wallet-service/internal/storage/mysql/agency_host_gift_stats.go b/services/wallet-service/internal/storage/mysql/agency_host_gift_stats.go new file mode 100644 index 00000000..82c6f49d --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/agency_host_gift_stats.go @@ -0,0 +1,43 @@ +package mysql + +import ( + "context" + "fmt" + "strings" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +// GetAgencyHostGiftStats 直接读取送礼交易里的 host_point_added 快照,只统计礼物收益; +// 任务奖励、提现释放和 Agency 分成虽然也进入 POINT 账户,但都不能混入 BD 的公会收礼口径。 +func (r *Repository) GetAgencyHostGiftStats(ctx context.Context, query ledger.AgencyHostGiftStatsQuery) (ledger.AgencyHostGiftStats, error) { + if r == nil || r.db == nil { + return ledger.AgencyHostGiftStats{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + if len(query.HostUserIDs) == 0 { + return ledger.AgencyHostGiftStats{}, nil + } + placeholders := make([]string, len(query.HostUserIDs)) + args := make([]any, 0, len(query.HostUserIDs)+5) + args = append(args, appcode.FromContext(ctx), bizTypeGiftDebit, bizTypeDirectGiftDebit, query.StartAtMS, query.EndAtMS) + for index, userID := range query.HostUserIDs { + placeholders[index] = "?" + args = append(args, userID) + } + querySQL := fmt.Sprintf(` + SELECT + COALESCE(SUM(CAST(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.host_point_added')), '0') AS SIGNED)), 0), + COUNT(DISTINCT CASE + WHEN CAST(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.host_point_added')), '0') AS SIGNED) > 0 + THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.target_user_id')) AS SIGNED) + END) + FROM wallet_transactions + WHERE app_code = ? AND biz_type IN (?, ?) AND status = 'succeeded' + AND created_at_ms >= ? AND created_at_ms < ? + AND CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.target_user_id')) AS SIGNED) IN (%s)`, strings.Join(placeholders, ",")) + var stats ledger.AgencyHostGiftStats + err := r.db.QueryRowContext(ctx, querySQL, args...).Scan(&stats.GiftIncome, &stats.GiftedHostCount) + return stats, err +} diff --git a/services/wallet-service/internal/storage/mysql/agency_point_share.go b/services/wallet-service/internal/storage/mysql/agency_point_share.go new file mode 100644 index 00000000..4af3beea --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/agency_point_share.go @@ -0,0 +1,26 @@ +package mysql + +import ( + "context" + "database/sql" + + "hyapp/pkg/appcode" +) + +// insertAgencyPointShare 把“当时属于哪个 Agency”与实际入账金额固化在同一送礼事务内; +// 后续主播转会时统计仍按这条快照归属,不能用当前成员关系重算历史收入。 +func (r *Repository) insertAgencyPointShare(ctx context.Context, tx *sql.Tx, transactionID string, metadata giftMetadata, nowMS int64) error { + if metadata.AgencyPointAdded <= 0 || metadata.TargetAgencyOwnerUserID <= 0 { + return nil + } + _, err := tx.ExecContext(ctx, ` + INSERT INTO agency_point_share_entries ( + app_code, transaction_id, host_user_id, agency_owner_user_id, sender_user_id, + room_id, point_delta, ratio_ppm, policy_instance_code, created_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + appcode.FromContext(ctx), transactionID, metadata.TargetUserID, metadata.TargetAgencyOwnerUserID, + metadata.SenderUserID, metadata.RoomID, metadata.AgencyPointAdded, metadata.AgencyPointRatioPPM, + metadata.HostPointPolicyInstanceCode, nowMS, + ) + return err +} diff --git a/services/wallet-service/internal/storage/mysql/agency_point_share_stats.go b/services/wallet-service/internal/storage/mysql/agency_point_share_stats.go new file mode 100644 index 00000000..39ae7404 --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/agency_point_share_stats.go @@ -0,0 +1,25 @@ +package mysql + +import ( + "context" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +// GetAgencyPointShareStats 按入账时归属快照统计,不受主播之后转会或被移除影响。 +func (r *Repository) GetAgencyPointShareStats(ctx context.Context, query ledger.AgencyPointShareStatsQuery) (ledger.AgencyPointShareStats, error) { + if r == nil || r.db == nil { + return ledger.AgencyPointShareStats{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + var stats ledger.AgencyPointShareStats + err := r.db.QueryRowContext(ctx, ` + SELECT COALESCE(SUM(point_delta), 0), COUNT(DISTINCT host_user_id) + FROM agency_point_share_entries + WHERE app_code = ? AND agency_owner_user_id = ? + AND created_at_ms >= ? AND created_at_ms < ?`, + appcode.FromContext(ctx), query.AgencyOwnerUserID, query.StartAtMS, query.EndAtMS, + ).Scan(&stats.ShareIncome, &stats.GiftedHostCount) + return stats, err +} diff --git a/services/wallet-service/internal/storage/mysql/agency_point_share_stats_test.go b/services/wallet-service/internal/storage/mysql/agency_point_share_stats_test.go new file mode 100644 index 00000000..4d0e0b2f --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/agency_point_share_stats_test.go @@ -0,0 +1,40 @@ +package mysql + +import ( + "context" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "hyapp/pkg/appcode" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +func TestGetAgencyPointShareStatsUsesOwnershipSnapshot(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + mock.ExpectQuery(regexp.QuoteMeta(` + SELECT COALESCE(SUM(point_delta), 0), COUNT(DISTINCT host_user_id) + FROM agency_point_share_entries + WHERE app_code = ? AND agency_owner_user_id = ? + AND created_at_ms >= ? AND created_at_ms < ?`)). + WithArgs("fami", int64(73), int64(1000), int64(2000)). + WillReturnRows(sqlmock.NewRows([]string{"share_income", "gifted_hosts"}).AddRow(int64(2400), int64(3))) + + repo := &Repository{db: db} + stats, err := repo.GetAgencyPointShareStats(appcode.WithContext(context.Background(), "fami"), ledger.AgencyPointShareStatsQuery{ + AgencyOwnerUserID: 73, StartAtMS: 1000, EndAtMS: 2000, + }) + if err != nil { + t.Fatalf("GetAgencyPointShareStats failed: %v", err) + } + if stats.ShareIncome != 2400 || stats.GiftedHostCount != 3 { + t.Fatalf("unexpected agency stats: %+v", stats) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("unmet SQL expectations: %v", err) + } +} diff --git a/services/wallet-service/internal/storage/mysql/app_wallet_constants.go b/services/wallet-service/internal/storage/mysql/app_wallet_constants.go index 9a635e58..fae5ba2c 100644 --- a/services/wallet-service/internal/storage/mysql/app_wallet_constants.go +++ b/services/wallet-service/internal/storage/mysql/app_wallet_constants.go @@ -3,6 +3,7 @@ package mysql const ( bizTypeVIPPurchase = "vip_purchase" bizTypeVIPGrant = "vip_grant" + bizTypeVIPTrialCardGrant = "vip_trial_card_grant" vipOutboxAsset = "VIP" managerCenterVIPGrantDurationMS = 30 * 24 * 60 * 60 * 1000 ) diff --git a/services/wallet-service/internal/storage/mysql/badge_projection_relay.go b/services/wallet-service/internal/storage/mysql/badge_projection_relay.go index af4477ec..7aeef24f 100644 --- a/services/wallet-service/internal/storage/mysql/badge_projection_relay.go +++ b/services/wallet-service/internal/storage/mysql/badge_projection_relay.go @@ -17,8 +17,8 @@ import ( const ( badgeDisplayProjectionName = "activity_badge_display" - walletResourceGrantedEvent = "ResourceGranted" - walletResourceGroupGrantedEvent = "ResourceGroupGranted" + walletResourceGrantedEvent = walletmq.EventTypeResourceGranted + walletResourceGroupGrantedEvent = walletmq.EventTypeResourceGroupGranted grantSourceVIPPurchase = "vip_purchase" ) diff --git a/services/wallet-service/internal/storage/mysql/constants.go b/services/wallet-service/internal/storage/mysql/constants.go index ddd230eb..32b9924c 100644 --- a/services/wallet-service/internal/storage/mysql/constants.go +++ b/services/wallet-service/internal/storage/mysql/constants.go @@ -19,6 +19,7 @@ const ( bizTypeCoinSellerCoinCompensation = "coin_seller_coin_compensation" bizTypeSalaryExchangeToCoin = "salary_exchange_to_coin" bizTypeSalaryTransferToCoinSeller = "salary_transfer_to_coin_seller" + bizTypePointTransferToCoinSeller = "point_transfer_to_coin_seller" bizTypeSalaryWithdrawalFreeze = "salary_withdrawal_freeze" bizTypeSalaryWithdrawalSettle = "salary_withdrawal_settle" bizTypeSalaryWithdrawalRelease = "salary_withdrawal_release" @@ -33,6 +34,7 @@ const ( bizTypeHostSalarySettlement = "host_salary_settlement" bizTypeCPBreakupFee = "cp_breakup_fee" bizTypeWheelDrawDebit = "wheel_draw_debit" + bizTypeVipDailyCoinRebate = "vip_daily_coin_rebate" outboxStatusPending = "pending" outboxStatusDelivering = "delivering" outboxStatusDelivered = "delivered" diff --git a/services/wallet-service/internal/storage/mysql/gift_balance_version_test.go b/services/wallet-service/internal/storage/mysql/gift_balance_version_test.go new file mode 100644 index 00000000..f5b476ed --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/gift_balance_version_test.go @@ -0,0 +1,49 @@ +package mysql + +import ( + "testing" + + "hyapp/services/wallet-service/internal/domain/ledger" +) + +func TestGiftSenderBalanceVersionAfterCountsEverySenderLedgerEntry(t *testing.T) { + tests := []struct { + name string + current int64 + robotTopUp bool + debit bool + selfGiftIncome bool + want int64 + }{ + {name: "coin debit", current: 7, debit: true, want: 8}, + {name: "robot top up and debit", current: 7, robotTopUp: true, debit: true, want: 9}, + {name: "self gift debit and income", current: 7, debit: true, selfGiftIncome: true, want: 9}, + {name: "bag gift without sender coin mutation", current: 7, want: 7}, + {name: "bag self gift income", current: 7, selfGiftIncome: true, want: 8}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if got := giftSenderBalanceVersionAfter(test.current, test.robotTopUp, test.debit, test.selfGiftIncome); got != test.want { + t.Fatalf("balance version = %d, want %d", got, test.want) + } + }) + } +} + +func TestGiftReceiptReplayAndBatchAggregatePreserveBalanceVersion(t *testing.T) { + first := receiptFromGiftMetadata("tx-1", giftMetadata{BillingReceipt: "receipt-1", BalanceAfter: 900, BalanceVersion: 11}) + last := receiptFromGiftMetadata("tx-2", giftMetadata{BillingReceipt: "receipt-2", BalanceAfter: 800, BalanceVersion: 12}) + if first.BalanceVersion != 11 || last.BalanceVersion != 12 { + t.Fatalf("metadata replay lost balance version: first=%+v last=%+v", first, last) + } + aggregate, err := aggregateGiftReceipts([]ledger.BatchGiftTargetReceipt{ + {TargetUserID: 1, CommandID: "target-1", Receipt: first}, + {TargetUserID: 2, CommandID: "target-2", Receipt: last}, + }) + if err != nil { + t.Fatalf("aggregate gift receipts: %v", err) + } + if aggregate.BalanceAfter != 800 || aggregate.BalanceVersion != 12 { + t.Fatalf("aggregate must expose final sender snapshot: %+v", aggregate) + } +} diff --git a/services/wallet-service/internal/storage/mysql/gift_batch_debit_repository.go b/services/wallet-service/internal/storage/mysql/gift_batch_debit_repository.go index 9d917e5b..2c6af529 100644 --- a/services/wallet-service/internal/storage/mysql/gift_batch_debit_repository.go +++ b/services/wallet-service/internal/storage/mysql/gift_batch_debit_repository.go @@ -112,10 +112,12 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb } type targetHostPointPolicy struct { - policy walletPolicyInstance - amount int64 - userID int64 - active bool + policy walletPolicyInstance + amount int64 + userID int64 + agencyAmount int64 + agencyOwnerUserID int64 + active bool } hostPointPolicies := make(map[string]targetHostPointPolicy, len(command.Targets)) if chargeSource != giftChargeSourceBag { @@ -139,7 +141,21 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb if err != nil { return ledger.BatchGiftReceipt{}, err } - hostPointPolicies[target.CommandID] = targetHostPointPolicy{policy: policy, amount: amount, userID: target.TargetUserID, active: true} + agencyAmount := int64(0) + if target.TargetAgencyOwnerUserID > 0 && policy.AgencyPointRatioPPM > 0 { + agencyAmount, err = giftDiamondAmount(chargeAmount, policy.AgencyPointRatioPPM) + if err != nil { + return ledger.BatchGiftReceipt{}, err + } + } + hostPointPolicies[target.CommandID] = targetHostPointPolicy{ + policy: policy, + amount: amount, + userID: target.TargetUserID, + agencyAmount: agencyAmount, + agencyOwnerUserID: target.TargetAgencyOwnerUserID, + active: true, + } } } @@ -170,6 +186,13 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb AssetType: ledger.AssetPoint, CreateIfMissing: true, }) + if hostPointPolicy.agencyAmount > 0 { + lockRequests = append(lockRequests, walletAccountLockRequest{ + UserID: hostPointPolicy.agencyOwnerUserID, + AssetType: ledger.AssetPoint, + CreateIfMissing: true, + }) + } } accountStates, err := r.lockAccountsOrdered(ctx, tx, lockRequests, nowMs) if err != nil { @@ -267,6 +290,26 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb } hostPointBalanceAfter = targetPointState.account.AvailableAmount + hostPointPolicy.amount } + agencyPointBalanceAfter := int64(0) + var agencyPointState *walletAccountState + if hostPointPolicy.agencyAmount > 0 { + agencyPointState = accountStates[walletAccountLockKey(hostPointPolicy.agencyOwnerUserID, ledger.AssetPoint)] + if agencyPointState == nil { + return ledger.BatchGiftReceipt{}, xerr.New(xerr.Internal, "agency point account is missing") + } + agencyPointBalanceAfter = agencyPointState.account.AvailableAmount + hostPointPolicy.agencyAmount + if agencyPointState == targetPointState { + agencyPointBalanceAfter += hostPointPolicy.amount + } + } + // 批量事务逐目标生成独立账务交易;这里从当前 sender 版本预测本目标完成后的版本, + // 下一轮会读取已推进的 senderState,因此最后一条回执自然代表整批最终版本。 + senderBalanceVersionAfter := giftSenderBalanceVersionAfter( + senderState.account.Version, + false, + chargeSource != giftChargeSourceBag, + targetGiftIncomeCoinAmount > 0 && target.TargetUserID == command.SenderUserID && accountAssetType == ledger.AssetCoin, + ) metadata := giftMetadata{ AppCode: command.AppCode, GiftID: command.GiftID, @@ -291,6 +334,7 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb GiftPointAdded: 0, HeatValue: heatValue, BalanceAfter: senderAfter, + BalanceVersion: senderBalanceVersionAfter, GiftIncomeCoinAmount: targetGiftIncomeCoinAmount, GiftIncomeRatioPercent: giftIncomeRatio.Percent, GiftIncomeRatioRegionID: giftIncomeRatioRegionID, @@ -313,6 +357,9 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb HostPointTemplateCode: hostPointPolicy.policy.TemplateCode, HostPointTemplateVersion: hostPointPolicy.policy.TemplateVersion, HostPointRatioPPM: hostPointPolicy.policy.HostPointRatioPPM, + AgencyPointAdded: hostPointPolicy.agencyAmount, + AgencyPointBalanceAfter: agencyPointBalanceAfter, + AgencyPointRatioPPM: hostPointPolicy.policy.AgencyPointRatioPPM, PointsPerUSD: hostPointPolicy.policy.PointsPerUSD, RoomID: command.RoomID, RoomRegionID: command.RegionID, @@ -395,6 +442,29 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb return ledger.BatchGiftReceipt{}, err } } + if hostPointPolicy.agencyAmount > 0 { + agencyPointAccount, err := r.applyTrackedAccountDelta(ctx, tx, agencyPointState, hostPointPolicy.agencyAmount, 0, nowMs) + if err != nil { + return ledger.BatchGiftReceipt{}, err + } + metadata.AgencyPointBalanceAfter = agencyPointAccount.AvailableAmount + if err := r.insertEntry(ctx, tx, walletEntry{ + TransactionID: transactionID, + UserID: hostPointPolicy.agencyOwnerUserID, + AssetType: ledger.AssetPoint, + AvailableDelta: hostPointPolicy.agencyAmount, + AvailableAfter: agencyPointAccount.AvailableAmount, + FrozenAfter: agencyPointAccount.FrozenAmount, + CounterpartyUserID: command.SenderUserID, + RoomID: command.RoomID, + CreatedAtMS: nowMs, + }); err != nil { + return ledger.BatchGiftReceipt{}, err + } + if err := r.insertAgencyPointShare(ctx, tx, transactionID, metadata, nowMs); err != nil { + return ledger.BatchGiftReceipt{}, err + } + } if hostPeriodDiamondAdded > 0 { hostPeriodDiamondAfter, hostPeriodDiamondVersion, err := r.creditHostPeriodDiamonds(ctx, tx, transactionID, target.CommandID, metadata, nowMs) if err != nil { @@ -403,6 +473,14 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb metadata.HostPeriodDiamondAfter = hostPeriodDiamondAfter metadata.HostPeriodDiamondVersion = hostPeriodDiamondVersion } + // Each target owns an independent idempotent wallet transaction. Freeze the actual sender snapshot + // after this target's ledger entries, so aggregate replay can take the last receipt and recover the + // exact final balance_version without consulting today's wallet account. + metadata.BalanceAfter = senderState.account.AvailableAmount + metadata.BalanceVersion = senderState.account.Version + if err := r.updateTransactionMetadata(ctx, tx, transactionID, metadata, nowMs); err != nil { + return ledger.BatchGiftReceipt{}, err + } if chargeSource != giftChargeSourceBag { if target.TargetUserID == command.SenderUserID && chargeAssetType == ledger.AssetCoin { @@ -422,6 +500,10 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb events = append(events, balanceChangedEvent(transactionID, target.CommandID, target.TargetUserID, ledger.AssetPoint, hostPointPolicy.amount, 0, metadata.HostPointBalanceAfter, targetPointState.account.FrozenAmount, targetPointState.account.Version, metadata, nowMs, bizTypeGiftDebit)) events = append(events, hostPointCreditedEvent(transactionID, target.CommandID, metadata, nowMs)) } + if hostPointPolicy.agencyAmount > 0 { + events = append(events, balanceChangedEvent(transactionID, target.CommandID, hostPointPolicy.agencyOwnerUserID, ledger.AssetPoint, hostPointPolicy.agencyAmount, 0, metadata.AgencyPointBalanceAfter, agencyPointState.account.FrozenAmount, agencyPointState.account.Version, metadata, nowMs, bizTypeGiftDebit)) + events = append(events, agencyPointCreditedEvent(transactionID, target.CommandID, metadata, nowMs)) + } if hostPeriodDiamondAdded > 0 { events = append(events, hostPeriodDiamondCreditedEvent(transactionID, target.CommandID, metadata, nowMs)) } diff --git a/services/wallet-service/internal/storage/mysql/gift_debit_repository.go b/services/wallet-service/internal/storage/mysql/gift_debit_repository.go index 722b963c..ec7ee941 100644 --- a/services/wallet-service/internal/storage/mysql/gift_debit_repository.go +++ b/services/wallet-service/internal/storage/mysql/gift_debit_repository.go @@ -90,6 +90,7 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm } } hostPointAdded := int64(0) + agencyPointAdded := int64(0) hostPointPolicy := walletPolicyInstance{} hostPointPolicyActive := false hostPointRegionID := command.TargetHostRegionID @@ -109,6 +110,13 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm } // 命中新政策后不再给主播走旧 COIN 返还,避免同一笔礼物同时产生 COIN 收礼返还和 POINT 收益。 giftIncomeCoinAmount = 0 + if command.TargetAgencyOwnerUserID > 0 && hostPointPolicy.AgencyPointRatioPPM > 0 { + // Agency 分成只跟随服务端注入的送礼时归属快照,客户端不能指定;自送礼同样属于有效付费礼物。 + agencyPointAdded, err = giftDiamondAmount(chargeAmount, hostPointPolicy.AgencyPointRatioPPM) + if err != nil { + return ledger.Receipt{}, err + } + } } } hostPeriodDiamondAdded := int64(0) @@ -137,6 +145,7 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm var senderState *walletAccountState var targetCoinState *walletAccountState var targetPointState *walletAccountState + var agencyPointState *walletAccountState if command.RobotGift { sender, err := r.lockAccount(ctx, tx, command.SenderUserID, accountAssetType, true, nowMs) @@ -164,6 +173,13 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm CreateIfMissing: true, }) } + if agencyPointAdded > 0 { + lockRequests = append(lockRequests, walletAccountLockRequest{ + UserID: command.TargetAgencyOwnerUserID, + AssetType: ledger.AssetPoint, + CreateIfMissing: true, + }) + } states, err := r.lockAccountsOrdered(ctx, tx, lockRequests, nowMs) if err != nil { return ledger.Receipt{}, err @@ -175,6 +191,9 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm if hostPointAdded > 0 { targetPointState = states[walletAccountLockKey(command.TargetUserID, ledger.AssetPoint)] } + if agencyPointAdded > 0 { + agencyPointState = states[walletAccountLockKey(command.TargetAgencyOwnerUserID, ledger.AssetPoint)] + } } sender := senderState.account robotCoinTopUp := int64(0) @@ -209,7 +228,25 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm } hostPointBalanceAfter = targetPointState.account.AvailableAmount + hostPointAdded } + agencyPointBalanceAfter := int64(0) + if agencyPointAdded > 0 { + if agencyPointState == nil { + return ledger.Receipt{}, xerr.New(xerr.Internal, "agency point account is missing") + } + agencyPointBalanceAfter = agencyPointState.account.AvailableAmount + agencyPointAdded + if agencyPointState == targetPointState { + agencyPointBalanceAfter += hostPointAdded + } + } transactionID := transactionID(command.AppCode, command.CommandID) + // metadata 在账户更新前写入交易表,因此版本必须按本事务即将执行的 sender 分录精确预测; + // 事务内实际更新仍由 wallet_accounts.version 乐观锁裁决,失败会整体回滚该预测值。 + senderBalanceVersionAfter := giftSenderBalanceVersionAfter( + senderState.account.Version, + robotCoinTopUp > 0, + chargeSource != giftChargeSourceBag, + giftIncomeCoinAmount > 0 && command.TargetUserID == command.SenderUserID && accountAssetType == ledger.AssetCoin, + ) metadata := giftMetadata{ AppCode: command.AppCode, GiftID: command.GiftID, @@ -234,6 +271,7 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm GiftPointAdded: 0, HeatValue: heatValue, BalanceAfter: senderAfter, + BalanceVersion: senderBalanceVersionAfter, GiftIncomeCoinAmount: giftIncomeCoinAmount, GiftIncomeRatioPercent: giftIncomeRatio.Percent, GiftIncomeRatioRegionID: giftIncomeRatioRegionID, @@ -256,6 +294,9 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm HostPointTemplateCode: hostPointPolicy.TemplateCode, HostPointTemplateVersion: hostPointPolicy.TemplateVersion, HostPointRatioPPM: hostPointPolicy.HostPointRatioPPM, + AgencyPointAdded: agencyPointAdded, + AgencyPointBalanceAfter: agencyPointBalanceAfter, + AgencyPointRatioPPM: hostPointPolicy.AgencyPointRatioPPM, PointsPerUSD: hostPointPolicy.PointsPerUSD, RoomID: command.RoomID, RoomRegionID: command.RegionID, @@ -369,6 +410,29 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm return ledger.Receipt{}, err } } + if agencyPointAdded > 0 { + agencyPointAccount, err := r.applyTrackedAccountDelta(ctx, tx, agencyPointState, agencyPointAdded, 0, nowMs) + if err != nil { + return ledger.Receipt{}, err + } + metadata.AgencyPointBalanceAfter = agencyPointAccount.AvailableAmount + if err := r.insertEntry(ctx, tx, walletEntry{ + TransactionID: transactionID, + UserID: command.TargetAgencyOwnerUserID, + AssetType: ledger.AssetPoint, + AvailableDelta: agencyPointAdded, + AvailableAfter: agencyPointAccount.AvailableAmount, + FrozenAfter: agencyPointAccount.FrozenAmount, + CounterpartyUserID: command.SenderUserID, + RoomID: command.RoomID, + CreatedAtMS: nowMs, + }); err != nil { + return ledger.Receipt{}, err + } + if err := r.insertAgencyPointShare(ctx, tx, transactionID, metadata, nowMs); err != nil { + return ledger.Receipt{}, err + } + } if hostPeriodDiamondAdded > 0 { hostPeriodDiamondAfter, hostPeriodDiamondVersion, err := r.creditHostPeriodDiamonds(ctx, tx, transactionID, command.CommandID, metadata, nowMs) @@ -378,6 +442,15 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm metadata.HostPeriodDiamondAfter = hostPeriodDiamondAfter metadata.HostPeriodDiamondVersion = hostPeriodDiamondVersion } + // The transaction row is inserted before ledger entries, so its first JSON value is necessarily a + // provisional snapshot. Replace it with the locked account's actual post-mutation version inside the + // same transaction; this also covers robot top-up and bag self-gift paths where one command can move + // the sender account more than once and prediction alone is too fragile for financial replay. + metadata.BalanceAfter = senderState.account.AvailableAmount + metadata.BalanceVersion = senderState.account.Version + if err := r.updateTransactionMetadata(ctx, tx, transactionID, metadata, nowMs); err != nil { + return ledger.Receipt{}, err + } if !command.RobotGift { @@ -399,6 +472,10 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm events = append(events, balanceChangedEvent(transactionID, command.CommandID, command.TargetUserID, ledger.AssetPoint, hostPointAdded, 0, metadata.HostPointBalanceAfter, targetPointState.account.FrozenAmount, targetPointState.account.Version, metadata, nowMs, bizType)) events = append(events, hostPointCreditedEvent(transactionID, command.CommandID, metadata, nowMs)) } + if agencyPointAdded > 0 { + events = append(events, balanceChangedEvent(transactionID, command.CommandID, command.TargetAgencyOwnerUserID, ledger.AssetPoint, agencyPointAdded, 0, metadata.AgencyPointBalanceAfter, agencyPointState.account.FrozenAmount, agencyPointState.account.Version, metadata, nowMs, bizType)) + events = append(events, agencyPointCreditedEvent(transactionID, command.CommandID, metadata, nowMs)) + } } if hostPeriodDiamondAdded > 0 { events = append(events, hostPeriodDiamondCreditedEvent(transactionID, command.CommandID, metadata, nowMs)) diff --git a/services/wallet-service/internal/storage/mysql/gift_ledger_helpers.go b/services/wallet-service/internal/storage/mysql/gift_ledger_helpers.go index e339964a..7a73cfc0 100644 --- a/services/wallet-service/internal/storage/mysql/gift_ledger_helpers.go +++ b/services/wallet-service/internal/storage/mysql/gift_ledger_helpers.go @@ -17,6 +17,23 @@ func giftDebitBizType(command ledger.DebitGiftCommand) string { return bizTypeGiftDebit } +// giftSenderBalanceVersionAfter 预测本目标交易完成后的 sender 账户版本,并在插入交易 metadata 前固化。 +// 钱包账户每执行一次 applyTrackedAccountDelta 都推进一个版本;机器人补足、实际扣费和自送礼返币 +// 是三个独立分录,必须分别计数,才能让首次回执和后续幂等回放得到同一个精确版本。 +func giftSenderBalanceVersionAfter(current int64, robotTopUp bool, debit bool, selfGiftIncome bool) int64 { + next := current + if robotTopUp { + next++ + } + if debit { + next++ + } + if selfGiftIncome { + next++ + } + return next +} + func aggregateGiftReceipts(targets []ledger.BatchGiftTargetReceipt) (ledger.Receipt, error) { aggregate := ledger.Receipt{} billingReceiptIDs := make([]string, 0, len(targets)) @@ -85,6 +102,7 @@ func aggregateGiftReceipts(targets []ledger.BatchGiftTargetReceipt) (ledger.Rece } aggregate.BalanceAfter = receipt.BalanceAfter + aggregate.BalanceVersion = receipt.BalanceVersion aggregate.HostPointBalanceAfter = receipt.HostPointBalanceAfter } aggregate.BillingReceiptID = strings.Join(billingReceiptIDs, ",") diff --git a/services/wallet-service/internal/storage/mysql/gift_wall_projection.go b/services/wallet-service/internal/storage/mysql/gift_wall_projection.go index d69f4c62..3966ee9e 100644 --- a/services/wallet-service/internal/storage/mysql/gift_wall_projection.go +++ b/services/wallet-service/internal/storage/mysql/gift_wall_projection.go @@ -16,7 +16,7 @@ import ( const ( giftWallProjectionName = "user_gift_wall" - walletGiftDebitedEvent = "WalletGiftDebited" + walletGiftDebitedEvent = walletmq.EventTypeWalletGiftDebited // projection 状态机只描述“某个 outbox 事件是否已经投影进礼物墙”。 // 这里不复用 wallet_outbox.status,避免展示读模型反向污染账务事件分发语义。 diff --git a/services/wallet-service/internal/storage/mysql/google_payment_repository.go b/services/wallet-service/internal/storage/mysql/google_payment_repository.go index 71dc5fc7..ecfbabc6 100644 --- a/services/wallet-service/internal/storage/mysql/google_payment_repository.go +++ b/services/wallet-service/internal/storage/mysql/google_payment_repository.go @@ -13,6 +13,11 @@ import ( "hyapp/services/wallet-service/internal/domain/ledger" ) +const ( + googlePaidSyncRetryBase = 15 * time.Second + googlePaidSyncRetryMax = 15 * time.Minute +) + // ConfirmGooglePayment 将 Google Play 已购买 token 落为平台支付订单、钱包交易和充值审计记录。 func (r *Repository) ConfirmGooglePayment(ctx context.Context, command ledger.GooglePaymentCommand, product ledger.RechargeProduct, purchase ledger.GooglePlayPurchase) (ledger.GooglePaymentReceipt, error) { if r == nil || r.db == nil { @@ -321,7 +326,8 @@ func (r *Repository) ListGooglePaymentOrderRefs(ctx context.Context, appCode str args = append(args, transactionID) } rows, err := r.db.QueryContext(ctx, ` - SELECT payment_order_id, wallet_transaction_id, package_name, provider_order_id, paid_synced_at_ms + SELECT app_code, payment_order_id, wallet_transaction_id, package_name, provider_order_id, + paid_synced_at_ms, paid_sync_attempt_count FROM payment_orders WHERE app_code = ? AND provider = ? AND wallet_transaction_id IN (`+placeholders+`)`, args..., @@ -334,7 +340,7 @@ func (r *Repository) ListGooglePaymentOrderRefs(ctx context.Context, appCode str refs := make(map[string]ledger.GooglePaymentOrderRef, len(cleaned)) for rows.Next() { var ref ledger.GooglePaymentOrderRef - if err := rows.Scan(&ref.PaymentOrderID, &ref.TransactionID, &ref.PackageName, &ref.ProviderOrderID, &ref.PaidSyncedAtMS); err != nil { + if err := rows.Scan(&ref.AppCode, &ref.PaymentOrderID, &ref.TransactionID, &ref.PackageName, &ref.ProviderOrderID, &ref.PaidSyncedAtMS, &ref.SyncAttemptCount); err != nil { return nil, err } refs[ref.TransactionID] = ref @@ -342,6 +348,43 @@ func (r *Repository) ListGooglePaymentOrderRefs(ctx context.Context, appCode str return refs, rows.Err() } +// ListPendingGooglePaymentOrderRefs claims no rows: paid-detail synchronization is idempotent and the +// retry columns prevent failed orders from monopolizing every 15-second scan. Duplicate workers may +// read the same row during a rolling deploy, but both write the same provider fact. +func (r *Repository) ListPendingGooglePaymentOrderRefs(ctx context.Context, limit int, nowMS int64) ([]ledger.GooglePaymentOrderRef, error) { + if r == nil || r.db == nil { + return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + if limit <= 0 { + return []ledger.GooglePaymentOrderRef{}, nil + } + if limit > 1000 { + limit = 1000 + } + rows, err := r.db.QueryContext(ctx, ` + SELECT app_code, payment_order_id, wallet_transaction_id, package_name, provider_order_id, + paid_synced_at_ms, paid_sync_attempt_count + FROM payment_orders + WHERE provider = ? AND paid_synced_at_ms = 0 AND paid_sync_next_retry_at_ms <= ? + ORDER BY created_at_ms DESC, payment_order_id DESC + LIMIT ?`, + ledger.PaymentProviderGooglePlay, nowMS, limit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + refs := make([]ledger.GooglePaymentOrderRef, 0, limit) + for rows.Next() { + var ref ledger.GooglePaymentOrderRef + if err := rows.Scan(&ref.AppCode, &ref.PaymentOrderID, &ref.TransactionID, &ref.PackageName, &ref.ProviderOrderID, &ref.PaidSyncedAtMS, &ref.SyncAttemptCount); err != nil { + return nil, err + } + refs = append(refs, ref) + } + return refs, rows.Err() +} + // UpdateGooglePaymentPaidDetails 把 Orders API 返回的实付明细写回 payment_orders;同步时间必须由调用方给出。 func (r *Repository) UpdateGooglePaymentPaidDetails(ctx context.Context, appCode string, paymentOrderID string, details ledger.GooglePaymentPaidDetails) error { if r == nil || r.db == nil { @@ -354,7 +397,8 @@ func (r *Repository) UpdateGooglePaymentPaidDetails(ctx context.Context, appCode } _, err := r.db.ExecContext(ctx, ` UPDATE payment_orders - SET paid_currency_code = ?, paid_amount_micro = ?, paid_tax_micro = ?, paid_net_micro = ?, paid_synced_at_ms = ?, updated_at_ms = ? + SET paid_currency_code = ?, paid_amount_micro = ?, paid_tax_micro = ?, paid_net_micro = ?, paid_synced_at_ms = ?, + paid_sync_attempt_count = 0, paid_sync_last_error = '', paid_sync_next_retry_at_ms = 0, updated_at_ms = ? WHERE app_code = ? AND payment_order_id = ? AND provider = ?`, strings.ToUpper(strings.TrimSpace(details.CurrencyCode)), details.PaidAmountMicro, @@ -369,6 +413,34 @@ func (r *Repository) UpdateGooglePaymentPaidDetails(ctx context.Context, appCode return err } +// MarkGooglePaymentPaidSyncFailure stores sanitized diagnostics and exponential retry time. +// It never overwrites a concurrently completed synchronization because paid_synced_at_ms=0 is required. +func (r *Repository) MarkGooglePaymentPaidSyncFailure(ctx context.Context, ref ledger.GooglePaymentOrderRef, message string, nowMS int64) error { + if r == nil || r.db == nil { + return xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + message = strings.TrimSpace(message) + if len(message) > 512 { + message = message[:512] + } + backoff := googlePaidSyncRetryBase + for attempt := 0; attempt < ref.SyncAttemptCount && backoff < googlePaidSyncRetryMax; attempt++ { + backoff *= 2 + if backoff > googlePaidSyncRetryMax { + backoff = googlePaidSyncRetryMax + } + } + _, err := r.db.ExecContext(ctx, ` + UPDATE payment_orders + SET paid_sync_attempt_count = paid_sync_attempt_count + 1, + paid_sync_last_error = ?, paid_sync_next_retry_at_ms = ?, updated_at_ms = ? + WHERE app_code = ? AND payment_order_id = ? AND provider = ? AND paid_synced_at_ms = 0`, + message, nowMS+backoff.Milliseconds(), nowMS, + strings.ToLower(strings.TrimSpace(ref.AppCode)), strings.TrimSpace(ref.PaymentOrderID), ledger.PaymentProviderGooglePlay, + ) + return err +} + type googlePaymentMetadata struct { AppCode string `json:"app_code"` PaymentOrderID string `json:"payment_order_id"` diff --git a/services/wallet-service/internal/storage/mysql/host_revenue_stats.go b/services/wallet-service/internal/storage/mysql/host_revenue_stats.go new file mode 100644 index 00000000..e4a0823d --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/host_revenue_stats.go @@ -0,0 +1,31 @@ +package mysql + +import ( + "context" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +// GetHostRevenueStats 聚合 POINT 分录;兑换按 available 侧冻结扣减计数,审核结算不会再次累计。 +func (r *Repository) GetHostRevenueStats(ctx context.Context, query ledger.HostRevenueStatsQuery) (ledger.HostRevenueStats, error) { + if r == nil || r.db == nil { + return ledger.HostRevenueStats{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + var stats ledger.HostRevenueStats + err := r.db.QueryRowContext(ctx, ` + SELECT + COALESCE(SUM(CASE WHEN e.available_delta > 0 AND t.biz_type <> ? THEN e.available_delta ELSE 0 END), 0), + COALESCE(SUM(CASE WHEN e.available_delta < 0 THEN -e.available_delta ELSE 0 END), 0), + COUNT(DISTINCT CASE WHEN e.available_delta > 0 AND e.counterparty_user_id > 0 THEN e.counterparty_user_id END) + FROM wallet_entries e + INNER JOIN wallet_transactions t + ON t.app_code = e.app_code AND t.transaction_id = e.transaction_id + WHERE e.app_code = ? AND e.user_id = ? AND e.asset_type = ? + AND e.created_at_ms >= ? AND e.created_at_ms < ?`, + bizTypeSalaryWithdrawalRelease, + appcode.FromContext(ctx), query.HostUserID, ledger.AssetPoint, query.StartAtMS, query.EndAtMS, + ).Scan(&stats.DiamondEarnings, &stats.DiamondExchanged, &stats.GiftSenders) + return stats, err +} diff --git a/services/wallet-service/internal/storage/mysql/host_revenue_stats_test.go b/services/wallet-service/internal/storage/mysql/host_revenue_stats_test.go new file mode 100644 index 00000000..426dacfc --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/host_revenue_stats_test.go @@ -0,0 +1,48 @@ +package mysql + +import ( + "context" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "hyapp/pkg/appcode" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +func TestGetHostRevenueStatsAggregatesPointLedger(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + + mock.ExpectQuery(regexp.QuoteMeta(` + SELECT + COALESCE(SUM(CASE WHEN e.available_delta > 0 AND t.biz_type <> ? THEN e.available_delta ELSE 0 END), 0), + COALESCE(SUM(CASE WHEN e.available_delta < 0 THEN -e.available_delta ELSE 0 END), 0), + COUNT(DISTINCT CASE WHEN e.available_delta > 0 AND e.counterparty_user_id > 0 THEN e.counterparty_user_id END) + FROM wallet_entries e + INNER JOIN wallet_transactions t + ON t.app_code = e.app_code AND t.transaction_id = e.transaction_id + WHERE e.app_code = ? AND e.user_id = ? AND e.asset_type = ? + AND e.created_at_ms >= ? AND e.created_at_ms < ?`)). + WithArgs(bizTypeSalaryWithdrawalRelease, "fami", int64(42), ledger.AssetPoint, int64(1000), int64(2000)). + WillReturnRows(sqlmock.NewRows([]string{"earnings", "exchanged", "senders"}).AddRow(int64(8800), int64(1200), int64(7))) + + repo := &Repository{db: db} + stats, err := repo.GetHostRevenueStats(appcode.WithContext(context.Background(), "fami"), ledger.HostRevenueStatsQuery{ + HostUserID: 42, + StartAtMS: 1000, + EndAtMS: 2000, + }) + if err != nil { + t.Fatalf("GetHostRevenueStats failed: %v", err) + } + if stats.DiamondEarnings != 8800 || stats.DiamondExchanged != 1200 || stats.GiftSenders != 7 { + t.Fatalf("unexpected host revenue stats: %+v", stats) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("unmet SQL expectations: %v", err) + } +} diff --git a/services/wallet-service/internal/storage/mysql/metadata.go b/services/wallet-service/internal/storage/mysql/metadata.go index 175b0489..4ec68068 100644 --- a/services/wallet-service/internal/storage/mysql/metadata.go +++ b/services/wallet-service/internal/storage/mysql/metadata.go @@ -27,9 +27,11 @@ type giftMetadata struct { // EntitlementID 只在背包送礼时存在,用于排查某次送礼具体消耗了哪条用户权益。 EntitlementID string `json:"entitlement_id,omitempty"` // GiftPointAdded 是历史收礼积分字段,新送礼固定写 0,保留 JSON 字段只为旧事件解析兼容。 - GiftPointAdded int64 `json:"gift_point_added"` - HeatValue int64 `json:"heat_value"` - BalanceAfter int64 `json:"balance_after"` + GiftPointAdded int64 `json:"gift_point_added"` + HeatValue int64 `json:"heat_value"` + BalanceAfter int64 `json:"balance_after"` + // BalanceVersion 与 BalanceAfter 一起固化 sender 账后快照;重试不能读取当前账户版本冒充首次结果。 + BalanceVersion int64 `json:"balance_version,omitempty"` GiftIncomeCoinAmount int64 `json:"gift_income_coin_amount"` GiftIncomeRatioPercent string `json:"gift_income_ratio_percent,omitempty"` GiftIncomeRatioRegionID int64 `json:"gift_income_ratio_region_id,omitempty"` @@ -54,6 +56,9 @@ type giftMetadata struct { HostPointTemplateCode string `json:"host_point_template_code,omitempty"` HostPointTemplateVersion string `json:"host_point_template_version,omitempty"` HostPointRatioPPM int64 `json:"host_point_ratio_ppm,omitempty"` + AgencyPointAdded int64 `json:"agency_point_added,omitempty"` + AgencyPointBalanceAfter int64 `json:"agency_point_balance_after,omitempty"` + AgencyPointRatioPPM int64 `json:"agency_point_ratio_ppm,omitempty"` PointsPerUSD int64 `json:"points_per_usd,omitempty"` RoomID string `json:"room_id"` RoomRegionID int64 `json:"room_region_id"` diff --git a/services/wallet-service/internal/storage/mysql/outbox.go b/services/wallet-service/internal/storage/mysql/outbox.go index 3232dfc7..79c0cd11 100644 --- a/services/wallet-service/internal/storage/mysql/outbox.go +++ b/services/wallet-service/internal/storage/mysql/outbox.go @@ -84,6 +84,29 @@ func (r *Repository) insertWalletOutbox(ctx context.Context, tx *sql.Tx, events return nil } +// ListWalletOutboxAppCodes enumerates the tenant key stored in this owner table. +// PRIMARY starts with app_code, so MySQL can use a loose ordered index scan; +// callers cache the result and never run this discovery query on every poll. +func (r *Repository) ListWalletOutboxAppCodes(ctx context.Context) ([]string, error) { + if r == nil || r.db == nil { + return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + rows, err := r.db.QueryContext(ctx, `SELECT DISTINCT app_code FROM wallet_outbox FORCE INDEX (PRIMARY) ORDER BY app_code`) + if err != nil { + return nil, err + } + defer rows.Close() + appCodes := make([]string, 0, 4) + for rows.Next() { + var appCode string + if err := rows.Scan(&appCode); err != nil { + return nil, err + } + appCodes = append(appCodes, appCode) + } + return appCodes, rows.Err() +} + // ClaimPendingWalletOutbox atomically claims committed wallet facts for MQ fanout. func (r *Repository) ClaimPendingWalletOutbox(ctx context.Context, workerID string, limit int, lockUntilMS int64) ([]WalletOutboxRecord, error) { return r.ClaimPendingWalletOutboxFiltered(ctx, workerID, limit, lockUntilMS, WalletOutboxClaimFilter{}) @@ -119,16 +142,14 @@ func (r *Repository) ClaimPendingWalletOutboxFiltered(ctx context.Context, worke defer func() { _ = tx.Rollback() }() records := make([]WalletOutboxRecord, 0, limit) + // Recovery work must claim before fresh pending facts. A continuously growing + // pending backlog can otherwise fill every batch and permanently strand both + // due retries and deliveries whose worker lease expired. Keep the states in + // separate SKIP LOCKED queries so each branch retains its status-leading index. claimBranches := []struct { query string args []any }{ - { - query: walletOutboxClaimQuery(pendingIndex, ` - AND next_retry_at_ms IS NULL - AND lock_until_ms IS NULL`, eventFilterSQL), - args: append([]any{appCode, outboxStatusPending}, eventFilterArgs...), - }, { query: walletOutboxClaimQuery(pendingIndex, ` AND next_retry_at_ms <= ? @@ -137,9 +158,15 @@ func (r *Repository) ClaimPendingWalletOutboxFiltered(ctx context.Context, worke }, { query: walletOutboxClaimQuery(claimIndex, ` - AND lock_until_ms <= ?`, eventFilterSQL), + AND lock_until_ms <= ?`, eventFilterSQL), args: append([]any{appCode, outboxStatusDelivering, nowMS}, eventFilterArgs...), }, + { + query: walletOutboxClaimQuery(pendingIndex, ` + AND next_retry_at_ms IS NULL + AND lock_until_ms IS NULL`, eventFilterSQL), + args: append([]any{appCode, outboxStatusPending}, eventFilterArgs...), + }, } for _, branch := range claimBranches { remaining := limit - len(records) @@ -350,6 +377,25 @@ func balanceChangedEvent(transactionID string, commandID string, userID int64, a if len(bizTypes) > 0 { bizType = strings.TrimSpace(bizTypes[0]) } + eventPayload := map[string]any{ + "transaction_id": transactionID, + "command_id": commandID, + "user_id": userID, + "asset_type": assetType, + "biz_type": bizType, + "available_delta": availableDelta, + "frozen_delta": frozenDelta, + "available_after": availableAfter, + "frozen_after": frozenAfter, + "balance_version": balanceVersion, + "metadata": payload, + "created_at_ms": nowMs, + } + if suppressWalletPrivateNotice(assetType, payload) { + // 抑制决策必须和账务事实一起固化,notice-service 只能消费这份不可变快照, + // 不能跨库读取用户 source 或根据当前资料反推交易发生时的账号语义。 + eventPayload["suppress_private_notice"] = true + } return walletOutboxEvent{ EventID: eventID(transactionID, "WalletBalanceChanged", userID, assetType), EventType: "WalletBalanceChanged", @@ -359,21 +405,24 @@ func balanceChangedEvent(transactionID string, commandID string, userID int64, a AssetType: assetType, AvailableDelta: availableDelta, FrozenDelta: frozenDelta, - Payload: map[string]any{ - "transaction_id": transactionID, - "command_id": commandID, - "user_id": userID, - "asset_type": assetType, - "biz_type": bizType, - "available_delta": availableDelta, - "frozen_delta": frozenDelta, - "available_after": availableAfter, - "frozen_after": frozenAfter, - "balance_version": balanceVersion, - "metadata": payload, - "created_at_ms": nowMs, - }, - CreatedAtMS: nowMs, + Payload: eventPayload, + CreatedAtMS: nowMs, + } +} + +func suppressWalletPrivateNotice(assetType string, payload any) bool { + if strings.EqualFold(strings.TrimSpace(assetType), ledger.AssetRobotCoin) { + return true + } + // giftMetadata.RobotGift 是 wallet 扣费时的 owner 快照;只认可强类型业务元数据, + // 不在通用 JSON/map 里猎猜 source、command_id 或 biz_type。 + switch metadata := payload.(type) { + case giftMetadata: + return metadata.RobotGift + case *giftMetadata: + return metadata != nil && metadata.RobotGift + default: + return false } } @@ -792,3 +841,37 @@ func hostPointCreditedEvent(transactionID string, commandID string, metadata gif CreatedAtMS: nowMs, } } + +func agencyPointCreditedEvent(transactionID string, commandID string, metadata giftMetadata, nowMs int64) walletOutboxEvent { + return walletOutboxEvent{ + EventID: eventID(transactionID, "AgencyPointCredited", metadata.TargetAgencyOwnerUserID, ledger.AssetPoint), + EventType: "AgencyPointCredited", + TransactionID: transactionID, + CommandID: commandID, + UserID: metadata.TargetAgencyOwnerUserID, + AssetType: ledger.AssetPoint, + AvailableDelta: metadata.AgencyPointAdded, + FrozenDelta: 0, + Payload: map[string]any{ + "transaction_id": transactionID, + "command_id": commandID, + "host_user_id": metadata.TargetUserID, + "agency_owner_user_id": metadata.TargetAgencyOwnerUserID, + "sender_user_id": metadata.SenderUserID, + "room_id": metadata.RoomID, + "asset_type": ledger.AssetPoint, + "point_delta": metadata.AgencyPointAdded, + "point_balance_after": metadata.AgencyPointBalanceAfter, + "gift_charge_asset_type": metadata.ChargeAssetType, + "gift_charge_amount": metadata.ChargeAmount, + "gift_id": metadata.GiftID, + "gift_count": metadata.GiftCount, + "policy_instance_code": metadata.HostPointPolicyInstanceCode, + "policy_template_code": metadata.HostPointTemplateCode, + "policy_template_version": metadata.HostPointTemplateVersion, + "agency_point_ratio_ppm": metadata.AgencyPointRatioPPM, + "created_at_ms": nowMs, + }, + CreatedAtMS: nowMs, + } +} diff --git a/services/wallet-service/internal/storage/mysql/outbox_app_codes_test.go b/services/wallet-service/internal/storage/mysql/outbox_app_codes_test.go new file mode 100644 index 00000000..f9796929 --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/outbox_app_codes_test.go @@ -0,0 +1,30 @@ +package mysql + +import ( + "context" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestListWalletOutboxAppCodesForcesPrimaryPrefixIndex(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT app_code FROM wallet_outbox FORCE INDEX (PRIMARY) ORDER BY app_code`)). + WillReturnRows(sqlmock.NewRows([]string{"app_code"}).AddRow("fami").AddRow("huwaa").AddRow("lalu")) + + apps, err := (&Repository{db: db}).ListWalletOutboxAppCodes(context.Background()) + if err != nil { + t.Fatalf("list wallet outbox apps: %v", err) + } + if len(apps) != 3 || apps[0] != "fami" || apps[1] != "huwaa" || apps[2] != "lalu" { + t.Fatalf("wallet outbox apps mismatch: %v", apps) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("wallet app discovery query mismatch: %v", err) + } +} diff --git a/services/wallet-service/internal/storage/mysql/outbox_claim_priority_test.go b/services/wallet-service/internal/storage/mysql/outbox_claim_priority_test.go new file mode 100644 index 00000000..a23de4ff --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/outbox_claim_priority_test.go @@ -0,0 +1,83 @@ +package mysql + +import ( + "context" + "testing" + "time" + + "github.com/DATA-DOG/go-sqlmock" + "hyapp/pkg/appcode" +) + +func TestClaimPendingWalletOutboxPrioritizesRecoveryOverPendingAtBatchOne(t *testing.T) { + tests := []struct { + name string + eventID string + recoveryStatus string + expectEmptyRetry bool + }{ + {name: "due retry", eventID: "wallet-retry", recoveryStatus: outboxStatusRetryable}, + {name: "expired delivery", eventID: "wallet-expired", recoveryStatus: outboxStatusDelivering, expectEmptyRetry: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + + mock.ExpectBegin() + if tt.expectEmptyRetry { + mock.ExpectQuery(walletOutboxRetryClaimPattern()). + WithArgs("lalu", outboxStatusRetryable, sqlmock.AnyArg(), 1). + WillReturnRows(walletOutboxClaimRows()) + } + claimPattern := walletOutboxRetryClaimPattern() + if tt.recoveryStatus == outboxStatusDelivering { + claimPattern = walletOutboxExpiredClaimPattern() + } + mock.ExpectQuery(claimPattern). + WithArgs("lalu", tt.recoveryStatus, sqlmock.AnyArg(), 1). + WillReturnRows(walletOutboxClaimRows().AddRow( + "lalu", tt.eventID, "WalletBalanceChanged", "tx-1", "cmd-1", int64(1001), + "COIN", int64(1), int64(0), `{}`, int64(200), 0, "", "old-worker", int64(100), + )) + lockUntilMS := time.Now().UTC().Add(time.Minute).UnixMilli() + mock.ExpectExec(`(?s)UPDATE wallet_outbox\s+SET status = \?, worker_id = \?, lock_until_ms = \?, updated_at_ms = \?\s+WHERE app_code = \? AND event_id = \?`). + WithArgs(outboxStatusDelivering, "worker-1", lockUntilMS, sqlmock.AnyArg(), "lalu", tt.eventID). + WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectCommit() + + records, err := (&Repository{db: db}).ClaimPendingWalletOutbox( + appcode.WithContext(context.Background(), "lalu"), "worker-1", 1, lockUntilMS, + ) + if err != nil { + t.Fatalf("claim wallet recovery row: %v", err) + } + if len(records) != 1 || records[0].EventID != tt.eventID { + t.Fatalf("batch=1 must claim %s before an older pending row: %+v", tt.recoveryStatus, records) + } + // No pending query is registered: issuing one after the recovery row filled + // the batch makes sqlmock fail, protecting the starvation boundary directly. + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("wallet claim priority mismatch: %v", err) + } + }) + } +} + +func walletOutboxClaimRows() *sqlmock.Rows { + return sqlmock.NewRows([]string{ + "app_code", "event_id", "event_type", "transaction_id", "command_id", "user_id", "asset_type", + "available_delta", "frozen_delta", "payload", "created_at_ms", "retry_count", "last_error", "worker_id", "lock_until_ms", + }) +} + +func walletOutboxRetryClaimPattern() string { + return `(?s)FROM wallet_outbox FORCE INDEX \(idx_wallet_outbox_pending\).*WHERE app_code = \? AND status = \?\s+AND next_retry_at_ms <= \?\s+AND lock_until_ms IS NULL.*FOR UPDATE SKIP LOCKED` +} + +func walletOutboxExpiredClaimPattern() string { + return `(?s)FROM wallet_outbox FORCE INDEX \(idx_wallet_outbox_claim\).*WHERE app_code = \? AND status = \?\s+AND lock_until_ms <= \?.*FOR UPDATE SKIP LOCKED` +} diff --git a/services/wallet-service/internal/storage/mysql/outbox_private_notice_test.go b/services/wallet-service/internal/storage/mysql/outbox_private_notice_test.go new file mode 100644 index 00000000..195fc235 --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/outbox_private_notice_test.go @@ -0,0 +1,69 @@ +package mysql + +import ( + "testing" + + "hyapp/services/wallet-service/internal/domain/ledger" +) + +func TestBalanceChangedEventPersistsPrivateNoticeSuppressionSnapshot(t *testing.T) { + event := balanceChangedEvent( + "tx-robot-1", + "cmd-robot-1", + 100001, + // COIN 刻意不依赖 ROBOT_COIN 兼容分支,证明强类型 giftMetadata.RobotGift 本身能固化决策。 + ledger.AssetCoin, + -10, + 0, + 90, + 0, + 2, + giftMetadata{RobotGift: true}, + 1710000000000, + bizTypeRobotGiftDebit, + ) + payload, ok := event.Payload.(map[string]any) + if !ok { + t.Fatalf("payload type = %T, want map[string]any", event.Payload) + } + if suppressed, ok := payload["suppress_private_notice"].(bool); !ok || !suppressed { + t.Fatalf("suppress_private_notice = %#v, want true", payload["suppress_private_notice"]) + } + legacyRobotAsset := balanceChangedEvent( + "tx-robot-asset-1", + "cmd-robot-asset-1", + 100001, + ledger.AssetRobotCoin, + -10, + 0, + 90, + 0, + 2, + struct{}{}, + 1710000000000, + bizTypeRobotGiftDebit, + ) + legacyRobotPayload := legacyRobotAsset.Payload.(map[string]any) + if suppressed, ok := legacyRobotPayload["suppress_private_notice"].(bool); !ok || !suppressed { + t.Fatalf("ROBOT_COIN compatibility suppression = %#v, want true", legacyRobotPayload["suppress_private_notice"]) + } + + human := balanceChangedEvent( + "tx-human-1", + "cmd-human-1", + 100002, + ledger.AssetCoin, + -10, + 0, + 90, + 0, + 2, + giftMetadata{}, + 1710000000000, + bizTypeGiftDebit, + ) + humanPayload := human.Payload.(map[string]any) + if _, exists := humanPayload["suppress_private_notice"]; exists { + t.Fatalf("human wallet event must not persist suppression control: %#v", humanPayload) + } +} diff --git a/services/wallet-service/internal/storage/mysql/point_coin_seller.go b/services/wallet-service/internal/storage/mysql/point_coin_seller.go new file mode 100644 index 00000000..37a0156e --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/point_coin_seller.go @@ -0,0 +1,268 @@ +package mysql + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +const pointWithdrawalMinimumPoints int64 = 1_000_000 + +type pointToCoinSellerMetadata struct { + AppCode string `json:"app_code"` + SourceUserID int64 `json:"source_user_id"` + SellerUserID int64 `json:"seller_user_id"` + SourceCountryCode string `json:"source_country_code"` + SourceAssetType string `json:"source_asset_type"` + SellerAssetType string `json:"seller_asset_type"` + PointAmount int64 `json:"point_amount"` + SellerCoinAmount int64 `json:"seller_coin_amount"` + RatioPointAmount int64 `json:"ratio_point_amount"` + RatioSellerCoinAmount int64 `json:"ratio_seller_coin_amount"` + SourcePointBalanceAfter int64 `json:"source_point_balance_after"` + SellerBalanceAfter int64 `json:"seller_balance_after"` + Reason string `json:"reason"` + CreatedAtMS int64 `json:"created_at_ms"` +} + +// ListPointWithdrawalCoinSellers 返回当前 App 的白名单;国家过滤在 wallet 内执行,gateway 不能绕过服务范围。 +func (r *Repository) ListPointWithdrawalCoinSellers(ctx context.Context, appCode string, countryCode string, includeDisabled bool) ([]ledger.PointWithdrawalCoinSellerConfig, error) { + if r == nil || r.db == nil { + return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, appCode) + where := "app_code = ?" + args := []any{appcode.FromContext(ctx)} + if !includeDisabled { + where += " AND status = 'active'" + } + rows, err := r.db.QueryContext(ctx, ` + SELECT app_code, seller_user_id, sort_order, point_amount, seller_coin_amount, + service_country_codes, status, created_at_ms, updated_at_ms + FROM point_withdrawal_coin_seller_configs + WHERE `+where+` + ORDER BY sort_order ASC, seller_user_id ASC`, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + countryCode = strings.ToUpper(strings.TrimSpace(countryCode)) + items := make([]ledger.PointWithdrawalCoinSellerConfig, 0) + for rows.Next() { + var item ledger.PointWithdrawalCoinSellerConfig + var countriesJSON []byte + if err := rows.Scan(&item.AppCode, &item.SellerUserID, &item.SortOrder, &item.PointAmount, &item.SellerCoinAmount, &countriesJSON, &item.Status, &item.CreatedAtMS, &item.UpdatedAtMS); err != nil { + return nil, err + } + if err := json.Unmarshal(countriesJSON, &item.ServiceCountryCodes); err != nil { + return nil, xerr.New(xerr.Internal, "point withdrawal seller country config is invalid") + } + item.ServiceCountryCodes = normalizeCountryCodes(item.ServiceCountryCodes) + if item.PointAmount <= 0 || item.SellerCoinAmount <= 0 { + return nil, xerr.New(xerr.Internal, "point withdrawal seller ratio is invalid") + } + if countryCode != "" && !countryAllowed(item.ServiceCountryCodes, countryCode) { + continue + } + items = append(items, item) + } + return items, rows.Err() +} + +// TransferPointToCoinSeller 在一个事务中锁定白名单配置和双方账户;任何比例更新只影响新 command_id。 +func (r *Repository) TransferPointToCoinSeller(ctx context.Context, command ledger.PointToCoinSellerCommand) (ledger.PointToCoinSellerReceipt, error) { + if r == nil || r.db == nil { + return ledger.PointToCoinSellerReceipt{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, command.AppCode) + command.AppCode = appcode.FromContext(ctx) + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return ledger.PointToCoinSellerReceipt{}, err + } + defer func() { _ = tx.Rollback() }() + + requestHash := stableHash(fmt.Sprintf("point_transfer_coin_seller|%s|%d|%d|%d|%s|%s", + command.AppCode, command.SourceUserID, command.SellerUserID, command.PointAmount, + strings.ToUpper(strings.TrimSpace(command.SourceCountryCode)), strings.TrimSpace(command.Reason))) + if txRow, exists, lookupErr := r.lookupTransactionWithConflictCode(ctx, tx, command.CommandID, requestHash, bizTypePointTransferToCoinSeller, xerr.IdempotencyConflict); lookupErr != nil || exists { + if lookupErr != nil || !exists { + return ledger.PointToCoinSellerReceipt{}, lookupErr + } + return r.receiptForPointToCoinSellerTransaction(ctx, tx, txRow.TransactionID) + } + + config, err := r.lockPointWithdrawalCoinSellerConfig(ctx, tx, command.SellerUserID) + if err != nil { + return ledger.PointToCoinSellerReceipt{}, err + } + countryCode := strings.ToUpper(strings.TrimSpace(command.SourceCountryCode)) + if !countryAllowed(config.ServiceCountryCodes, countryCode) { + return ledger.PointToCoinSellerReceipt{}, xerr.New(xerr.PermissionDenied, "coin seller does not serve user country") + } + numerator, err := checkedMul(command.PointAmount, config.SellerCoinAmount) + if err != nil { + return ledger.PointToCoinSellerReceipt{}, err + } + sellerCoinAmount := numerator / config.PointAmount + if sellerCoinAmount <= 0 { + return ledger.PointToCoinSellerReceipt{}, xerr.New(xerr.InvalidArgument, "point amount is below exchange precision") + } + + nowMS := time.Now().UnixMilli() + source, err := r.lockAccount(ctx, tx, command.SourceUserID, ledger.AssetPoint, false, nowMS) + if err != nil { + return ledger.PointToCoinSellerReceipt{}, err + } + if source.AvailableAmount < command.PointAmount { + return ledger.PointToCoinSellerReceipt{}, xerr.New(xerr.InsufficientBalance, "insufficient POINT balance") + } + seller, err := r.lockAccount(ctx, tx, command.SellerUserID, ledger.AssetCoinSellerCoin, true, nowMS) + if err != nil { + return ledger.PointToCoinSellerReceipt{}, err + } + sourceAfter := source.AvailableAmount - command.PointAmount + sellerAfter, err := checkedAdd(seller.AvailableAmount, sellerCoinAmount) + if err != nil { + return ledger.PointToCoinSellerReceipt{}, err + } + transactionID := transactionID(command.AppCode, command.CommandID) + metadata := pointToCoinSellerMetadata{ + AppCode: command.AppCode, SourceUserID: command.SourceUserID, SellerUserID: command.SellerUserID, + SourceCountryCode: countryCode, SourceAssetType: ledger.AssetPoint, SellerAssetType: ledger.AssetCoinSellerCoin, + PointAmount: command.PointAmount, SellerCoinAmount: sellerCoinAmount, + RatioPointAmount: config.PointAmount, RatioSellerCoinAmount: config.SellerCoinAmount, + SourcePointBalanceAfter: sourceAfter, SellerBalanceAfter: sellerAfter, Reason: command.Reason, CreatedAtMS: nowMS, + } + if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypePointTransferToCoinSeller, requestHash, fmt.Sprintf("point_coin_seller:%d:%d", command.SourceUserID, command.SellerUserID), metadata, nowMS); err != nil { + return ledger.PointToCoinSellerReceipt{}, err + } + if err := r.applyAccountDelta(ctx, tx, source, -command.PointAmount, 0, nowMS); err != nil { + return ledger.PointToCoinSellerReceipt{}, err + } + if err := r.insertEntry(ctx, tx, walletEntry{TransactionID: transactionID, UserID: command.SourceUserID, AssetType: ledger.AssetPoint, AvailableDelta: -command.PointAmount, AvailableAfter: sourceAfter, FrozenAfter: source.FrozenAmount, CounterpartyUserID: command.SellerUserID, CreatedAtMS: nowMS}); err != nil { + return ledger.PointToCoinSellerReceipt{}, err + } + if err := r.applyAccountDelta(ctx, tx, seller, sellerCoinAmount, 0, nowMS); err != nil { + return ledger.PointToCoinSellerReceipt{}, err + } + if err := r.insertEntry(ctx, tx, walletEntry{TransactionID: transactionID, UserID: command.SellerUserID, AssetType: ledger.AssetCoinSellerCoin, AvailableDelta: sellerCoinAmount, AvailableAfter: sellerAfter, FrozenAfter: seller.FrozenAmount, CounterpartyUserID: command.SourceUserID, CreatedAtMS: nowMS}); err != nil { + return ledger.PointToCoinSellerReceipt{}, err + } + if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{ + balanceChangedEvent(transactionID, command.CommandID, command.SourceUserID, ledger.AssetPoint, -command.PointAmount, 0, sourceAfter, source.FrozenAmount, source.Version+1, metadata, nowMS, bizTypePointTransferToCoinSeller), + balanceChangedEvent(transactionID, command.CommandID, command.SellerUserID, ledger.AssetCoinSellerCoin, sellerCoinAmount, 0, sellerAfter, seller.FrozenAmount, seller.Version+1, metadata, nowMS, bizTypePointTransferToCoinSeller), + }); err != nil { + return ledger.PointToCoinSellerReceipt{}, err + } + if err := tx.Commit(); err != nil { + return ledger.PointToCoinSellerReceipt{}, err + } + return receiptFromPointToCoinSellerMetadata(transactionID, metadata), nil +} + +// GetPointWithdrawalConfig 只读取已发布钱包政策;不存在时显式 found=false,gateway 不回退到猜测参数。 +func (r *Repository) GetPointWithdrawalConfig(ctx context.Context, appCode string, regionID int64, nowMS int64) (ledger.PointWithdrawalRuntimeConfig, error) { + if r == nil || r.db == nil { + return ledger.PointWithdrawalRuntimeConfig{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, appCode) + tx, err := r.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return ledger.PointWithdrawalRuntimeConfig{}, err + } + defer func() { _ = tx.Rollback() }() + policy, found, err := r.resolveActiveWalletPolicy(ctx, tx, appcode.FromContext(ctx), regionID, nowMS) + if err != nil || !found { + return ledger.PointWithdrawalRuntimeConfig{Found: false}, err + } + return ledger.PointWithdrawalRuntimeConfig{Found: true, PointsPerUSD: policy.PointsPerUSD, FeeBPS: policy.WithdrawFeeBPS, MinimumPoints: pointWithdrawalMinimumPoints, PolicyInstanceCode: policy.InstanceCode}, nil +} + +func (r *Repository) lockPointWithdrawalCoinSellerConfig(ctx context.Context, tx *sql.Tx, sellerUserID int64) (ledger.PointWithdrawalCoinSellerConfig, error) { + var item ledger.PointWithdrawalCoinSellerConfig + var countriesJSON []byte + err := tx.QueryRowContext(ctx, ` + SELECT app_code, seller_user_id, sort_order, point_amount, seller_coin_amount, + service_country_codes, status, created_at_ms, updated_at_ms + FROM point_withdrawal_coin_seller_configs + WHERE app_code = ? AND seller_user_id = ? AND status = 'active' + FOR UPDATE`, appcode.FromContext(ctx), sellerUserID).Scan( + &item.AppCode, &item.SellerUserID, &item.SortOrder, &item.PointAmount, &item.SellerCoinAmount, + &countriesJSON, &item.Status, &item.CreatedAtMS, &item.UpdatedAtMS, + ) + if errors.Is(err, sql.ErrNoRows) { + return item, xerr.New(xerr.NotFound, "point withdrawal coin seller is not available") + } + if err != nil { + return item, err + } + if err := json.Unmarshal(countriesJSON, &item.ServiceCountryCodes); err != nil || item.PointAmount <= 0 || item.SellerCoinAmount <= 0 { + return item, xerr.New(xerr.Internal, "point withdrawal coin seller config is invalid") + } + item.ServiceCountryCodes = normalizeCountryCodes(item.ServiceCountryCodes) + return item, nil +} + +func (r *Repository) receiptForPointToCoinSellerTransaction(ctx context.Context, tx *sql.Tx, transactionID string) (ledger.PointToCoinSellerReceipt, error) { + var metadataJSON string + if err := tx.QueryRowContext(ctx, `SELECT COALESCE(CAST(metadata_json AS CHAR), '{}') FROM wallet_transactions WHERE app_code = ? AND transaction_id = ?`, appcode.FromContext(ctx), transactionID).Scan(&metadataJSON); err != nil { + return ledger.PointToCoinSellerReceipt{}, err + } + var metadata pointToCoinSellerMetadata + if err := json.Unmarshal([]byte(metadataJSON), &metadata); err != nil { + return ledger.PointToCoinSellerReceipt{}, err + } + return receiptFromPointToCoinSellerMetadata(transactionID, metadata), nil +} + +func receiptFromPointToCoinSellerMetadata(transactionID string, metadata pointToCoinSellerMetadata) ledger.PointToCoinSellerReceipt { + return ledger.PointToCoinSellerReceipt{ + TransactionID: transactionID, SourceUserID: metadata.SourceUserID, SellerUserID: metadata.SellerUserID, + SourcePointBalanceAfter: metadata.SourcePointBalanceAfter, SellerBalanceAfter: metadata.SellerBalanceAfter, + PointAmount: metadata.PointAmount, SellerCoinAmount: metadata.SellerCoinAmount, + RatioPointAmount: metadata.RatioPointAmount, RatioSellerCoinAmount: metadata.RatioSellerCoinAmount, CreatedAtMS: metadata.CreatedAtMS, + } +} + +func normalizeCountryCodes(values []string) []string { + seen := make(map[string]struct{}, len(values)) + out := make([]string, 0, len(values)) + for _, value := range values { + value = strings.ToUpper(strings.TrimSpace(value)) + if value == "" { + continue + } + if _, exists := seen[value]; exists { + continue + } + seen[value] = struct{}{} + out = append(out, value) + } + return out +} + +func countryAllowed(configured []string, countryCode string) bool { + if len(configured) == 0 { + return true + } + countryCode = strings.ToUpper(strings.TrimSpace(countryCode)) + if countryCode == "" { + return false + } + for _, configuredCode := range configured { + if strings.EqualFold(configuredCode, countryCode) { + return true + } + } + return false +} diff --git a/services/wallet-service/internal/storage/mysql/policy_instance.go b/services/wallet-service/internal/storage/mysql/policy_instance.go index 8cefd734..47a518bd 100644 --- a/services/wallet-service/internal/storage/mysql/policy_instance.go +++ b/services/wallet-service/internal/storage/mysql/policy_instance.go @@ -12,14 +12,15 @@ import ( ) type walletPolicyInstance struct { - InstanceCode string - TemplateCode string - TemplateVersion string - RegionID int64 - HostPointAsset string - HostPointRatioPPM int64 - PointsPerUSD int64 - WithdrawFeeBPS int64 + InstanceCode string + TemplateCode string + TemplateVersion string + RegionID int64 + HostPointAsset string + HostPointRatioPPM int64 + AgencyPointRatioPPM int64 + PointsPerUSD int64 + WithdrawFeeBPS int64 } func (r *Repository) resolveActiveWalletPolicy(ctx context.Context, tx *sql.Tx, appCode string, regionID int64, nowMS int64) (walletPolicyInstance, bool, error) { @@ -29,7 +30,7 @@ func (r *Repository) resolveActiveWalletPolicy(ctx context.Context, tx *sql.Tx, } row := tx.QueryRowContext(ctx, ` SELECT instance_code, template_code, template_version, region_id, host_point_asset_type, - host_point_ratio_ppm, points_per_usd, withdraw_fee_bps + host_point_ratio_ppm, agency_point_ratio_ppm, points_per_usd, withdraw_fee_bps FROM wallet_policy_instances WHERE app_code = ? AND region_id IN (?, 0) @@ -41,7 +42,7 @@ func (r *Repository) resolveActiveWalletPolicy(ctx context.Context, tx *sql.Tx, appCode, regionID, nowMS, nowMS, regionID, ) var item walletPolicyInstance - if err := row.Scan(&item.InstanceCode, &item.TemplateCode, &item.TemplateVersion, &item.RegionID, &item.HostPointAsset, &item.HostPointRatioPPM, &item.PointsPerUSD, &item.WithdrawFeeBPS); err != nil { + if err := row.Scan(&item.InstanceCode, &item.TemplateCode, &item.TemplateVersion, &item.RegionID, &item.HostPointAsset, &item.HostPointRatioPPM, &item.AgencyPointRatioPPM, &item.PointsPerUSD, &item.WithdrawFeeBPS); err != nil { if errors.Is(err, sql.ErrNoRows) { return walletPolicyInstance{}, false, nil } @@ -51,7 +52,7 @@ func (r *Repository) resolveActiveWalletPolicy(ctx context.Context, tx *sql.Tx, if item.HostPointAsset == "" { item.HostPointAsset = ledger.AssetPoint } - if item.HostPointAsset != ledger.AssetPoint || item.HostPointRatioPPM <= 0 || item.HostPointRatioPPM > 1_000_000 || item.PointsPerUSD <= 0 || item.WithdrawFeeBPS < 0 || item.WithdrawFeeBPS > 10_000 { + if item.HostPointAsset != ledger.AssetPoint || item.HostPointRatioPPM <= 0 || item.HostPointRatioPPM > 1_000_000 || item.AgencyPointRatioPPM < 0 || item.AgencyPointRatioPPM > 1_000_000 || item.HostPointRatioPPM+item.AgencyPointRatioPPM > 1_000_000 || item.PointsPerUSD <= 0 || item.WithdrawFeeBPS < 0 || item.WithdrawFeeBPS > 10_000 { return walletPolicyInstance{}, false, xerr.New(xerr.InvalidArgument, "wallet policy instance is invalid") } return item, true, nil diff --git a/services/wallet-service/internal/storage/mysql/receipts.go b/services/wallet-service/internal/storage/mysql/receipts.go index 990c6e33..5b59f041 100644 --- a/services/wallet-service/internal/storage/mysql/receipts.go +++ b/services/wallet-service/internal/storage/mysql/receipts.go @@ -54,6 +54,7 @@ func receiptFromGiftMetadata(transactionID string, metadata giftMetadata) ledger GiftEffectTypes: normalizeGiftEffectTypes(metadata.GiftEffectTypes), PriceVersion: metadata.PriceVersion, BalanceAfter: metadata.BalanceAfter, + BalanceVersion: metadata.BalanceVersion, HostPeriodDiamondAdded: metadata.HostPeriodDiamondAdded, HostPeriodCycleKey: metadata.HostPeriodCycleKey, HostPointAdded: metadata.HostPointAdded, diff --git a/services/wallet-service/internal/storage/mysql/recharge_stats_repository.go b/services/wallet-service/internal/storage/mysql/recharge_stats_repository.go new file mode 100644 index 00000000..93576c9a --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/recharge_stats_repository.go @@ -0,0 +1,73 @@ +package mysql + +import ( + "context" + "fmt" + + "hyapp/pkg/appcode" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +const maxBatchRechargeStatsUsers = 500 + +// BatchGetUserRechargeStats 批量读取 owner 维护的累计表;输入先去重,避免 IN 参数和返回行无意义膨胀。 +func (r *Repository) BatchGetUserRechargeStats(ctx context.Context, userIDs []int64) ([]ledger.UserRechargeStats, error) { + userIDs = uniquePositiveUserIDs(userIDs) + if len(userIDs) == 0 { + return []ledger.UserRechargeStats{}, nil + } + if len(userIDs) > maxBatchRechargeStatsUsers { + return nil, fmt.Errorf("too many user ids: %d", len(userIDs)) + } + args := make([]any, 0, len(userIDs)+1) + args = append(args, appcode.FromContext(ctx)) + for _, userID := range userIDs { + args = append(args, userID) + } + rows, err := r.db.QueryContext(ctx, ` + SELECT user_id, recharge_count, total_coin_amount, total_usd_minor_amount, + first_recharged_at_ms, updated_at_ms + FROM wallet_user_recharge_stats + WHERE app_code = ? AND user_id IN (`+placeholders(len(userIDs))+`) + ORDER BY user_id ASC`, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + items := make([]ledger.UserRechargeStats, 0, len(userIDs)) + for rows.Next() { + var item ledger.UserRechargeStats + if err := rows.Scan( + &item.UserID, + &item.RechargeCount, + &item.TotalCoinAmount, + &item.TotalUSDMinorAmount, + &item.FirstRechargedAtMS, + &item.UpdatedAtMS, + ); err != nil { + return nil, err + } + items = append(items, item) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +func uniquePositiveUserIDs(values []int64) []int64 { + seen := make(map[int64]struct{}, len(values)) + result := make([]int64, 0, len(values)) + for _, value := range values { + if value <= 0 { + continue + } + if _, ok := seen[value]; ok { + continue + } + seen[value] = struct{}{} + result = append(result, value) + } + return result +} diff --git a/services/wallet-service/internal/storage/mysql/recharge_stats_repository_test.go b/services/wallet-service/internal/storage/mysql/recharge_stats_repository_test.go new file mode 100644 index 00000000..4c973a46 --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/recharge_stats_repository_test.go @@ -0,0 +1,41 @@ +package mysql + +import ( + "context" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "hyapp/pkg/appcode" +) + +func TestBatchGetUserRechargeStatsUsesAggregateTableAndDeduplicatesIDs(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + + mock.ExpectQuery(regexp.QuoteMeta(` + SELECT user_id, recharge_count, total_coin_amount, total_usd_minor_amount, + first_recharged_at_ms, updated_at_ms + FROM wallet_user_recharge_stats + WHERE app_code = ? AND user_id IN (?,?) + ORDER BY user_id ASC`)). + WithArgs("lalu", int64(1001), int64(1002)). + WillReturnRows(sqlmock.NewRows([]string{ + "user_id", "recharge_count", "total_coin_amount", "total_usd_minor_amount", "first_recharged_at_ms", "updated_at_ms", + }).AddRow(int64(1001), int64(3), int64(9000), int64(1250), int64(1700000000000), int64(1700000005000))) + + repo := &Repository{db: db} + items, err := repo.BatchGetUserRechargeStats(appcode.WithContext(context.Background(), "lalu"), []int64{1001, 0, 1002, 1001}) + if err != nil { + t.Fatalf("BatchGetUserRechargeStats failed: %v", err) + } + if len(items) != 1 || items[0].UserID != 1001 || items[0].TotalUSDMinorAmount != 1250 || items[0].RechargeCount != 3 { + t.Fatalf("unexpected recharge stats: %+v", items) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("unmet sql expectations: %v", err) + } +} diff --git a/services/wallet-service/internal/storage/mysql/repository.go b/services/wallet-service/internal/storage/mysql/repository.go index edca9b97..27258f6d 100644 --- a/services/wallet-service/internal/storage/mysql/repository.go +++ b/services/wallet-service/internal/storage/mysql/repository.go @@ -71,6 +71,10 @@ func Open(ctx context.Context, dsn string) (*Repository, error) { _ = db.Close() return nil, err } + if err := ensurePointWithdrawalCoinSellerSchema(ctx, db); err != nil { + _ = db.Close() + return nil, err + } return &Repository{db: db}, nil } diff --git a/services/wallet-service/internal/storage/mysql/resource_entitlement_repository.go b/services/wallet-service/internal/storage/mysql/resource_entitlement_repository.go index 36020310..6342f8b0 100644 --- a/services/wallet-service/internal/storage/mysql/resource_entitlement_repository.go +++ b/services/wallet-service/internal/storage/mysql/resource_entitlement_repository.go @@ -84,6 +84,10 @@ func (r *Repository) EquipUserResource(ctx context.Context, command resourcedoma if err != nil { return resourcedomain.UserResourceEntitlement{}, err } + if resourcedomain.NormalizeResourceType(entitlement.Resource.ResourceType) == resourcedomain.TypeVIPTrialCard { + // 只有 service 的通用背包适配层可以把体验卡委托给专用状态机;仓储直写会绕过 program 开关和 VIP outbox。 + return resourcedomain.UserResourceEntitlement{}, xerr.New(xerr.InvalidArgument, "vip_trial_card must use the VIP trial-card state machine") + } if !isEquipableResourceType(entitlement.Resource.ResourceType) { return resourcedomain.UserResourceEntitlement{}, xerr.New(xerr.InvalidArgument, "resource_type cannot be equipped") } @@ -122,6 +126,10 @@ func (r *Repository) UnequipUserResource(ctx context.Context, command resourcedo if command.UserID <= 0 || command.ResourceType == "" { return resourcedomain.UnequipUserResourceResult{}, xerr.New(xerr.InvalidArgument, "user_id and resource_type are required") } + if command.ResourceType == resourcedomain.TypeVIPTrialCard { + // 防御性拒绝仓储直写;公开通用入口会在 service 层委托 UnequipVipTrialCard 并发布完整状态事件。 + return resourcedomain.UnequipUserResourceResult{}, xerr.New(xerr.InvalidArgument, "vip_trial_card must use the VIP trial-card state machine") + } if !resourcedomain.ValidResourceType(command.ResourceType) || !isEquipableResourceType(command.ResourceType) { return resourcedomain.UnequipUserResourceResult{}, xerr.New(xerr.InvalidArgument, "resource_type cannot be equipped") } @@ -599,7 +607,7 @@ func (r *Repository) pruneExpiredUserResourceEquipment(ctx context.Context, user func isEquipableResourceType(resourceType string) bool { switch resourcedomain.NormalizeResourceType(resourceType) { - case resourcedomain.TypeAvatarFrame, resourcedomain.TypeProfileCard, resourcedomain.TypeVehicle, resourcedomain.TypeChatBubble, resourcedomain.TypeBadge, resourcedomain.TypeMicSeatAnimation: + case resourcedomain.TypeAvatarFrame, resourcedomain.TypeProfileCard, resourcedomain.TypeVehicle, resourcedomain.TypeChatBubble, resourcedomain.TypeBadge, resourcedomain.TypeMicSeatAnimation, resourcedomain.TypeVIPTrialCard: return true default: return false diff --git a/services/wallet-service/internal/storage/mysql/resource_grant_repository.go b/services/wallet-service/internal/storage/mysql/resource_grant_repository.go index 1b883c60..9d42a33e 100644 --- a/services/wallet-service/internal/storage/mysql/resource_grant_repository.go +++ b/services/wallet-service/internal/storage/mysql/resource_grant_repository.go @@ -8,6 +8,7 @@ import ( "fmt" "hyapp/pkg/appcode" "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" resourcedomain "hyapp/services/wallet-service/internal/domain/resource" "strconv" "strings" @@ -44,6 +45,9 @@ func (r *Repository) GrantResource(ctx context.Context, command resourcedomain.G if err != nil { return resourcedomain.ResourceGrant{}, err } + if resourcedomain.NormalizeResourceType(resource.ResourceType) == resourcedomain.TypeVIPTrialCard { + return resourcedomain.ResourceGrant{}, xerr.New(xerr.InvalidArgument, "vip_trial_card must use GrantVipTrialCard") + } if err := validateGrantableResourceForSource(resource, command.GrantSource); err != nil { return resourcedomain.ResourceGrant{}, err } @@ -119,6 +123,9 @@ func (r *Repository) GrantResourceGroup(ctx context.Context, command resourcedom } continue } + if resourcedomain.NormalizeResourceType(item.Resource.ResourceType) == resourcedomain.TypeVIPTrialCard { + return resourcedomain.ResourceGrant{}, xerr.New(xerr.InvalidArgument, "resource group cannot grant vip_trial_card") + } if err := validateActiveResourceGroupResource(item.Resource); err != nil { return resourcedomain.ResourceGrant{}, err } @@ -580,6 +587,17 @@ func (r *Repository) revokeGrantedEntitlement(ctx context.Context, tx *sql.Tx, g ); err != nil { return walletOutboxEvent{}, err } + // VIP 体验卡的业务元数据与通用 entitlement 同事务撤销;否则后台审计会残留 active 卡片, + // 虽然状态查询因 entitlement 失效不会继续授权,仍会形成两套互相矛盾的事实。 + if _, err := tx.ExecContext(ctx, ` + UPDATE user_vip_trial_cards + SET status = ?, updated_at_ms = ? + WHERE app_code = ? AND user_id = ? AND entitlement_id = ? AND status = ?`, + ledger.VipTrialCardStatusRevoked, nowMs, command.AppCode, grant.TargetUserID, + item.EntitlementID, ledger.VipTrialCardStatusActive, + ); err != nil { + return walletOutboxEvent{}, err + } } eventCommandID := resourceGrantRevokeCommandID(command, grant.GrantID, item.GrantItemID) diff --git a/services/wallet-service/internal/storage/mysql/resource_outbox.go b/services/wallet-service/internal/storage/mysql/resource_outbox.go index fbf3260f..785e1cb6 100644 --- a/services/wallet-service/internal/storage/mysql/resource_outbox.go +++ b/services/wallet-service/internal/storage/mysql/resource_outbox.go @@ -8,10 +8,14 @@ import ( func resourceOutboxEvent(eventType string, commandID string, userID int64, resourceID int64, payload any, nowMs int64) walletOutboxEvent { eventKey := fmt.Sprintf("%s|%s|%d|%d", eventType, commandID, userID, resourceID) + eventID := "wev_" + stableHash(eventKey) return walletOutboxEvent{ - EventID: "wev_" + stableHash(eventKey), - EventType: eventType, - TransactionID: "", + EventID: eventID, + EventType: eventType, + // 资源目录、赠送和装备变更没有金融 ledger transaction;使用稳定 event_id + // 作为 correlation transaction_id,既满足统一 MQ envelope,又能从消息直接 + // 回查唯一 outbox 行。相同业务命令重试会得到同一 ID,不会制造新事实。 + TransactionID: eventID, CommandID: commandID, UserID: userID, AssetType: resourceOutboxAsset, diff --git a/services/wallet-service/internal/storage/mysql/resource_outbox_test.go b/services/wallet-service/internal/storage/mysql/resource_outbox_test.go new file mode 100644 index 00000000..b269d805 --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/resource_outbox_test.go @@ -0,0 +1,98 @@ +package mysql + +import ( + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "hyapp/pkg/walletmq" +) + +func TestResourceOutboxEventsUseStableEventIDAsTransactionID(t *testing.T) { + // 这些是当前所有通过 resourceOutboxEvent 创建的资源事实;helper 对事件类型 + // 本身无分支,因此该约束也自动覆盖以后接入同一入口的新资源事实。 + eventTypes := []string{ + "GiftConfigChanged", + "ResourceChanged", + "ResourceGrantRevoked", + "ResourceGranted", + "ResourceGroupChanged", + "ResourceGroupGranted", + "UserResourceChanged", + "VipEffectiveChanged", + "VipTrialCardGranted", + } + for _, eventType := range eventTypes { + t.Run(eventType, func(t *testing.T) { + first := resourceOutboxEvent(eventType, "command-1", 1001, 2001, map[string]any{"source": "test"}, 1_700_000_000_000) + second := resourceOutboxEvent(eventType, "command-1", 1001, 2001, map[string]any{"source": "retry"}, 1_700_000_000_999) + if first.TransactionID == "" || first.TransactionID != first.EventID { + t.Fatalf("resource correlation ids mismatch: event_id=%q transaction_id=%q", first.EventID, first.TransactionID) + } + if second.EventID != first.EventID || second.TransactionID != first.TransactionID { + t.Fatalf("resource correlation id changed on retry: first=%+v second=%+v", first, second) + } + if len(first.TransactionID) > 96 { + t.Fatalf("resource transaction_id exceeds wallet_outbox column: %d", len(first.TransactionID)) + } + if _, err := walletmq.EncodeWalletOutboxMessage(walletmq.WalletOutboxMessage{ + AppCode: "lalu", + EventID: first.EventID, + EventType: first.EventType, + TransactionID: first.TransactionID, + CommandID: first.CommandID, + UserID: first.UserID, + AssetType: first.AssetType, + PayloadJSON: `{"source":"test"}`, + OccurredAtMS: first.CreatedAtMS, + }); err != nil { + t.Fatalf("resource event is still rejected by wallet MQ encoder: %v", err) + } + }) + } +} + +func TestResourceOutboxHistoricalRetryIsOptInAndNarrowlyScoped(t *testing.T) { + _, file, _, ok := runtime.Caller(0) + if !ok { + t.Fatal("resolve test source path") + } + path := filepath.Join(filepath.Dir(file), "../../../deploy/mysql/maintenance/20260711_retry_incomplete_resource_outbox.sql") + body, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read resource outbox maintenance SQL: %v", err) + } + sql := string(body) + for _, required := range []string{ + "COALESCE(@execute_wallet_resource_outbox_retry, 0)", + "WHERE @execute_wallet_resource_outbox_retry = 1", + "AND wo.asset_type = 'RESOURCE'", + "AND wo.transaction_id = ''", + "AND BINARY wo.last_error = BINARY 'wallet outbox message is incomplete'", + "AND wo.status IN ('failed', 'retryable')", + "LIMIT 100", + "SET wo.transaction_id = wo.event_id", + "wo.status = 'pending'", + } { + if !strings.Contains(sql, required) { + t.Fatalf("maintenance SQL misses safety contract %q", required) + } + } + for _, eventType := range []string{ + "GiftConfigChanged", + "ResourceChanged", + "ResourceGrantRevoked", + "ResourceGranted", + "ResourceGroupChanged", + "ResourceGroupGranted", + "UserResourceChanged", + "VipEffectiveChanged", + "VipTrialCardGranted", + } { + if !strings.Contains(sql, "'"+eventType+"'") { + t.Fatalf("maintenance SQL misses resource event type %q", eventType) + } + } +} diff --git a/services/wallet-service/internal/storage/mysql/resource_repository_test.go b/services/wallet-service/internal/storage/mysql/resource_repository_test.go index 2382cf98..8a981b45 100644 --- a/services/wallet-service/internal/storage/mysql/resource_repository_test.go +++ b/services/wallet-service/internal/storage/mysql/resource_repository_test.go @@ -14,6 +14,15 @@ func TestDecorativeResourceTypesAreEquipable(t *testing.T) { } } +func TestVIPTrialCardUsesInternalSingleSelectEquipmentSlot(t *testing.T) { + if !isEquipableResourceType(resourcedomain.TypeVIPTrialCard) { + t.Fatalf("vip_trial_card dedicated state machine must reuse the internal equipment slot") + } + if allowsMultipleEquippedResources(resourcedomain.TypeVIPTrialCard) { + t.Fatalf("vip_trial_card equipment must keep exactly one current card") + } +} + func TestGrantDefaultEquippedResourceTypes(t *testing.T) { for _, resourceType := range []string{resourcedomain.TypeBadge, resourcedomain.TypeMicSeatAnimation} { if !isAutoEquippedOnGrantResourceType(resourceType) { diff --git a/services/wallet-service/internal/storage/mysql/schema.go b/services/wallet-service/internal/storage/mysql/schema.go index 4934cb5a..e602e671 100644 --- a/services/wallet-service/internal/storage/mysql/schema.go +++ b/services/wallet-service/internal/storage/mysql/schema.go @@ -172,6 +172,9 @@ func ensureGooglePaymentPaidDetailsSchema(ctx context.Context, db *sql.DB) error `ALTER TABLE payment_orders ADD COLUMN paid_tax_micro BIGINT NOT NULL DEFAULT 0 COMMENT '订单税额微单位(实付币种)' AFTER paid_amount_micro`, `ALTER TABLE payment_orders ADD COLUMN paid_net_micro BIGINT NOT NULL DEFAULT 0 COMMENT 'Google 扣费后开发者净收入微单位(实付币种)' AFTER paid_tax_micro`, `ALTER TABLE payment_orders ADD COLUMN paid_synced_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '实付明细同步时间,UTC epoch ms;0 表示未同步' AFTER paid_net_micro`, + `ALTER TABLE payment_orders ADD COLUMN paid_sync_attempt_count INT NOT NULL DEFAULT 0 COMMENT 'Google 实付同步连续失败次数' AFTER paid_synced_at_ms`, + `ALTER TABLE payment_orders ADD COLUMN paid_sync_last_error VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'Google 实付同步最近错误' AFTER paid_sync_attempt_count`, + `ALTER TABLE payment_orders ADD COLUMN paid_sync_next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT 'Google 实付同步下次重试时间,UTC epoch ms' AFTER paid_sync_last_error`, } for _, stmt := range alters { if _, err := db.ExecContext(ctx, stmt); err != nil && !isDuplicateColumnError(err) { @@ -183,6 +186,11 @@ func ensureGooglePaymentPaidDetailsSchema(ctx context.Context, db *sql.DB) error ADD INDEX idx_payment_orders_wallet_tx (app_code, wallet_transaction_id)`); err != nil && !isDuplicateKeyNameError(err) { return err } + if _, err := db.ExecContext(ctx, ` + ALTER TABLE payment_orders + ADD INDEX idx_payment_orders_paid_sync (provider, paid_synced_at_ms, paid_sync_next_retry_at_ms, created_at_ms)`); err != nil && !isDuplicateKeyNameError(err) { + return err + } return nil } @@ -199,6 +207,7 @@ func ensureWalletPolicyInstanceSchema(ctx context.Context, db *sql.DB) error { effective_to_ms BIGINT NOT NULL DEFAULT 0 COMMENT '0 表示长期有效', host_point_asset_type VARCHAR(32) NOT NULL DEFAULT 'POINT' COMMENT '主播收益积分资产', host_point_ratio_ppm BIGINT NOT NULL DEFAULT 700000 COMMENT '有效付费礼物转 POINT 比例,ppm', + agency_point_ratio_ppm BIGINT NOT NULL DEFAULT 0 COMMENT '有效付费礼物给 Agency owner 的 POINT 分成比例,ppm', points_per_usd BIGINT NOT NULL DEFAULT 100000 COMMENT 'POINT/USD 展示换算比例', withdraw_fee_bps INT NOT NULL DEFAULT 500 COMMENT '提现手续费 bps', rule_json JSON NOT NULL COMMENT '完整政策快照', @@ -209,6 +218,50 @@ func ensureWalletPolicyInstanceSchema(ctx context.Context, db *sql.DB) error { PRIMARY KEY (app_code, instance_code, region_id), KEY idx_wallet_policy_active (app_code, region_id, status, effective_from_ms, effective_to_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='钱包运行侧收益政策实例表'`) + if err != nil { + return err + } + if _, err := db.ExecContext(ctx, `ALTER TABLE wallet_policy_instances ADD COLUMN agency_point_ratio_ppm BIGINT NOT NULL DEFAULT 0 COMMENT '有效付费礼物给 Agency owner 的 POINT 分成比例,ppm' AFTER host_point_ratio_ppm`); err != nil && !isDuplicateColumnError(err) { + return err + } + _, err = db.ExecContext(ctx, ` + CREATE TABLE IF NOT EXISTS agency_point_share_entries ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu', + transaction_id VARCHAR(96) NOT NULL, + host_user_id BIGINT NOT NULL, + agency_owner_user_id BIGINT NOT NULL, + sender_user_id BIGINT NOT NULL, + room_id VARCHAR(96) NOT NULL DEFAULT '', + point_delta BIGINT NOT NULL, + ratio_ppm BIGINT NOT NULL, + policy_instance_code VARCHAR(96) NOT NULL DEFAULT '', + created_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, transaction_id, agency_owner_user_id), + KEY idx_agency_point_share_owner_time (app_code, agency_owner_user_id, created_at_ms), + KEY idx_agency_point_share_host_time (app_code, host_user_id, created_at_ms) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Agency POINT 礼物分成流水'`) + return err +} + +// ensurePointWithdrawalCoinSellerSchema 创建跨 Fami/Huwaa/Lalu 共用、按 app_code 隔离的提现币商配置。 +// 表放在 wallet 数据库,保证兑换时读取比例和写账本处于同一可信服务边界,H5 不能自报比例。 +func ensurePointWithdrawalCoinSellerSchema(ctx context.Context, db *sql.DB) error { + _, err := db.ExecContext(ctx, ` + CREATE TABLE IF NOT EXISTS point_withdrawal_coin_seller_configs ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码', + seller_user_id BIGINT NOT NULL COMMENT '币商用户 ID', + sort_order INT NOT NULL DEFAULT 0 COMMENT '展示顺序,越小越靠前', + point_amount BIGINT NOT NULL COMMENT '兑换比例中的 POINT 分母', + seller_coin_amount BIGINT NOT NULL COMMENT '兑换比例中的币商金币分子', + service_country_codes JSON NOT NULL COMMENT '服务国家码数组;空数组表示不限国家', + status VARCHAR(24) NOT NULL DEFAULT 'active' COMMENT 'active/disabled', + created_by_admin_id BIGINT UNSIGNED NOT NULL DEFAULT 0, + updated_by_admin_id BIGINT UNSIGNED NOT NULL DEFAULT 0, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, seller_user_id), + KEY idx_point_withdraw_seller_list (app_code, status, sort_order, seller_user_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='POINT 提现币商白名单与兑换比例'`) return err } diff --git a/services/wallet-service/internal/storage/mysql/transaction.go b/services/wallet-service/internal/storage/mysql/transaction.go index 8ff64214..812df32a 100644 --- a/services/wallet-service/internal/storage/mysql/transaction.go +++ b/services/wallet-service/internal/storage/mysql/transaction.go @@ -45,6 +45,31 @@ func (r *Repository) lookupTransactionWithConflictCode(ctx context.Context, tx * return txRow, true, nil } +// lookupTransactionConsistent 不对不存在的唯一键做 gap lock,供能在 INSERT duplicate 后安全回读的命令使用。 +// 这避免两个同 command 首发都持 gap lock、再以相反顺序等待业务行锁和唯一键插入造成死锁。 +func (r *Repository) lookupTransactionConsistent(ctx context.Context, tx *sql.Tx, commandID string, requestHash string, bizType string, conflictCode xerr.Code) (transactionRow, bool, error) { + row := tx.QueryRowContext(ctx, + `SELECT transaction_id, request_hash, biz_type, COALESCE(CAST(metadata_json AS CHAR), '{}') + FROM wallet_transactions + WHERE app_code = ? AND command_id = ?`, + appcode.FromContext(ctx), + commandID, + ) + var txRow transactionRow + var storedHash string + var storedBizType string + if err := row.Scan(&txRow.TransactionID, &storedHash, &storedBizType, &txRow.MetadataJSON); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return transactionRow{}, false, nil + } + return transactionRow{}, false, err + } + if storedHash != requestHash || storedBizType != bizType { + return transactionRow{}, true, xerr.New(conflictCode, "wallet command idempotency conflict") + } + return txRow, true, nil +} + func (r *Repository) insertTransaction(ctx context.Context, tx *sql.Tx, transactionID string, commandID string, bizType string, requestHash string, externalRef string, metadata any, nowMs int64) error { metadataJSON, err := json.Marshal(metadata) if err != nil { @@ -67,3 +92,38 @@ func (r *Repository) insertTransaction(ctx context.Context, tx *sql.Tx, transact ) return err } + +// updateTransactionMetadata replaces the provisional transaction snapshot after every tracked account +// mutation has succeeded. Gift transactions must be inserted before ledger entries for referential and +// idempotency ordering, but balance_version is authoritative only after applyTrackedAccountDelta has +// advanced the locked account state. Persisting the final JSON in the same transaction makes a later +// command_id replay return the exact original balance and version instead of reading mutable live state. +func (r *Repository) updateTransactionMetadata(ctx context.Context, tx *sql.Tx, transactionID string, metadata any, nowMS int64) error { + metadataJSON, err := json.Marshal(metadata) + if err != nil { + return err + } + result, err := tx.ExecContext(ctx, + `UPDATE wallet_transactions + SET metadata_json = ?, updated_at_ms = ? + WHERE app_code = ? AND transaction_id = ?`, + string(metadataJSON), + nowMS, + appcode.FromContext(ctx), + transactionID, + ) + if err != nil { + return err + } + affected, err := result.RowsAffected() + if err != nil { + return err + } + // go-sql-driver/mysql 默认返回 changed rows,不返回 matched rows。当最终 metadata + // 与插入时的预测值完全一致时,这条 UPDATE 合法命中主键但 affected=0;交易行已在 + // 同一事务中由 insertTransaction 成功插入,因此只能把多行更新视为不变量破坏。 + if affected > 1 { + return xerr.New(xerr.Internal, "wallet transaction metadata update lost its transaction") + } + return nil +} diff --git a/services/wallet-service/internal/storage/mysql/transaction_test.go b/services/wallet-service/internal/storage/mysql/transaction_test.go new file mode 100644 index 00000000..d5510920 --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/transaction_test.go @@ -0,0 +1,39 @@ +package mysql + +import ( + "context" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + + "hyapp/pkg/appcode" +) + +func TestUpdateTransactionMetadataAcceptsMatchedButUnchangedRow(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + + mock.ExpectBegin() + tx, err := db.Begin() + if err != nil { + t.Fatalf("begin transaction: %v", err) + } + mock.ExpectExec(regexp.QuoteMeta(`UPDATE wallet_transactions + SET metadata_json = ?, updated_at_ms = ? + WHERE app_code = ? AND transaction_id = ?`)). + WithArgs(`{"balance_version":9}`, int64(1234), "lalu", "tx-1"). + WillReturnResult(sqlmock.NewResult(0, 0)) + + repo := &Repository{} + ctx := appcode.WithContext(context.Background(), "lalu") + if err := repo.updateTransactionMetadata(ctx, tx, "tx-1", map[string]int64{"balance_version": 9}, 1234); err != nil { + t.Fatalf("unchanged matched row must not fail: %v", err) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("unmet SQL expectations: %v", err) + } +} diff --git a/services/wallet-service/internal/storage/mysql/vip_admin_repository.go b/services/wallet-service/internal/storage/mysql/vip_admin_repository.go index 623d5b06..205456b7 100644 --- a/services/wallet-service/internal/storage/mysql/vip_admin_repository.go +++ b/services/wallet-service/internal/storage/mysql/vip_admin_repository.go @@ -3,111 +3,227 @@ package mysql import ( "context" "database/sql" + "encoding/json" + "errors" "hyapp/pkg/appcode" "hyapp/pkg/xerr" "hyapp/services/wallet-service/internal/domain/ledger" resourcedomain "hyapp/services/wallet-service/internal/domain/resource" + "strconv" "strings" "time" + "unicode/utf8" ) -// UpdateAdminVipLevels 保存完整 10 级 VIP 配置。 -func (r *Repository) UpdateAdminVipLevels(ctx context.Context, levels []ledger.AdminVipLevelCommand, operatorUserID int64) ([]ledger.VipLevel, error) { +const vipPlaceholderDurationMS int64 = 30 * 24 * 60 * 60 * 1000 + +// UpdateAdminVipLevels 按 program.level_count 保存完整等级与每级显式权益集合。 +// 每次变更同时递增 config_version,使 room/user 等执行方能够识别最新权限快照。 +func (r *Repository) UpdateAdminVipLevels(ctx context.Context, levels []ledger.AdminVipLevelCommand, operatorUserID int64) (ledger.VipProgramConfig, []ledger.VipLevel, error) { if r == nil || r.db == nil { - return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + return ledger.VipProgramConfig{}, nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") } if operatorUserID <= 0 { - return nil, xerr.New(xerr.InvalidArgument, "operator_user_id is required") + return ledger.VipProgramConfig{}, nil, xerr.New(xerr.InvalidArgument, "operator_user_id is required") } - normalized, err := normalizeAdminVipLevelCommands(levels) - if err != nil { - return nil, err - } - tx, err := r.db.BeginTx(ctx, nil) if err != nil { - return nil, err + return ledger.VipProgramConfig{}, nil, err } defer func() { _ = tx.Rollback() }() - nowMs := time.Now().UnixMilli() + program, err := r.queryVipProgramConfig(ctx, tx.QueryRowContext(ctx, vipProgramSelectSQL()+` FOR UPDATE`, appcode.FromContext(ctx))) + if errors.Is(err, sql.ErrNoRows) { + return ledger.VipProgramConfig{}, nil, xerr.New(xerr.NotFound, "vip program config not found") + } + if err != nil { + return ledger.VipProgramConfig{}, nil, err + } + normalized, err := normalizeAdminVipLevelCommands(levels, program) + if err != nil { + return ledger.VipProgramConfig{}, nil, err + } + + nowMS := time.Now().UnixMilli() for _, level := range normalized { - if level.Status == ledger.VipStatusActive { + if level.Status == ledger.VipStatusActive && level.RewardResourceGroupID > 0 { if err := r.requireActiveResourceGroup(ctx, tx, level.RewardResourceGroupID); err != nil { - return nil, err + return ledger.VipProgramConfig{}, nil, err } } if _, err := tx.ExecContext(ctx, ` INSERT INTO vip_levels ( app_code, level, name, status, price_coin, duration_ms, reward_resource_group_id, required_recharge_coin_amount, sort_order, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ) VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?) ON DUPLICATE KEY UPDATE - name = VALUES(name), - status = VALUES(status), - price_coin = VALUES(price_coin), - duration_ms = VALUES(duration_ms), - reward_resource_group_id = VALUES(reward_resource_group_id), - required_recharge_coin_amount = VALUES(required_recharge_coin_amount), - sort_order = VALUES(sort_order), + name = VALUES(name), status = VALUES(status), price_coin = VALUES(price_coin), + duration_ms = VALUES(duration_ms), reward_resource_group_id = VALUES(reward_resource_group_id), + required_recharge_coin_amount = 0, sort_order = VALUES(sort_order), updated_at_ms = VALUES(updated_at_ms)`, - appcode.FromContext(ctx), - level.Level, - level.Name, - level.Status, - level.PriceCoin, - level.DurationMS, - level.RewardResourceGroupID, - level.RequiredRechargeCoinAmount, - level.SortOrder, - nowMs, - nowMs, + appcode.FromContext(ctx), level.Level, level.Name, level.Status, level.PriceCoin, + level.DurationMS, level.RewardResourceGroupID, level.SortOrder, nowMS, nowMS, ); err != nil { - return nil, err + return ledger.VipProgramConfig{}, nil, err + } + // 请求携带的是该等级最终完整集合,因此先删后插,明确支持运营移除权益且不做 unlock_level 自动继承。 + if _, err := tx.ExecContext(ctx, `DELETE FROM vip_level_benefits WHERE app_code = ? AND level = ?`, appcode.FromContext(ctx), level.Level); err != nil { + return ledger.VipProgramConfig{}, nil, err + } + for _, benefit := range level.Benefits { + if err := r.validateAdminVipBenefit(ctx, tx, level.Level, benefit); err != nil { + return ledger.VipProgramConfig{}, nil, err + } + var metadata any + if strings.TrimSpace(benefit.MetadataJSON) != "" { + metadata = benefit.MetadataJSON + } + if _, err := tx.ExecContext(ctx, ` + INSERT INTO vip_level_benefits ( + app_code, level, benefit_code, name, benefit_type, unlock_level, status, + trial_enabled, resource_id, resource_type, execution_scope, auto_equip, + sort_order, metadata_json, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + appcode.FromContext(ctx), level.Level, benefit.BenefitCode, benefit.Name, + benefit.BenefitType, benefit.UnlockLevel, benefit.Status, benefit.TrialEnabled, + benefit.ResourceID, benefit.ResourceType, benefit.ExecutionScope, benefit.AutoEquip, + benefit.SortOrder, metadata, nowMS, nowMS, + ); err != nil { + return ledger.VipProgramConfig{}, nil, err + } } } - if err := tx.Commit(); err != nil { - return nil, err + if _, err := tx.ExecContext(ctx, ` + UPDATE vip_program_configs + SET config_version = config_version + 1, updated_by_admin_id = ?, updated_at_ms = ? + WHERE app_code = ?`, operatorUserID, nowMS, appcode.FromContext(ctx)); err != nil { + return ledger.VipProgramConfig{}, nil, err } + if err := tx.Commit(); err != nil { + return ledger.VipProgramConfig{}, nil, err + } + // 提交后用单独 repeatable-read 快照返回 program+levels;即使另一管理员紧接着更新,回包也不会跨版本拼接。 return r.ListAdminVipLevels(ctx) } -func normalizeAdminVipLevelCommands(levels []ledger.AdminVipLevelCommand) ([]ledger.AdminVipLevelCommand, error) { - if len(levels) != 10 { - return nil, xerr.New(xerr.InvalidArgument, "vip config requires exactly 10 levels") +// UpdateAdminVipProgramConfig 只接受服务端已实现的策略组合,并在锁内递增配置版本。 +func (r *Repository) UpdateAdminVipProgramConfig(ctx context.Context, command ledger.AdminVipProgramConfigCommand) (ledger.VipProgramConfig, error) { + if r == nil || r.db == nil { + return ledger.VipProgramConfig{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") } - byLevel := make(map[int32]ledger.AdminVipLevelCommand, 10) + config := normalizeAdminVipProgramConfig(command.Config) + if err := validateAdminVipProgramConfig(config); err != nil { + return ledger.VipProgramConfig{}, err + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return ledger.VipProgramConfig{}, err + } + defer func() { _ = tx.Rollback() }() + + nowMS := time.Now().UnixMilli() + if _, err := tx.ExecContext(ctx, ` + INSERT INTO vip_program_configs ( + app_code, program_type, level_count, same_level_expiry_policy, + upgrade_expiry_policy, downgrade_purchase_policy, benefit_inheritance_policy, + grant_mode, trial_card_enabled, status, config_version, updated_by_admin_id, + created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?) + ON DUPLICATE KEY UPDATE + program_type = VALUES(program_type), level_count = VALUES(level_count), + same_level_expiry_policy = VALUES(same_level_expiry_policy), + upgrade_expiry_policy = VALUES(upgrade_expiry_policy), + downgrade_purchase_policy = VALUES(downgrade_purchase_policy), + benefit_inheritance_policy = VALUES(benefit_inheritance_policy), + grant_mode = VALUES(grant_mode), trial_card_enabled = VALUES(trial_card_enabled), + status = VALUES(status), config_version = config_version + 1, + updated_by_admin_id = VALUES(updated_by_admin_id), updated_at_ms = VALUES(updated_at_ms)`, + appcode.FromContext(ctx), config.ProgramType, config.LevelCount, config.SameLevelExpiryPolicy, + config.UpgradeExpiryPolicy, config.DowngradePurchasePolicy, + config.BenefitInheritancePolicy, config.GrantMode, config.TrialCardEnabled, + config.Status, command.OperatorUserID, nowMS, nowMS, + ); err != nil { + return ledger.VipProgramConfig{}, err + } + // level_count 增大或新 App 首次建体系时,事务内补齐不可购买的结构占位等级。 + // 价格和资源组保持 0,只有运营随后提交完整等级配置并显式启用后才能进入购买链路。 + for level := int32(1); level <= config.LevelCount; level++ { + if _, err := tx.ExecContext(ctx, ` + INSERT IGNORE INTO vip_levels ( + app_code, level, name, status, price_coin, duration_ms, + reward_resource_group_id, required_recharge_coin_amount, sort_order, + created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, 0, ?, 0, 0, ?, ?, ?)`, + appcode.FromContext(ctx), level, "VIP"+strconv.Itoa(int(level)), ledger.VipStatusDisabled, + vipPlaceholderDurationMS, level*10, nowMS, nowMS, + ); err != nil { + return ledger.VipProgramConfig{}, err + } + } + config, err = r.queryVipProgramConfig(ctx, tx.QueryRowContext(ctx, vipProgramSelectSQL(), appcode.FromContext(ctx))) + if err != nil { + return ledger.VipProgramConfig{}, err + } + if err := tx.Commit(); err != nil { + return ledger.VipProgramConfig{}, err + } + return config, nil +} + +func normalizeAdminVipLevelCommands(levels []ledger.AdminVipLevelCommand, program ledger.VipProgramConfig) ([]ledger.AdminVipLevelCommand, error) { + if int32(len(levels)) != program.LevelCount { + return nil, xerr.New(xerr.InvalidArgument, "vip config level count does not match program") + } + byLevel := make(map[int32]ledger.AdminVipLevelCommand, len(levels)) for _, level := range levels { level.Name = strings.TrimSpace(level.Name) level.Status = strings.ToLower(strings.TrimSpace(level.Status)) if level.Status == "" { level.Status = ledger.VipStatusDisabled } - if level.Level < 1 || level.Level > 10 { - return nil, xerr.New(xerr.InvalidArgument, "vip level must be between 1 and 10") + if level.Level < 1 || level.Level > program.LevelCount { + return nil, xerr.New(xerr.InvalidArgument, "vip level is outside program level_count") } if _, exists := byLevel[level.Level]; exists { return nil, xerr.New(xerr.InvalidArgument, "vip level is duplicated") } - // 累充门槛字段仅保留数据库和 proto 兼容性;后台保存任意等级时都归零,防止旧客户端把历史门槛重新写入生效数据。 level.RequiredRechargeCoinAmount = 0 - if err := validateAdminVipLevelCommand(level); err != nil { + benefitCodes := make(map[string]struct{}, len(level.Benefits)) + for index := range level.Benefits { + benefit := &level.Benefits[index] + benefit.BenefitCode = strings.ToLower(strings.TrimSpace(benefit.BenefitCode)) + benefit.Name = strings.TrimSpace(benefit.Name) + benefit.BenefitType = strings.ToLower(strings.TrimSpace(benefit.BenefitType)) + benefit.Status = strings.ToLower(strings.TrimSpace(benefit.Status)) + benefit.ResourceType = resourcedomain.NormalizeResourceType(benefit.ResourceType) + benefit.ExecutionScope = strings.ToLower(strings.TrimSpace(benefit.ExecutionScope)) + benefit.MetadataJSON = strings.TrimSpace(benefit.MetadataJSON) + // 体验卡永远不参与金币返现;保存时强制收敛,运行时仍会按编码再次兜底。 + if benefit.BenefitCode == ledger.VipBenefitCodeDailyCoinRebate { + benefit.TrialEnabled = false + } + if _, exists := benefitCodes[benefit.BenefitCode]; benefit.BenefitCode == "" || exists { + return nil, xerr.New(xerr.InvalidArgument, "vip benefit code is empty or duplicated") + } + benefitCodes[benefit.BenefitCode] = struct{}{} + } + if err := validateAdminVipLevelCommand(level, program); err != nil { return nil, err } byLevel[level.Level] = level } - normalized := make([]ledger.AdminVipLevelCommand, 0, 10) - for level := int32(1); level <= 10; level++ { + normalized := make([]ledger.AdminVipLevelCommand, 0, program.LevelCount) + for level := int32(1); level <= program.LevelCount; level++ { item, ok := byLevel[level] if !ok { - return nil, xerr.New(xerr.InvalidArgument, "vip config requires levels 1 to 10") + return nil, xerr.New(xerr.InvalidArgument, "vip config requires a complete level sequence") } normalized = append(normalized, item) } return normalized, nil } -func validateAdminVipLevelCommand(level ledger.AdminVipLevelCommand) error { +func validateAdminVipLevelCommand(level ledger.AdminVipLevelCommand, program ledger.VipProgramConfig) error { if level.Name == "" { return xerr.New(xerr.InvalidArgument, "vip level name is required") } @@ -116,17 +232,148 @@ func validateAdminVipLevelCommand(level ledger.AdminVipLevelCommand) error { default: return xerr.New(xerr.InvalidArgument, "vip level status is invalid") } - if level.PriceCoin < 0 || level.DurationMS <= 0 || level.RewardResourceGroupID < 0 || level.RequiredRechargeCoinAmount < 0 { + if level.PriceCoin < 0 || level.DurationMS <= 0 || level.RewardResourceGroupID < 0 { return xerr.New(xerr.InvalidArgument, "vip level config is invalid") } - if level.Status != ledger.VipStatusActive { - return nil - } - if level.PriceCoin <= 0 { + if level.Status == ledger.VipStatusActive && level.PriceCoin <= 0 { return xerr.New(xerr.InvalidArgument, "active vip level price_coin must be greater than zero") } - if level.RewardResourceGroupID <= 0 { - return xerr.New(xerr.InvalidArgument, "active vip level reward_resource_group_id is required") + // legacy_timed 继续允许无资源组的既有等级;tiered 体系的能力也来自显式 benefits,不能强制复制旧资源组模型。 + _ = program + return nil +} + +func (r *Repository) validateAdminVipBenefit(ctx context.Context, tx *sql.Tx, level int32, benefit ledger.VipBenefit) error { + if benefit.BenefitCode == "" || benefit.Name == "" || benefit.UnlockLevel < 0 || benefit.ResourceID < 0 { + return xerr.New(xerr.InvalidArgument, "vip benefit is incomplete") + } + switch benefit.BenefitType { + case ledger.VipBenefitTypeDecoration, ledger.VipBenefitTypeFunction: + default: + return xerr.New(xerr.InvalidArgument, "vip benefit type is invalid") + } + switch benefit.Status { + case ledger.VipStatusActive, ledger.VipStatusDisabled: + default: + return xerr.New(xerr.InvalidArgument, "vip benefit status is invalid") + } + switch benefit.ExecutionScope { + case ledger.VipExecutionScopeWallet, ledger.VipExecutionScopeRoom, ledger.VipExecutionScopeUser, ledger.VipExecutionScopeNotice: + default: + return xerr.New(xerr.InvalidArgument, "vip benefit execution_scope is invalid") + } + if benefit.MetadataJSON != "" && !json.Valid([]byte(benefit.MetadataJSON)) { + return xerr.New(xerr.InvalidArgument, "vip benefit metadata_json is invalid") + } + if benefit.BenefitCode == ledger.VipBenefitCodeDailyCoinRebate { + if benefit.ExecutionScope != ledger.VipExecutionScopeWallet { + return xerr.New(xerr.InvalidArgument, "daily_coin_rebate execution_scope must be wallet") + } + if benefit.TrialEnabled { + return xerr.New(xerr.InvalidArgument, "daily_coin_rebate cannot be enabled for trial cards") + } + // disabled 允许空对象,便于后台表单原样提交未定金额;若已显式带 coin_amount, + // 即使 disabled 也拒绝非法类型,避免稍后仅切状态时激活脏数据。 + validateAmount := benefit.Status == ledger.VipStatusActive + if !validateAmount && benefit.MetadataJSON != "" { + var metadata map[string]any + if err := json.Unmarshal([]byte(benefit.MetadataJSON), &metadata); err != nil || metadata == nil { + return xerr.New(xerr.InvalidArgument, "daily_coin_rebate metadata_json must be an object") + } + _, validateAmount = metadata["coin_amount"] + } + // active 必须是 JSON 正 int64,不猜测任何产品上限。 + if validateAmount { + if _, err := parseVipDailyCoinRebateAmount(benefit.MetadataJSON); err != nil { + return xerr.New(xerr.InvalidArgument, "daily_coin_rebate metadata_json.coin_amount must be a positive int64") + } + } + } + if benefit.BenefitCode == ledger.VipBenefitCodeOnlineGlobalNotice { + if benefit.ExecutionScope != ledger.VipExecutionScopeNotice { + return xerr.New(xerr.InvalidArgument, "online_global_notice execution_scope must be notice") + } + if benefit.MetadataJSON != "" { + var metadata map[string]any + if err := json.Unmarshal([]byte(benefit.MetadataJSON), &metadata); err != nil || metadata == nil { + return xerr.New(xerr.InvalidArgument, "online_global_notice metadata_json must be an object") + } + if rawMessage, exists := metadata["message"]; exists { + message, ok := rawMessage.(string) + if !ok || strings.TrimSpace(message) == "" || utf8.RuneCountInString(message) > 256 { + return xerr.New(xerr.InvalidArgument, "online_global_notice metadata_json.message must be a non-empty string within 256 characters") + } + } + } + } + if benefit.ResourceID == 0 { + return nil + } + resource, err := r.getResourceForUpdate(ctx, tx, benefit.ResourceID) + if err != nil { + return err + } + if resource.Status != resourcedomain.StatusActive { + return xerr.New(xerr.Conflict, "vip benefit resource is disabled") + } + if benefit.ResourceType != "" && resourcedomain.NormalizeResourceType(resource.ResourceType) != benefit.ResourceType { + return xerr.New(xerr.Conflict, "vip benefit resource_type mismatch") + } + _ = level + return nil +} + +func normalizeAdminVipProgramConfig(config ledger.VipProgramConfig) ledger.VipProgramConfig { + config.AppCode = appcode.Normalize(config.AppCode) + config.ProgramType = strings.ToLower(strings.TrimSpace(config.ProgramType)) + config.SameLevelExpiryPolicy = strings.ToLower(strings.TrimSpace(config.SameLevelExpiryPolicy)) + config.UpgradeExpiryPolicy = strings.ToLower(strings.TrimSpace(config.UpgradeExpiryPolicy)) + config.DowngradePurchasePolicy = strings.ToLower(strings.TrimSpace(config.DowngradePurchasePolicy)) + // “最低等级自动继承”已被产品明确移除。旧后台若仍提交 cumulative,也只能作为兼容输入, + // 服务端持久化和回包一律收敛为 target_only,防止隐藏字段恢复旧语义。 + config.BenefitInheritancePolicy = ledger.VipBenefitInheritanceTargetOnly + config.GrantMode = strings.ToLower(strings.TrimSpace(config.GrantMode)) + config.Status = strings.ToLower(strings.TrimSpace(config.Status)) + return config +} + +func validateAdminVipProgramConfig(config ledger.VipProgramConfig) error { + switch config.ProgramType { + case ledger.VipProgramTypeLegacyTimed, ledger.VipProgramTypeTieredPrivilegeV1: + default: + return xerr.New(xerr.InvalidArgument, "vip program_type is invalid") + } + if config.LevelCount < 1 || config.LevelCount > 20 { + return xerr.New(xerr.InvalidArgument, "vip level_count must be between 1 and 20") + } + if config.SameLevelExpiryPolicy != ledger.VipExpiryPolicyExtendRemaining { + return xerr.New(xerr.InvalidArgument, "vip same_level_expiry_policy is invalid") + } + switch config.UpgradeExpiryPolicy { + case ledger.VipExpiryPolicyExtendRemaining, ledger.VipExpiryPolicyReplaceFromNow: + default: + return xerr.New(xerr.InvalidArgument, "vip upgrade_expiry_policy is invalid") + } + if config.DowngradePurchasePolicy != ledger.VipDowngradePurchasePolicyReject { + return xerr.New(xerr.InvalidArgument, "vip downgrade_purchase_policy is invalid") + } + // 产品已明确忽略“最低等级自动继承”;当前运行时只支持后台为每级保存完整集合。 + if config.BenefitInheritancePolicy != ledger.VipBenefitInheritanceTargetOnly { + return xerr.New(xerr.InvalidArgument, "vip benefit_inheritance_policy must be target_only") + } + switch config.GrantMode { + case ledger.VipGrantModeDirectMembership: + case ledger.VipGrantModeTrialCard: + if !config.TrialCardEnabled { + return xerr.New(xerr.InvalidArgument, "trial_card grant mode requires trial_card_enabled") + } + default: + return xerr.New(xerr.InvalidArgument, "vip grant_mode is invalid") + } + switch config.Status { + case ledger.VipStatusActive, ledger.VipStatusDisabled: + default: + return xerr.New(xerr.InvalidArgument, "vip program status is invalid") } return nil } diff --git a/services/wallet-service/internal/storage/mysql/vip_command_repository.go b/services/wallet-service/internal/storage/mysql/vip_command_repository.go new file mode 100644 index 00000000..a8e5f120 --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/vip_command_repository.go @@ -0,0 +1,44 @@ +package mysql + +import ( + "context" + "database/sql" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" +) + +// lockVipCommand 在任何会员、账户、返现或资源业务锁之前串行化同 App、同 command_id 的 VIP 写命令。 +// +// 仅依赖 wallet_transactions 或 resource_grants 的唯一键时,两个首发事务可能先锁住不同业务行, +// 再同时争抢幂等唯一键,导致后到请求泄漏余额不足、降级或 MySQL duplicate 等可变结果。守卫行与 +// 业务事实处于同一事务:失败会一起回滚,成功后则让重放请求先等待原事务提交,再读取稳定回执。 +func (r *Repository) lockVipCommand(ctx context.Context, tx *sql.Tx, commandID string, bizType string, requestHash string, nowMS int64, conflictCode xerr.Code) error { + if _, err := tx.ExecContext(ctx, ` + INSERT INTO vip_command_locks ( + app_code, command_id, biz_type, request_hash, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE command_id = VALUES(command_id)`, + appcode.FromContext(ctx), commandID, bizType, requestHash, nowMS, nowMS, + ); err != nil { + return err + } + + // UPSERT 已经承担等待未提交同键事务的职责;FOR UPDATE 再读取已提交语义,既校验跨 VIP + // 命令类型复用,也不依赖 MySQL RowsAffected/CLIENT_FOUND_ROWS 的驱动差异。 + var storedBizType string + var storedRequestHash string + if err := tx.QueryRowContext(ctx, ` + SELECT biz_type, request_hash + FROM vip_command_locks + WHERE app_code = ? AND command_id = ? + FOR UPDATE`, + appcode.FromContext(ctx), commandID, + ).Scan(&storedBizType, &storedRequestHash); err != nil { + return err + } + if storedBizType != bizType || storedRequestHash != requestHash { + return xerr.New(conflictCode, "vip command idempotency conflict") + } + return nil +} diff --git a/services/wallet-service/internal/storage/mysql/vip_daily_coin_rebate_repository.go b/services/wallet-service/internal/storage/mysql/vip_daily_coin_rebate_repository.go new file mode 100644 index 00000000..b015d5be --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/vip_daily_coin_rebate_repository.go @@ -0,0 +1,632 @@ +package mysql + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +const ( + vipDailyCoinRebateTaskDayLayout = "2006-01-02" + vipDailyCoinRebateAvailableEvent = "VipDailyCoinRebateAvailable" +) + +type vipDailyCoinRebateCandidate struct { + UserID int64 + Level int32 + VipName string + ExpiresAtMS int64 +} + +type vipDailyCoinRebateMetadata struct { + RebateID string `json:"rebate_id"` + UserID int64 `json:"user_id"` + TaskDay string `json:"task_day"` + VipLevel int32 `json:"vip_level"` + VipName string `json:"vip_name"` + CoinAmount int64 `json:"coin_amount"` + AvailableAtMS int64 `json:"available_at_ms"` + ExpiresAtMS int64 `json:"expires_at_ms"` + ConfigVersion int64 `json:"config_version"` + CreatedAtMS int64 `json:"created_at_ms"` +} + +// ProcessVipDailyCoinRebateBatch 在一个事务内串行推进单 App+UTC 日游标。 +// run 首次创建时锁住 program 并固化金额矩阵;后续页只读 run 快照,绝不混入后台的新配置版本。 +func (r *Repository) ProcessVipDailyCoinRebateBatch(ctx context.Context, command ledger.ProcessVipDailyCoinRebateBatchCommand) (ledger.ProcessVipDailyCoinRebateBatchResult, error) { + if r == nil || r.db == nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, command.AppCode) + command.AppCode = appcode.FromContext(ctx) + dayStart, err := time.ParseInLocation(vipDailyCoinRebateTaskDayLayout, command.TaskDay, time.UTC) + if err != nil || dayStart.Format(vipDailyCoinRebateTaskDayLayout) != command.TaskDay { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, xerr.New(xerr.InvalidArgument, "vip rebate task_day is invalid") + } + dayStartMS := dayStart.UnixMilli() + dayEndMS := dayStart.Add(24 * time.Hour).UnixMilli() + if command.BatchSize <= 0 { + command.BatchSize = 100 + } + if command.BatchSize > 500 { + command.BatchSize = 500 + } + + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, err + } + defer func() { _ = tx.Rollback() }() + + run, exists, err := r.findVipDailyCoinRebateRun(ctx, tx, command.TaskDay, false) + if err != nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, err + } + if !exists { + // 缺 run 时先锁 program,再二次检查唯一日 run。并发 worker 会在该锁上排队, + // 避免两个不存在键的 gap lock 互相等待,并保证金额与 config_version 来自同一快照。 + program, lockErr := r.queryVipProgramConfig(ctx, tx.QueryRowContext(ctx, vipProgramSelectSQL()+` FOR UPDATE`, command.AppCode)) + if errors.Is(lockErr, sql.ErrNoRows) { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, xerr.New(xerr.NotFound, "vip program config not found") + } + if lockErr != nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, lockErr + } + // 等待 program 锁后必须使用 locking current read。普通 RR 快照仍停留在等待前, + // 看不到另一个 worker 刚提交的 run,随后 INSERT 会错误撞唯一键。 + run, exists, err = r.findVipDailyCoinRebateRun(ctx, tx, command.TaskDay, true) + if err != nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, err + } + if !exists { + levelAmounts := map[int32]int64{} + if program.Status == ledger.VipStatusActive { + levelAmounts, err = r.loadVipDailyCoinRebateLevelAmounts(ctx, tx, program.LevelCount) + if err != nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, err + } + } + levelAmountsJSON, marshalErr := json.Marshal(levelAmounts) + if marshalErr != nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, marshalErr + } + run = ledger.VipDailyCoinRebateRun{ + RunID: vipDailyCoinRebateRunID(command.AppCode, command.TaskDay), + TaskDay: command.TaskDay, + DayStartMS: dayStartMS, + DayEndMS: dayEndMS, + ConfigVersion: program.ConfigVersion, + LevelAmounts: levelAmounts, + LevelAmountsJSON: string(levelAmountsJSON), + Status: ledger.VipDailyCoinRebateRunStatusRunning, + CreatedAtMS: command.NowMS, + UpdatedAtMS: command.NowMS, + } + if _, err := tx.ExecContext(ctx, ` + INSERT INTO vip_daily_coin_rebate_runs ( + app_code, task_day, run_id, day_start_ms, day_end_ms, config_version, + level_amounts_json, status, last_user_id, scanned_count, created_count, + created_at_ms, updated_at_ms, completed_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0, ?, ?, 0)`, + command.AppCode, run.TaskDay, run.RunID, run.DayStartMS, run.DayEndMS, + run.ConfigVersion, run.LevelAmountsJSON, run.Status, command.NowMS, command.NowMS, + ); err != nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, err + } + } + } + run, exists, err = r.findVipDailyCoinRebateRun(ctx, tx, command.TaskDay, true) + if err != nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, err + } + if !exists { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, xerr.New(xerr.Internal, "vip daily coin rebate run creation failed") + } + if run.Status == ledger.VipDailyCoinRebateRunStatusCompleted { + if err := tx.Commit(); err != nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, err + } + return ledger.ProcessVipDailyCoinRebateBatchResult{Run: run}, nil + } + + // 没有任何已配置正数金额时,直接完成 run。扫描会员既不会生成返现,也只会放大日切压力。 + if len(run.LevelAmounts) == 0 { + run.Status = ledger.VipDailyCoinRebateRunStatusCompleted + run.CompletedAtMS = command.NowMS + run.UpdatedAtMS = command.NowMS + if err := r.updateVipDailyCoinRebateRun(ctx, tx, run); err != nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, err + } + if err := tx.Commit(); err != nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, err + } + return ledger.ProcessVipDailyCoinRebateBatchResult{Run: run}, nil + } + + candidates, err := r.listVipDailyCoinRebateCandidates(ctx, tx, run.DayStartMS, run.LastUserID, command.BatchSize) + if err != nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, err + } + createdCount := 0 + for _, candidate := range candidates { + if candidate.Level <= 0 || candidate.ExpiresAtMS <= run.DayStartMS { + continue + } + amount, enabled := run.LevelAmounts[candidate.Level] + if !enabled || amount <= 0 { + continue + } + rebateID := vipDailyCoinRebateID(command.AppCode, run.TaskDay, candidate.UserID) + result, insertErr := tx.ExecContext(ctx, ` + INSERT IGNORE INTO vip_daily_coin_rebates ( + app_code, rebate_id, run_id, task_day, user_id, vip_level, vip_name, + coin_amount, status, available_at_ms, expires_at_ms, config_version, + claimed_at_ms, wallet_transaction_id, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, '', ?, ?)`, + command.AppCode, rebateID, run.RunID, run.TaskDay, candidate.UserID, candidate.Level, + candidate.VipName, amount, ledger.VipDailyCoinRebateStatusClaimable, + run.DayStartMS, run.DayEndMS, run.ConfigVersion, command.NowMS, command.NowMS, + ) + if insertErr != nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, insertErr + } + inserted, rowsErr := result.RowsAffected() + if rowsErr != nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, rowsErr + } + if inserted == 0 { + continue + } + createdCount++ + metadata := vipDailyCoinRebateMetadata{ + RebateID: rebateID, UserID: candidate.UserID, TaskDay: run.TaskDay, + VipLevel: candidate.Level, VipName: candidate.VipName, CoinAmount: amount, + AvailableAtMS: run.DayStartMS, ExpiresAtMS: run.DayEndMS, + ConfigVersion: run.ConfigVersion, CreatedAtMS: command.NowMS, + } + // available 不是余额变化,因此 transaction_id 使用稳定 rebate_id 占位;事件与返现行 + // 同事务提交,下游可直接用 rebate_id 做通知幂等源 ID。 + if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{{ + EventID: eventID(rebateID, vipDailyCoinRebateAvailableEvent, candidate.UserID, ledger.AssetCoin), + EventType: vipDailyCoinRebateAvailableEvent, + TransactionID: rebateID, + CommandID: vipDailyCoinRebateAvailableCommandID(run.TaskDay, candidate.UserID), + UserID: candidate.UserID, + AssetType: ledger.AssetCoin, + Payload: metadata, + CreatedAtMS: command.NowMS, + }}); err != nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, err + } + } + + if len(candidates) > 0 { + run.LastUserID = candidates[len(candidates)-1].UserID + } + run.ScannedCount += int64(len(candidates)) + run.CreatedCount += int64(createdCount) + run.UpdatedAtMS = command.NowMS + if len(candidates) < command.BatchSize { + run.Status = ledger.VipDailyCoinRebateRunStatusCompleted + run.CompletedAtMS = command.NowMS + } + if err := r.updateVipDailyCoinRebateRun(ctx, tx, run); err != nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, err + } + if err := tx.Commit(); err != nil { + return ledger.ProcessVipDailyCoinRebateBatchResult{}, err + } + return ledger.ProcessVipDailyCoinRebateBatchResult{ + Run: run, ScannedCount: len(candidates), CreatedCount: createdCount, + HasMore: run.Status == ledger.VipDailyCoinRebateRunStatusRunning, + }, nil +} + +// GetMyCurrentVipDailyCoinRebate 只读取当前 UTC task_day;未生成记录不视为错误。 +func (r *Repository) GetMyCurrentVipDailyCoinRebate(ctx context.Context, userID int64, nowMS int64) (ledger.VipDailyCoinRebate, bool, error) { + if r == nil || r.db == nil { + return ledger.VipDailyCoinRebate{}, false, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + taskDay := time.UnixMilli(nowMS).UTC().Format(vipDailyCoinRebateTaskDayLayout) + rebate, err := scanVipDailyCoinRebate(r.db.QueryRowContext(ctx, vipDailyCoinRebateSelectSQL()+` + WHERE app_code = ? AND user_id = ? AND task_day = ?`, appcode.FromContext(ctx), userID, taskDay)) + if errors.Is(err, sql.ErrNoRows) { + return ledger.VipDailyCoinRebate{}, false, nil + } + if err != nil { + return ledger.VipDailyCoinRebate{}, false, err + } + return projectVipDailyCoinRebateStatus(rebate, nowMS), true, nil +} + +// ListMyVipDailyCoinRebateStatuses 按 UTC 日倒序分页,禁止为状态展示扫描整张返现表。 +func (r *Repository) ListMyVipDailyCoinRebateStatuses(ctx context.Context, query ledger.ListVipDailyCoinRebateStatusesQuery) ([]ledger.VipDailyCoinRebate, int64, error) { + if r == nil || r.db == nil { + return nil, 0, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + appCode := appcode.FromContext(ctx) + if len(query.RebateIDs) > 0 { + args := []any{appCode, query.UserID} + args = append(args, walletOutboxEventArgs(query.RebateIDs)...) + rows, err := r.db.QueryContext(ctx, vipDailyCoinRebateSelectSQL()+` + WHERE app_code = ? AND user_id = ? AND rebate_id IN (`+walletOutboxPlaceholders(len(query.RebateIDs))+`) + ORDER BY task_day DESC, rebate_id DESC`, args...) + if err != nil { + return nil, 0, err + } + defer rows.Close() + items := make([]ledger.VipDailyCoinRebate, 0, len(query.RebateIDs)) + for rows.Next() { + item, scanErr := scanVipDailyCoinRebate(rows) + if scanErr != nil { + return nil, 0, scanErr + } + items = append(items, projectVipDailyCoinRebateStatus(item, query.NowMS)) + } + return items, int64(len(items)), rows.Err() + } + var total int64 + if err := r.db.QueryRowContext(ctx, ` + SELECT COUNT(*) FROM vip_daily_coin_rebates WHERE app_code = ? AND user_id = ?`, + appCode, query.UserID, + ).Scan(&total); err != nil { + return nil, 0, err + } + offset := (query.Page - 1) * query.PageSize + rows, err := r.db.QueryContext(ctx, vipDailyCoinRebateSelectSQL()+` + WHERE app_code = ? AND user_id = ? + ORDER BY task_day DESC, rebate_id DESC LIMIT ? OFFSET ?`, + appCode, query.UserID, query.PageSize, offset, + ) + if err != nil { + return nil, 0, err + } + defer rows.Close() + items := make([]ledger.VipDailyCoinRebate, 0, query.PageSize) + for rows.Next() { + item, scanErr := scanVipDailyCoinRebate(rows) + if scanErr != nil { + return nil, 0, scanErr + } + items = append(items, projectVipDailyCoinRebateStatus(item, query.NowMS)) + } + return items, total, rows.Err() +} + +// ClaimVipDailyCoinRebate 在返现行锁、COIN 账户锁、交易、分录和 outbox 的单一事务中完成领取。 +func (r *Repository) ClaimVipDailyCoinRebate(ctx context.Context, command ledger.ClaimVipDailyCoinRebateCommand) (ledger.ClaimVipDailyCoinRebateReceipt, error) { + if r == nil || r.db == nil { + return ledger.ClaimVipDailyCoinRebateReceipt{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, command.AppCode) + command.AppCode = appcode.FromContext(ctx) + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return ledger.ClaimVipDailyCoinRebateReceipt{}, err + } + defer func() { _ = tx.Rollback() }() + + requestHash := stableHash(fmt.Sprintf("vip_daily_coin_rebate_claim|%s|%d|%s", command.AppCode, command.UserID, command.RebateID)) + // 所有 VIP 写命令共用 command guard。不同 command 重复点击已领取返现虽然不再造交易, + // 也会持久化 guard,保证该 command 后续改投另一 rebate 时稳定返回 IDEMPOTENCY_CONFLICT。 + if err := r.lockVipCommand(ctx, tx, command.CommandID, bizTypeVipDailyCoinRebate, requestHash, command.NowMS, xerr.IdempotencyConflict); err != nil { + return ledger.ClaimVipDailyCoinRebateReceipt{}, err + } + rebate, err := scanVipDailyCoinRebate(tx.QueryRowContext(ctx, vipDailyCoinRebateSelectSQL()+` + WHERE app_code = ? AND rebate_id = ? AND user_id = ? FOR UPDATE`, + command.AppCode, command.RebateID, command.UserID, + )) + if errors.Is(err, sql.ErrNoRows) { + return ledger.ClaimVipDailyCoinRebateReceipt{}, xerr.New(xerr.VIPCoinRebateNotFound, "vip daily coin rebate not found") + } + if err != nil { + return ledger.ClaimVipDailyCoinRebateReceipt{}, err + } + if txRow, exists, lookupErr := r.lookupTransactionConsistent(ctx, tx, command.CommandID, requestHash, bizTypeVipDailyCoinRebate, xerr.IdempotencyConflict); lookupErr != nil || exists { + if lookupErr != nil || !exists { + return ledger.ClaimVipDailyCoinRebateReceipt{}, lookupErr + } + if rebate.WalletTransactionID != "" && rebate.WalletTransactionID != txRow.TransactionID { + return ledger.ClaimVipDailyCoinRebateReceipt{}, xerr.New(xerr.LedgerConflict, "vip daily coin rebate transaction mismatch") + } + balance, balanceErr := r.balanceAfterTransaction(ctx, tx, txRow.TransactionID, command.UserID, ledger.AssetCoin) + if balanceErr != nil { + return ledger.ClaimVipDailyCoinRebateReceipt{}, balanceErr + } + rebate.Status = ledger.VipDailyCoinRebateStatusClaimed + rebate.WalletTransactionID = txRow.TransactionID + if err := tx.Commit(); err != nil { + return ledger.ClaimVipDailyCoinRebateReceipt{}, err + } + return ledger.ClaimVipDailyCoinRebateReceipt{Rebate: rebate, TransactionID: txRow.TransactionID, CoinBalance: balance}, nil + } + if rebate.Status == ledger.VipDailyCoinRebateStatusClaimed || rebate.WalletTransactionID != "" { + // 不同 command 的重复点击也返回原成功回执;返现本身才是唯一业务操作, + // 不为第二个 command 伪造一笔钱包交易。若 command 已用于其他 payload,上面的幂等查询仍会拒绝。 + balance, balanceErr := r.balanceAfterTransaction(ctx, tx, rebate.WalletTransactionID, command.UserID, ledger.AssetCoin) + if balanceErr != nil { + return ledger.ClaimVipDailyCoinRebateReceipt{}, balanceErr + } + if err := tx.Commit(); err != nil { + return ledger.ClaimVipDailyCoinRebateReceipt{}, err + } + return ledger.ClaimVipDailyCoinRebateReceipt{ + Rebate: rebate, TransactionID: rebate.WalletTransactionID, CoinBalance: balance, + }, nil + } + // 领取窗严格为 [available_at_ms, expires_at_ms):日切毫秒可领取,次日日切毫秒不可领取。 + if command.NowMS < rebate.AvailableAtMS { + return ledger.ClaimVipDailyCoinRebateReceipt{}, xerr.New(xerr.Conflict, "vip daily coin rebate is not available yet") + } + if command.NowMS >= rebate.ExpiresAtMS { + return ledger.ClaimVipDailyCoinRebateReceipt{}, xerr.New(xerr.VIPCoinRebateExpired, "vip daily coin rebate expired") + } + if rebate.CoinAmount <= 0 { + return ledger.ClaimVipDailyCoinRebateReceipt{}, xerr.New(xerr.LedgerConflict, "vip daily coin rebate amount is invalid") + } + + account, err := r.lockAccount(ctx, tx, command.UserID, ledger.AssetCoin, true, command.NowMS) + if err != nil { + return ledger.ClaimVipDailyCoinRebateReceipt{}, err + } + transactionID := transactionID(command.AppCode, command.CommandID) + metadata := vipDailyCoinRebateMetadata{ + RebateID: rebate.RebateID, UserID: rebate.UserID, TaskDay: rebate.TaskDay, + VipLevel: rebate.VipLevel, VipName: rebate.VipName, CoinAmount: rebate.CoinAmount, + AvailableAtMS: rebate.AvailableAtMS, ExpiresAtMS: rebate.ExpiresAtMS, + ConfigVersion: rebate.ConfigVersion, CreatedAtMS: command.NowMS, + } + if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeVipDailyCoinRebate, requestHash, rebate.RebateID, metadata, command.NowMS); err != nil { + return ledger.ClaimVipDailyCoinRebateReceipt{}, err + } + if err := r.applyAccountDelta(ctx, tx, account, rebate.CoinAmount, 0, command.NowMS); err != nil { + return ledger.ClaimVipDailyCoinRebateReceipt{}, err + } + balance := ledger.AssetBalance{ + AppCode: command.AppCode, UserID: command.UserID, AssetType: ledger.AssetCoin, + AvailableAmount: account.AvailableAmount + rebate.CoinAmount, + FrozenAmount: account.FrozenAmount, Version: account.Version + 1, UpdatedAtMs: command.NowMS, + } + if err := r.insertEntry(ctx, tx, walletEntry{ + TransactionID: transactionID, UserID: command.UserID, AssetType: ledger.AssetCoin, + AvailableDelta: rebate.CoinAmount, AvailableAfter: balance.AvailableAmount, + FrozenAfter: balance.FrozenAmount, CreatedAtMS: command.NowMS, + }); err != nil { + return ledger.ClaimVipDailyCoinRebateReceipt{}, err + } + result, err := tx.ExecContext(ctx, ` + UPDATE vip_daily_coin_rebates + SET status = ?, claimed_at_ms = ?, wallet_transaction_id = ?, updated_at_ms = ? + WHERE app_code = ? AND rebate_id = ? AND user_id = ? AND status = ?`, + ledger.VipDailyCoinRebateStatusClaimed, command.NowMS, transactionID, command.NowMS, + command.AppCode, rebate.RebateID, command.UserID, ledger.VipDailyCoinRebateStatusClaimable, + ) + if err != nil { + return ledger.ClaimVipDailyCoinRebateReceipt{}, err + } + updated, err := result.RowsAffected() + if err != nil { + return ledger.ClaimVipDailyCoinRebateReceipt{}, err + } + if updated != 1 { + return ledger.ClaimVipDailyCoinRebateReceipt{}, xerr.New(xerr.LedgerConflict, "vip daily coin rebate claim conflict") + } + if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{ + balanceChangedEvent(transactionID, command.CommandID, command.UserID, ledger.AssetCoin, + rebate.CoinAmount, 0, balance.AvailableAmount, balance.FrozenAmount, balance.Version, + metadata, command.NowMS, bizTypeVipDailyCoinRebate), + }); err != nil { + return ledger.ClaimVipDailyCoinRebateReceipt{}, err + } + if err := tx.Commit(); err != nil { + return ledger.ClaimVipDailyCoinRebateReceipt{}, err + } + rebate.Status = ledger.VipDailyCoinRebateStatusClaimed + rebate.ClaimedAtMS = command.NowMS + rebate.WalletTransactionID = transactionID + rebate.UpdatedAtMS = command.NowMS + return ledger.ClaimVipDailyCoinRebateReceipt{Rebate: rebate, TransactionID: transactionID, CoinBalance: balance}, nil +} + +func (r *Repository) loadVipDailyCoinRebateLevelAmounts(ctx context.Context, tx *sql.Tx, levelCount int32) (map[int32]int64, error) { + rows, err := tx.QueryContext(ctx, ` + SELECT level, COALESCE(CAST(metadata_json AS CHAR), '') + FROM vip_level_benefits + WHERE app_code = ? AND level BETWEEN 1 AND ? AND benefit_code = ? AND status = ? + ORDER BY level ASC`, + appcode.FromContext(ctx), levelCount, ledger.VipBenefitCodeDailyCoinRebate, ledger.VipStatusActive, + ) + if err != nil { + return nil, err + } + defer rows.Close() + amounts := make(map[int32]int64) + for rows.Next() { + var level int32 + var metadataJSON string + if err := rows.Scan(&level, &metadataJSON); err != nil { + return nil, err + } + amount, err := parseVipDailyCoinRebateAmount(metadataJSON) + if err != nil { + return nil, xerr.New(xerr.Conflict, fmt.Sprintf("vip level %d daily_coin_rebate metadata is invalid", level)) + } + amounts[level] = amount + } + return amounts, rows.Err() +} + +// listVipDailyCoinRebateCandidates 先按 membership 主键取固定大小的一页,再只为本页用户还原 +// UTC 日切状态。不能在每一页对全 App history 做窗口排序;那会让日切批任务退化成重复全表扫描。 +// 迁移前 history 新增的有效期字段为 0,这些行不是可信的日切快照。必须先对全部历史排序, +// 再判断边界最近一条是否完整;否则过滤后再排序会越过较新未知变更,错误复活较旧 VIP。 +// 边界行不完整时改用首条后续 history.previous_* 或 current membership 安全兜底。 +func (r *Repository) listVipDailyCoinRebateCandidates(ctx context.Context, tx *sql.Tx, dayStartMS int64, lastUserID int64, limit int) ([]vipDailyCoinRebateCandidate, error) { + rows, err := tx.QueryContext(ctx, ` + WITH member_page AS ( + SELECT user_id, level, name, started_at_ms, expires_at_ms + FROM user_vip_memberships + WHERE app_code = ? AND user_id > ? + ORDER BY user_id ASC + LIMIT ? + ), ranked_history AS ( + SELECT h.user_id, h.new_level AS vip_level, h.new_expires_at_ms AS expires_at_ms, + ROW_NUMBER() OVER (PARTITION BY h.user_id ORDER BY h.created_at_ms DESC, h.history_id DESC) AS row_no + FROM user_vip_history h + INNER JOIN member_page p ON p.user_id = h.user_id + WHERE h.app_code = ? AND h.created_at_ms <= ? + ), ranked_after_history AS ( + SELECT h.user_id, h.previous_level AS vip_level, h.previous_expires_at_ms AS expires_at_ms, + ROW_NUMBER() OVER (PARTITION BY h.user_id ORDER BY h.created_at_ms ASC, h.history_id ASC) AS row_no + FROM user_vip_history h + INNER JOIN member_page p ON p.user_id = h.user_id + WHERE h.app_code = ? AND h.created_at_ms > ? + ), boundary_memberships AS ( + SELECT p.user_id, + COALESCE(h.vip_level, + CASE WHEN future.vip_level > 0 AND future.expires_at_ms > ? THEN future.vip_level + WHEN p.started_at_ms <= ? THEN p.level ELSE 0 END) AS vip_level, + COALESCE(h.expires_at_ms, + CASE WHEN future.vip_level > 0 AND future.expires_at_ms > ? THEN future.expires_at_ms + WHEN p.started_at_ms <= ? THEN p.expires_at_ms ELSE 0 END) AS expires_at_ms, + p.name AS membership_name + FROM member_page p + LEFT JOIN ranked_history h + ON h.user_id = p.user_id AND h.row_no = 1 + AND h.vip_level > 0 AND h.expires_at_ms > 0 + LEFT JOIN ranked_after_history future + ON future.user_id = p.user_id AND future.row_no = 1 + AND future.vip_level > 0 AND future.expires_at_ms > 0 + ) + SELECT b.user_id, b.vip_level, COALESCE(v.name, b.membership_name, CONCAT('VIP', b.vip_level)), b.expires_at_ms + FROM boundary_memberships b + LEFT JOIN vip_levels v ON v.app_code = ? AND v.level = b.vip_level + ORDER BY b.user_id ASC`, + appcode.FromContext(ctx), lastUserID, limit, + appcode.FromContext(ctx), dayStartMS, + appcode.FromContext(ctx), dayStartMS, + dayStartMS, dayStartMS, dayStartMS, dayStartMS, + appcode.FromContext(ctx), + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := make([]vipDailyCoinRebateCandidate, 0, limit) + for rows.Next() { + var item vipDailyCoinRebateCandidate + if err := rows.Scan(&item.UserID, &item.Level, &item.VipName, &item.ExpiresAtMS); err != nil { + return nil, err + } + items = append(items, item) + } + return items, rows.Err() +} + +func (r *Repository) findVipDailyCoinRebateRun(ctx context.Context, tx *sql.Tx, taskDay string, lock bool) (ledger.VipDailyCoinRebateRun, bool, error) { + query := ` + SELECT run_id, task_day, day_start_ms, day_end_ms, config_version, + CAST(level_amounts_json AS CHAR), status, last_user_id, scanned_count, + created_count, created_at_ms, updated_at_ms, completed_at_ms + FROM vip_daily_coin_rebate_runs + WHERE app_code = ? AND task_day = ?` + if lock { + query += ` FOR UPDATE` + } + var run ledger.VipDailyCoinRebateRun + if err := tx.QueryRowContext(ctx, query, appcode.FromContext(ctx), taskDay).Scan( + &run.RunID, &run.TaskDay, &run.DayStartMS, &run.DayEndMS, &run.ConfigVersion, + &run.LevelAmountsJSON, &run.Status, &run.LastUserID, &run.ScannedCount, + &run.CreatedCount, &run.CreatedAtMS, &run.UpdatedAtMS, &run.CompletedAtMS, + ); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return ledger.VipDailyCoinRebateRun{}, false, nil + } + return ledger.VipDailyCoinRebateRun{}, false, err + } + if err := json.Unmarshal([]byte(run.LevelAmountsJSON), &run.LevelAmounts); err != nil { + return ledger.VipDailyCoinRebateRun{}, false, xerr.New(xerr.LedgerConflict, "vip daily coin rebate run snapshot is invalid") + } + return run, true, nil +} + +func (r *Repository) updateVipDailyCoinRebateRun(ctx context.Context, tx *sql.Tx, run ledger.VipDailyCoinRebateRun) error { + _, err := tx.ExecContext(ctx, ` + UPDATE vip_daily_coin_rebate_runs + SET status = ?, last_user_id = ?, scanned_count = ?, created_count = ?, + updated_at_ms = ?, completed_at_ms = ? + WHERE app_code = ? AND task_day = ? AND run_id = ?`, + run.Status, run.LastUserID, run.ScannedCount, run.CreatedCount, + run.UpdatedAtMS, run.CompletedAtMS, appcode.FromContext(ctx), run.TaskDay, run.RunID, + ) + return err +} + +func vipDailyCoinRebateSelectSQL() string { + return ` + SELECT rebate_id, user_id, task_day, vip_level, vip_name, coin_amount, status, + available_at_ms, expires_at_ms, config_version, claimed_at_ms, + wallet_transaction_id, created_at_ms, updated_at_ms + FROM vip_daily_coin_rebates ` +} + +func scanVipDailyCoinRebate(scanner interface{ Scan(...any) error }) (ledger.VipDailyCoinRebate, error) { + var rebate ledger.VipDailyCoinRebate + err := scanner.Scan( + &rebate.RebateID, &rebate.UserID, &rebate.TaskDay, &rebate.VipLevel, + &rebate.VipName, &rebate.CoinAmount, &rebate.Status, &rebate.AvailableAtMS, + &rebate.ExpiresAtMS, &rebate.ConfigVersion, &rebate.ClaimedAtMS, + &rebate.WalletTransactionID, &rebate.CreatedAtMS, &rebate.UpdatedAtMS, + ) + return rebate, err +} + +func projectVipDailyCoinRebateStatus(rebate ledger.VipDailyCoinRebate, nowMS int64) ledger.VipDailyCoinRebate { + if rebate.Status == ledger.VipDailyCoinRebateStatusClaimable && nowMS >= rebate.ExpiresAtMS { + rebate.Status = ledger.VipDailyCoinRebateStatusExpired + } + return rebate +} + +func parseVipDailyCoinRebateAmount(metadataJSON string) (int64, error) { + decoder := json.NewDecoder(strings.NewReader(strings.TrimSpace(metadataJSON))) + decoder.UseNumber() + var metadata map[string]any + if err := decoder.Decode(&metadata); err != nil || metadata == nil { + return 0, errors.New("metadata_json must be an object") + } + raw, ok := metadata["coin_amount"] + if !ok { + return 0, errors.New("coin_amount is required") + } + number, ok := raw.(json.Number) + if !ok { + return 0, errors.New("coin_amount must be an integer") + } + amount, err := number.Int64() + if err != nil || amount <= 0 { + return 0, errors.New("coin_amount must be a positive int64") + } + return amount, nil +} + +func vipDailyCoinRebateRunID(appCode string, taskDay string) string { + return "vip_rebate_run_" + stableHash(appcode.Normalize(appCode)+"|"+taskDay) +} + +func vipDailyCoinRebateID(appCode string, taskDay string, userID int64) string { + return "vip_rebate_" + stableHash(fmt.Sprintf("%s|%s|%d", appcode.Normalize(appCode), taskDay, userID)) +} + +func vipDailyCoinRebateAvailableCommandID(taskDay string, userID int64) string { + return "vip_rebate_available:" + taskDay + ":" + strconv.FormatInt(userID, 10) +} diff --git a/services/wallet-service/internal/storage/mysql/vip_daily_coin_rebate_repository_test.go b/services/wallet-service/internal/storage/mysql/vip_daily_coin_rebate_repository_test.go new file mode 100644 index 00000000..f31e4882 --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/vip_daily_coin_rebate_repository_test.go @@ -0,0 +1,40 @@ +package mysql + +import ( + "math" + "testing" +) + +// TestParseVipDailyCoinRebateAmount 只接受 JSON 正 int64;不允许字符串、浮点数、零、负数或溢出值。 +func TestParseVipDailyCoinRebateAmount(t *testing.T) { + testCases := []struct { + name string + payload string + want int64 + valid bool + }{ + {name: "positive", payload: `{"coin_amount":123}`, want: 123, valid: true}, + {name: "max int64", payload: `{"coin_amount":9223372036854775807}`, want: math.MaxInt64, valid: true}, + {name: "missing", payload: `{}`, valid: false}, + {name: "string", payload: `{"coin_amount":"123"}`, valid: false}, + {name: "float", payload: `{"coin_amount":1.5}`, valid: false}, + {name: "zero", payload: `{"coin_amount":0}`, valid: false}, + {name: "negative", payload: `{"coin_amount":-1}`, valid: false}, + {name: "overflow", payload: `{"coin_amount":9223372036854775808}`, valid: false}, + {name: "array", payload: `[]`, valid: false}, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + amount, err := parseVipDailyCoinRebateAmount(testCase.payload) + if testCase.valid { + if err != nil || amount != testCase.want { + t.Fatalf("parse positive int64 mismatch: amount=%d err=%v", amount, err) + } + return + } + if err == nil { + t.Fatalf("invalid payload unexpectedly parsed as %d", amount) + } + }) + } +} diff --git a/services/wallet-service/internal/storage/mysql/vip_grant_repository.go b/services/wallet-service/internal/storage/mysql/vip_grant_repository.go index ace978e9..6fa3377d 100644 --- a/services/wallet-service/internal/storage/mysql/vip_grant_repository.go +++ b/services/wallet-service/internal/storage/mysql/vip_grant_repository.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "encoding/json" + "errors" "fmt" "hyapp/pkg/appcode" "hyapp/pkg/xerr" @@ -28,21 +29,42 @@ func (r *Repository) GrantVip(ctx context.Context, command ledger.GrantVipComman command.Reason = command.GrantSource } + requestHash := vipGrantRequestHash(command) + nowMs := time.Now().UnixMilli() tx, err := r.db.BeginTx(ctx, nil) if err != nil { return ledger.GrantVipReceipt{}, err } defer func() { _ = tx.Rollback() }() - requestHash := vipGrantRequestHash(command) - if txRow, exists, err := r.lookupTransaction(ctx, tx, command.CommandID, requestHash, bizTypeVIPGrant); err != nil || exists { + if err := r.lockVipCommand(ctx, tx, command.CommandID, bizTypeVIPGrant, requestHash, nowMs, xerr.LedgerConflict); err != nil { + return ledger.GrantVipReceipt{}, err + } + if _, exists, err := r.lookupTransactionConsistent(ctx, tx, command.CommandID, requestHash, bizTypeVIPGrant, xerr.LedgerConflict); err != nil || exists { if err != nil || !exists { return ledger.GrantVipReceipt{}, err } - return r.vipGrantReceiptForTransaction(ctx, tx, txRow, command.TargetUserID) + _ = tx.Rollback() + return r.replayVipGrant(ctx, command, requestHash) } - nowMs := time.Now().UnixMilli() + program, err := r.queryVipProgramConfig(ctx, tx.QueryRowContext(ctx, vipProgramSelectSQL()+` LOCK IN SHARE MODE`, command.AppCode)) + if errors.Is(err, sql.ErrNoRows) { + return ledger.GrantVipReceipt{}, xerr.New(xerr.NotFound, "vip program config not found") + } + if err != nil { + return ledger.GrantVipReceipt{}, err + } + if program.Status != ledger.VipStatusActive { + return ledger.GrantVipReceipt{}, xerr.New(xerr.Conflict, "vip program is disabled") + } + if command.Level > program.LevelCount { + return ledger.GrantVipReceipt{}, xerr.New(xerr.VIPLevelNotFound, "vip level exceeds program level_count") + } + // 体验卡体系的后台发放必须走 GrantVipTrialCard,禁止旧 GrantVip 绕过背包佩戴语义直接改会员表。 + if program.GrantMode != ledger.VipGrantModeDirectMembership { + return ledger.GrantVipReceipt{}, xerr.New(xerr.Conflict, "vip program requires trial card grant") + } level, err := r.getVipLevelForUpdate(ctx, tx, command.Level) if err != nil { return ledger.GrantVipReceipt{}, err @@ -58,6 +80,14 @@ func (r *Repository) GrantVip(ctx context.Context, command ledger.GrantVipComman if err != nil { return ledger.GrantVipReceipt{}, err } + expiryPolicy := program.SameLevelExpiryPolicy + if current.Active && command.Level > current.Level { + expiryPolicy = program.UpgradeExpiryPolicy + } + expectedStartedAtMS, expectedExpiresAtMS, err := vipActivationWindow(current, level, expiryPolicy, nowMs) + if err != nil { + return ledger.GrantVipReceipt{}, err + } transactionID := transactionID(command.AppCode, command.CommandID) recordID := "vip_grant_" + stableHash(command.AppCode+"|"+command.CommandID) expectedResourceGrantID := resourceGrantID(command.AppCode, "vip_reward:"+command.CommandID) @@ -67,6 +97,7 @@ func (r *Repository) GrantVip(ctx context.Context, command ledger.GrantVipComman "resource_grant_id": expectedResourceGrantID, "user_id": command.TargetUserID, "target_level": command.Level, + "vip_name": level.Name, "grant_source": command.GrantSource, "operator_user_id": command.OperatorUserID, "reason": command.Reason, @@ -74,8 +105,19 @@ func (r *Repository) GrantVip(ctx context.Context, command ledger.GrantVipComman "duration_ms": level.DurationMS, "transaction_id": transactionID, "required_recharge_bypassed": true, + "program_type": program.ProgramType, + "config_version": program.ConfigVersion, + "expiry_policy": expiryPolicy, + "previous_expires_at_ms": current.ExpiresAtMS, + "started_at_ms": expectedStartedAtMS, + "new_expires_at_ms": expectedExpiresAtMS, + "created_at_ms": nowMs, } if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeVIPGrant, requestHash, recordID, metadata, nowMs); err != nil { + if isMySQLDuplicateError(err) { + _ = tx.Rollback() + return r.replayVipGrant(ctx, command, requestHash) + } return ledger.GrantVipReceipt{}, err } activation, err := r.activateUserVip(ctx, tx, vipActivationCommand{ @@ -89,34 +131,85 @@ func (r *Repository) GrantVip(ctx context.Context, command ledger.GrantVipComman RecordID: recordID, Level: level, Current: current, + Program: program, + ExpiryPolicy: expiryPolicy, NowMS: nowMs, }) if err != nil { return ledger.GrantVipReceipt{}, err } if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{ - vipActivatedEvent(transactionID, command.CommandID, command.TargetUserID, command.GrantSource, activation.Action, activation.PreviousLevel, activation.VIP, level.RewardResourceGroupID, activation.ResourceGrantID, activation.RewardItems, nowMs), + vipActivatedEvent(transactionID, command.CommandID, command.TargetUserID, command.GrantSource, activation.Action, activation.PreviousLevel, activation.PreviousExpiresAtMS, activation.ExpiryPolicy, activation.VIP, level.RewardResourceGroupID, activation.ResourceGrantID, activation.RewardItems, nowMs), }); err != nil { return ledger.GrantVipReceipt{}, err } if err := tx.Commit(); err != nil { return ledger.GrantVipReceipt{}, err } + state, err := r.GetVipState(ctx, command.TargetUserID) + if err != nil { + return ledger.GrantVipReceipt{}, err + } return ledger.GrantVipReceipt{ TransactionID: transactionID, Vip: activation.VIP, RewardItems: activation.RewardItems, + State: state, }, nil } +func (r *Repository) replayVipGrant(ctx context.Context, command ledger.GrantVipCommand, requestHash string) (ledger.GrantVipReceipt, error) { + tx, err := r.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true, Isolation: sql.LevelRepeatableRead}) + if err != nil { + return ledger.GrantVipReceipt{}, err + } + defer func() { _ = tx.Rollback() }() + txRow, exists, err := r.lookupTransactionConsistent(ctx, tx, command.CommandID, requestHash, bizTypeVIPGrant, xerr.LedgerConflict) + if err != nil { + return ledger.GrantVipReceipt{}, err + } + if !exists { + return ledger.GrantVipReceipt{}, xerr.New(xerr.Unavailable, "vip grant idempotent receipt is not visible") + } + receipt, err := r.vipGrantReceiptForTransaction(ctx, tx, txRow, command.TargetUserID) + if err != nil { + return ledger.GrantVipReceipt{}, err + } + _ = tx.Rollback() + receipt.State, err = r.GetVipState(ctx, command.TargetUserID) + return receipt, err +} + func (r *Repository) vipGrantReceiptForTransaction(ctx context.Context, tx *sql.Tx, txRow transactionRow, userID int64) (ledger.GrantVipReceipt, error) { var metadata struct { ResourceGrantID string `json:"resource_grant_id"` + TargetLevel int32 `json:"target_level"` + VIPName string `json:"vip_name"` + StartedAtMS int64 `json:"started_at_ms"` + NewExpiresAtMS int64 `json:"new_expires_at_ms"` + ProgramType string `json:"program_type"` + ConfigVersion int64 `json:"config_version"` + CreatedAtMS int64 `json:"created_at_ms"` } _ = json.Unmarshal([]byte(txRow.MetadataJSON), &metadata) - vip, err := r.queryUserVip(ctx, tx, userID, time.Now().UnixMilli(), false) - if err != nil { - return ledger.GrantVipReceipt{}, err + vip := ledger.UserVip{ + UserID: userID, + Level: metadata.TargetLevel, + Name: metadata.VIPName, + Active: metadata.TargetLevel > 0 && metadata.NewExpiresAtMS > time.Now().UnixMilli(), + StartedAtMS: metadata.StartedAtMS, + ExpiresAtMS: metadata.NewExpiresAtMS, + UpdatedAtMS: metadata.CreatedAtMS, + ProgramType: metadata.ProgramType, + ConfigVersion: metadata.ConfigVersion, + } + if vip.Level <= 0 || vip.ExpiresAtMS <= 0 { + // 迁移前的历史赠送元数据没有完整快照,只能兼容读取当前会员;新命令始终走上面的稳定回执。 + var err error + vip, err = r.queryUserVip(ctx, tx, userID, time.Now().UnixMilli(), false) + if err != nil { + return ledger.GrantVipReceipt{}, err + } } rewards, err := r.rewardItemsByGrant(ctx, tx, metadata.ResourceGrantID, vip.ExpiresAtMS) if err != nil { diff --git a/services/wallet-service/internal/storage/mysql/vip_policy_test.go b/services/wallet-service/internal/storage/mysql/vip_policy_test.go new file mode 100644 index 00000000..9bbc4d5b --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/vip_policy_test.go @@ -0,0 +1,50 @@ +package mysql + +import ( + "testing" + "time" + + "hyapp/services/wallet-service/internal/domain/ledger" +) + +func TestVipActivationWindowReplaceFromNowDropsUpgradeRemainder(t *testing.T) { + nowMS := time.Now().UnixMilli() + durationMS := int64(30 * 24 * time.Hour / time.Millisecond) + current := ledger.UserVip{ + Level: 3, + Active: true, + StartedAtMS: nowMS - durationMS, + ExpiresAtMS: nowMS + int64(15*24*time.Hour/time.Millisecond), + } + startedAtMS, expiresAtMS, err := vipActivationWindow(current, ledger.VipLevel{Level: 4, DurationMS: durationMS}, ledger.VipExpiryPolicyReplaceFromNow, nowMS) + if err != nil { + t.Fatalf("vipActivationWindow failed: %v", err) + } + if startedAtMS != nowMS || expiresAtMS != nowMS+durationMS { + t.Fatalf("higher Fami VIP must replace from now: started=%d expires=%d now=%d duration=%d", startedAtMS, expiresAtMS, nowMS, durationMS) + } +} + +func TestVipActivationWindowKeepsSameLevelRenewalAndLegacyUpgrade(t *testing.T) { + nowMS := time.Now().UnixMilli() + durationMS := int64(7 * 24 * time.Hour / time.Millisecond) + current := ledger.UserVip{Level: 3, Active: true, StartedAtMS: nowMS - durationMS, ExpiresAtMS: nowMS + durationMS} + for _, testCase := range []struct { + name string + level int32 + policy string + }{ + {name: "same level in P1", level: 3, policy: ledger.VipExpiryPolicyReplaceFromNow}, + {name: "legacy upgrade", level: 4, policy: ledger.VipExpiryPolicyExtendRemaining}, + } { + t.Run(testCase.name, func(t *testing.T) { + startedAtMS, expiresAtMS, err := vipActivationWindow(current, ledger.VipLevel{Level: testCase.level, DurationMS: durationMS}, testCase.policy, nowMS) + if err != nil { + t.Fatalf("vipActivationWindow failed: %v", err) + } + if startedAtMS != current.StartedAtMS || expiresAtMS != current.ExpiresAtMS+durationMS { + t.Fatalf("renew/legacy must extend remaining time: started=%d expires=%d", startedAtMS, expiresAtMS) + } + }) + } +} diff --git a/services/wallet-service/internal/storage/mysql/vip_program_repository.go b/services/wallet-service/internal/storage/mysql/vip_program_repository.go new file mode 100644 index 00000000..3a01a948 --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/vip_program_repository.go @@ -0,0 +1,342 @@ +package mysql + +import ( + "context" + "database/sql" + "errors" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" + resourcedomain "hyapp/services/wallet-service/internal/domain/resource" + "strings" + "time" +) + +type vipProgramScanner interface { + Scan(dest ...any) error +} + +type vipBenefitRows interface { + Next() bool + Scan(dest ...any) error + Err() error + Close() error +} + +// vipSnapshotQuerier 是 VIP 组合读模型的最小查询能力;*sql.Tx 和 *sql.DB 都满足。 +// 组合读取统一接收该接口,防止 helper 意外逃逸到 r.db、破坏调用者的 repeatable-read 快照。 +type vipSnapshotQuerier interface { + QueryContext(context.Context, string, ...any) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...any) *sql.Row +} + +// GetVipProgramConfig 返回当前 App 唯一的 VIP 状态机配置和用于后台展示的权益目录。 +// 权益目录取最高等级的显式集合;运行时仍只查询用户有效等级对应的那一组数据。 +func (r *Repository) GetVipProgramConfig(ctx context.Context) (ledger.VipProgramConfig, []ledger.VipBenefit, error) { + if r == nil || r.db == nil { + return ledger.VipProgramConfig{}, nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + tx, err := r.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true, Isolation: sql.LevelRepeatableRead}) + if err != nil { + return ledger.VipProgramConfig{}, nil, err + } + defer func() { _ = tx.Rollback() }() + config, err := r.requireVipProgramConfig(ctx, tx) + if err != nil { + return ledger.VipProgramConfig{}, nil, err + } + benefits, err := r.listVipLevelBenefits(ctx, tx, config.LevelCount, false) + return config, benefits, err +} + +// GetVipState 合并付费会员和当前佩戴体验卡。启用体验卡时不按等级限制切换; +// 卡片过期、卸下或 App 关闭体验卡能力后都会自然回落到仍有效的付费会员。 +func (r *Repository) GetVipState(ctx context.Context, userID int64) (ledger.VipState, error) { + if r == nil || r.db == nil { + return ledger.VipState{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + if userID <= 0 { + return ledger.VipState{}, xerr.New(xerr.InvalidArgument, "user_id is required") + } + nowMS := time.Now().UnixMilli() + // 先清理失效装备指针,再用一个 repeatable-read 快照读取 program、会员、卡片和权益, + // 防止后台改配置时返回“新 config_version + 旧 benefits”一类混合状态。 + if err := r.pruneExpiredUserResourceEquipment(ctx, []int64{userID}, []string{resourcedomain.TypeVIPTrialCard}, nowMS); err != nil { + return ledger.VipState{}, err + } + tx, err := r.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true, Isolation: sql.LevelRepeatableRead}) + if err != nil { + return ledger.VipState{}, err + } + defer func() { _ = tx.Rollback() }() + config, err := r.requireVipProgramConfig(ctx, tx) + if err != nil { + return ledger.VipState{}, err + } + return r.getVipStateFromSnapshot(ctx, tx, userID, nowMS, config) +} + +// requireVipProgramConfig 从调用者指定的快照读取 App 配置,并把缺迁移/错 app_code 明确映射为业务错误。 +func (r *Repository) requireVipProgramConfig(ctx context.Context, querier vipSnapshotQuerier) (ledger.VipProgramConfig, error) { + config, err := r.queryVipProgramConfig(ctx, querier.QueryRowContext(ctx, vipProgramSelectSQL(), appcode.FromContext(ctx))) + if errors.Is(err, sql.ErrNoRows) { + return ledger.VipProgramConfig{}, xerr.New(xerr.NotFound, "vip program config not found") + } + return config, err +} + +// getVipStateFromSnapshot 只使用调用者事务装配 paid、已佩戴卡片、最终身份与权益。 +// raw 事实始终返回;program 总开关关闭时不产生 effective VIP,体验卡开关关闭时则仅回落 paid。 +func (r *Repository) getVipStateFromSnapshot(ctx context.Context, querier vipSnapshotQuerier, userID int64, nowMS int64, config ledger.VipProgramConfig) (ledger.VipState, error) { + paid, err := r.queryUserVip(ctx, querier, userID, nowMS, false) + if errors.Is(err, sql.ErrNoRows) { + paid = ledger.UserVip{UserID: userID, ProgramType: config.ProgramType, ConfigVersion: config.ConfigVersion} + } else if err != nil { + return ledger.VipState{}, err + } + + trial, err := r.queryEquippedVipTrialCardWithQuery(ctx, querier, userID, nowMS) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return ledger.VipState{}, err + } + if errors.Is(err, sql.ErrNoRows) { + trial = nil + } + settings, err := r.queryVipUserSettings(ctx, querier, userID, false) + if err != nil { + return ledger.VipState{}, err + } + + state := ledger.VipState{ + PaidVip: paid, + EquippedTrialCard: trial, + EffectiveSource: ledger.VipEffectiveSourceNone, + EvaluatedAtMS: nowMS, + ProgramConfig: config, + UserSettings: settings, + EffectiveVip: ledger.UserVip{ + UserID: userID, + ProgramType: config.ProgramType, + ConfigVersion: config.ConfigVersion, + }, + } + if config.Status != ledger.VipStatusActive { + return state, nil + } + if paid.Active { + state.EffectiveVip = paid + state.EffectiveSource = ledger.VipEffectiveSourcePaid + } + if config.TrialCardEnabled && trial != nil { + state.EffectiveVip = ledger.UserVip{ + UserID: userID, + Level: trial.Level, + Name: trial.Name, + Active: true, + StartedAtMS: trial.EffectiveAtMS, + ExpiresAtMS: trial.ExpiresAtMS, + UpdatedAtMS: trial.UpdatedAtMS, + ProgramType: config.ProgramType, + ConfigVersion: config.ConfigVersion, + } + state.EffectiveSource = ledger.VipEffectiveSourceTrial + } + if !state.EffectiveVip.Active || state.EffectiveVip.Level <= 0 { + return state, nil + } + benefits, err := r.listVipLevelBenefits(ctx, querier, state.EffectiveVip.Level, true) + if err != nil { + return ledger.VipState{}, err + } + if state.EffectiveSource == ledger.VipEffectiveSourceTrial { + filtered := benefits[:0] + for _, benefit := range benefits { + // 金币返现是付费会员硬边界,不能依赖后台复选框;即使历史配置误写为 true 也必须二次排除。 + if benefit.TrialEnabled && benefit.BenefitCode != ledger.VipBenefitCodeDailyCoinRebate { + filtered = append(filtered, benefit) + } + } + benefits = filtered + } + state.EffectiveBenefits = benefits + return state, nil +} + +// CheckVipBenefit 只匹配 wallet 已合并并过滤后的 effective_benefits,不能退化成 level >= N。 +func (r *Repository) CheckVipBenefit(ctx context.Context, userID int64, benefitCode string) (ledger.CheckVipBenefitResult, error) { + state, err := r.GetVipState(ctx, userID) + if err != nil { + return ledger.CheckVipBenefitResult{}, err + } + code := strings.ToLower(strings.TrimSpace(benefitCode)) + result := ledger.CheckVipBenefitResult{ + State: state, + DenialReason: "benefit_not_enabled", + EvaluatedAtMS: state.EvaluatedAtMS, + } + if !state.EffectiveVip.Active { + result.DenialReason = "vip_inactive" + return result, nil + } + for _, benefit := range state.EffectiveBenefits { + if strings.ToLower(strings.TrimSpace(benefit.BenefitCode)) != code { + continue + } + result.Benefit = benefit + // effective_benefits 保留用户实际拥有的权益资格;可关闭通知只在执行时做设置门禁, + // 从而客户端关闭后仍能看见权益并重新开启。 + if !state.UserSettings.AllowsBenefitSetting(code) { + result.DenialReason = "user_setting_disabled" + return result, nil + } + result.Allowed = true + result.DenialReason = "" + return result, nil + } + return result, nil +} + +func vipProgramSelectSQL() string { + return ` + SELECT app_code, program_type, level_count, same_level_expiry_policy, + upgrade_expiry_policy, downgrade_purchase_policy, benefit_inheritance_policy, + grant_mode, trial_card_enabled, status, config_version, updated_by_admin_id, + created_at_ms, updated_at_ms + FROM vip_program_configs + WHERE app_code = ?` +} + +func (r *Repository) queryVipProgramConfig(ctx context.Context, scanner vipProgramScanner) (ledger.VipProgramConfig, error) { + var config ledger.VipProgramConfig + err := scanner.Scan( + &config.AppCode, + &config.ProgramType, + &config.LevelCount, + &config.SameLevelExpiryPolicy, + &config.UpgradeExpiryPolicy, + &config.DowngradePurchasePolicy, + &config.BenefitInheritancePolicy, + &config.GrantMode, + &config.TrialCardEnabled, + &config.Status, + &config.ConfigVersion, + &config.UpdatedByAdminID, + &config.CreatedAtMS, + &config.UpdatedAtMS, + ) + return config, err +} + +type vipBenefitQuerier interface { + QueryContext(context.Context, string, ...any) (*sql.Rows, error) +} + +func (r *Repository) listVipLevelBenefits(ctx context.Context, querier vipBenefitQuerier, level int32, activeOnly bool) ([]ledger.VipBenefit, error) { + query := ` + SELECT benefit_code, name, benefit_type, unlock_level, status, trial_enabled, + resource_id, resource_type, execution_scope, auto_equip, sort_order, + metadata_json, created_at_ms, updated_at_ms + FROM vip_level_benefits + WHERE app_code = ? AND level = ?` + args := []any{appcode.FromContext(ctx), level} + if activeOnly { + query += ` AND status = ?` + args = append(args, ledger.VipStatusActive) + } + query += ` ORDER BY sort_order ASC, benefit_code ASC` + rows, err := querier.QueryContext(ctx, query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + benefits := make([]ledger.VipBenefit, 0) + for rows.Next() { + benefit, err := scanVipBenefit(rows) + if err != nil { + return nil, err + } + benefits = append(benefits, benefit) + } + return benefits, rows.Err() +} + +func scanVipBenefit(scanner vipProgramScanner) (ledger.VipBenefit, error) { + var benefit ledger.VipBenefit + var metadata sql.NullString + err := scanner.Scan( + &benefit.BenefitCode, + &benefit.Name, + &benefit.BenefitType, + &benefit.UnlockLevel, + &benefit.Status, + &benefit.TrialEnabled, + &benefit.ResourceID, + &benefit.ResourceType, + &benefit.ExecutionScope, + &benefit.AutoEquip, + &benefit.SortOrder, + &metadata, + &benefit.CreatedAtMS, + &benefit.UpdatedAtMS, + ) + if metadata.Valid { + benefit.MetadataJSON = metadata.String + } + return benefit, err +} + +type vipRowQuerier interface { + QueryRowContext(context.Context, string, ...any) *sql.Row +} + +func (r *Repository) queryEquippedVipTrialCardWithQuery(ctx context.Context, querier vipRowQuerier, userID int64, nowMS int64) (*ledger.VipTrialCard, error) { + row := querier.QueryRowContext(ctx, ` + SELECT c.trial_card_id, c.entitlement_id, c.resource_id, c.user_id, c.level, + c.name, c.status, c.duration_ms, c.effective_at_ms, c.expires_at_ms, + c.grant_source, c.source_grant_id, c.created_at_ms, c.updated_at_ms + FROM user_resource_equipment eq + JOIN user_vip_trial_cards c ON c.app_code = eq.app_code + AND c.user_id = eq.user_id AND c.entitlement_id = eq.entitlement_id + JOIN user_resource_entitlements e ON e.app_code = eq.app_code + AND e.user_id = eq.user_id AND e.entitlement_id = eq.entitlement_id + JOIN resources r ON r.app_code = eq.app_code AND r.resource_id = eq.resource_id + WHERE eq.app_code = ? AND eq.user_id = ? AND eq.resource_type = ? + AND c.status = ? AND c.effective_at_ms <= ? AND c.expires_at_ms > ? + AND e.status = 'active' AND e.effective_at_ms <= ? + AND (e.expires_at_ms = 0 OR e.expires_at_ms > ?) AND e.remaining_quantity > 0 + AND r.status = 'active' + LIMIT 1`, + appcode.FromContext(ctx), userID, resourcedomain.TypeVIPTrialCard, + ledger.VipTrialCardStatusActive, nowMS, nowMS, nowMS, nowMS, + ) + card, err := scanVipTrialCard(row, nowMS) + if err != nil { + return nil, err + } + card.Equipped = true + return &card, nil +} + +func scanVipTrialCard(scanner vipProgramScanner, nowMS int64) (ledger.VipTrialCard, error) { + var card ledger.VipTrialCard + err := scanner.Scan( + &card.TrialCardID, + &card.EntitlementID, + &card.ResourceID, + &card.UserID, + &card.Level, + &card.Name, + &card.Status, + &card.DurationMS, + &card.EffectiveAtMS, + &card.ExpiresAtMS, + &card.GrantSource, + &card.SourceGrantID, + &card.CreatedAtMS, + &card.UpdatedAtMS, + ) + if err == nil && card.ExpiresAtMS > nowMS { + card.RemainingDurationMS = card.ExpiresAtMS - nowMS + } + return card, err +} diff --git a/services/wallet-service/internal/storage/mysql/vip_purchase_repository.go b/services/wallet-service/internal/storage/mysql/vip_purchase_repository.go index 09bc246e..b5877ebc 100644 --- a/services/wallet-service/internal/storage/mysql/vip_purchase_repository.go +++ b/services/wallet-service/internal/storage/mysql/vip_purchase_repository.go @@ -22,21 +22,38 @@ func (r *Repository) PurchaseVip(ctx context.Context, command ledger.PurchaseVip ctx = contextWithCommandApp(ctx, command.AppCode) command.AppCode = appcode.FromContext(ctx) + requestHash := stableHash(fmt.Sprintf("vip_purchase|%s|%d|%d", command.AppCode, command.UserID, command.Level)) + nowMs := time.Now().UnixMilli() tx, err := r.db.BeginTx(ctx, nil) if err != nil { return ledger.PurchaseVipReceipt{}, err } defer func() { _ = tx.Rollback() }() - requestHash := stableHash(fmt.Sprintf("vip_purchase|%s|%d|%d", command.AppCode, command.UserID, command.Level)) - if txRow, exists, err := r.lookupTransaction(ctx, tx, command.CommandID, requestHash, bizTypeVIPPurchase); err != nil || exists { + if err := r.lockVipCommand(ctx, tx, command.CommandID, bizTypeVIPPurchase, requestHash, nowMs, xerr.LedgerConflict); err != nil { + return ledger.PurchaseVipReceipt{}, err + } + if _, exists, err := r.lookupTransactionConsistent(ctx, tx, command.CommandID, requestHash, bizTypeVIPPurchase, xerr.LedgerConflict); err != nil || exists { if err != nil || !exists { return ledger.PurchaseVipReceipt{}, err } - return r.vipPurchaseReceiptForTransaction(ctx, tx, txRow.TransactionID, command.UserID) + _ = tx.Rollback() + return r.replayVipPurchase(ctx, command, requestHash) } - nowMs := time.Now().UnixMilli() + program, err := r.queryVipProgramConfig(ctx, tx.QueryRowContext(ctx, vipProgramSelectSQL()+` LOCK IN SHARE MODE`, command.AppCode)) + if errors.Is(err, sql.ErrNoRows) { + return ledger.PurchaseVipReceipt{}, xerr.New(xerr.NotFound, "vip program config not found") + } + if err != nil { + return ledger.PurchaseVipReceipt{}, err + } + if program.Status != ledger.VipStatusActive { + return ledger.PurchaseVipReceipt{}, xerr.New(xerr.Conflict, "vip program is disabled") + } + if command.Level > program.LevelCount { + return ledger.PurchaseVipReceipt{}, xerr.New(xerr.VIPLevelNotFound, "vip level exceeds program level_count") + } level, err := r.getVipLevelForUpdate(ctx, tx, command.Level) if err != nil { return ledger.PurchaseVipReceipt{}, err @@ -49,15 +66,20 @@ func (r *Repository) PurchaseVip(ctx context.Context, command ledger.PurchaseVip return ledger.PurchaseVipReceipt{}, err } previousLevel := int32(0) - baseExpiresAt := nowMs if current.Active { previousLevel = current.Level - baseExpiresAt = current.ExpiresAtMS if current.Level > command.Level { return ledger.PurchaseVipReceipt{}, xerr.New(xerr.VIPDowngradeNotAllowed, "vip downgrade is not allowed") } } - newExpiresAt := baseExpiresAt + level.DurationMS + expiryPolicy := program.SameLevelExpiryPolicy + if current.Active && command.Level > current.Level { + expiryPolicy = program.UpgradeExpiryPolicy + } + startedAtMS, newExpiresAt, err := vipActivationWindow(current, level, expiryPolicy, nowMs) + if err != nil { + return ledger.PurchaseVipReceipt{}, err + } account, err := r.lockAccount(ctx, tx, command.UserID, ledger.AssetCoin, false, nowMs) if err != nil { return ledger.PurchaseVipReceipt{}, err @@ -70,20 +92,30 @@ func (r *Repository) PurchaseVip(ctx context.Context, command ledger.PurchaseVip orderID := "vip_order_" + stableHash(command.AppCode+"|"+command.CommandID) coinBalanceAfter := account.AvailableAmount - level.PriceCoin metadata := map[string]any{ - "app_code": command.AppCode, - "order_id": orderID, - "user_id": command.UserID, - "target_level": command.Level, - "previous_level": previousLevel, - "price_coin": level.PriceCoin, - "expires_at_ms": newExpiresAt, - "balance_after": coinBalanceAfter, - "reward_group_id": level.RewardResourceGroupID, - "duration_ms": level.DurationMS, + "app_code": command.AppCode, + "order_id": orderID, + "user_id": command.UserID, + "target_level": command.Level, + "vip_name": level.Name, + "previous_level": previousLevel, + "price_coin": level.PriceCoin, + "expires_at_ms": newExpiresAt, + "balance_after": coinBalanceAfter, + "reward_group_id": level.RewardResourceGroupID, + "duration_ms": level.DurationMS, + "previous_expires_at_ms": current.ExpiresAtMS, + "started_at_ms": startedAtMS, + "program_type": program.ProgramType, + "config_version": program.ConfigVersion, + "expiry_policy": expiryPolicy, // 兼容旧账务元数据字段;业务已不再按累计充值限制 VIP 购买,因此固定记录 0。 "required_recharge_coin_amount": int64(0), } if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeVIPPurchase, requestHash, orderID, metadata, nowMs); err != nil { + if isMySQLDuplicateError(err) { + _ = tx.Rollback() + return r.replayVipPurchase(ctx, command, requestHash) + } return ledger.PurchaseVipReceipt{}, err } if err := r.applyAccountDelta(ctx, tx, account, -level.PriceCoin, 0, nowMs); err != nil { @@ -112,6 +144,8 @@ func (r *Repository) PurchaseVip(ctx context.Context, command ledger.PurchaseVip RecordID: orderID, Level: level, Current: current, + Program: program, + ExpiryPolicy: expiryPolicy, NowMS: nowMs, }) if err != nil { @@ -119,11 +153,15 @@ func (r *Repository) PurchaseVip(ctx context.Context, command ledger.PurchaseVip } if _, err := tx.ExecContext(ctx, ` INSERT INTO vip_purchase_orders ( - app_code, order_id, command_id, user_id, target_level, previous_level, price_coin, - status, wallet_transaction_id, resource_grant_id, request_hash, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, 'succeeded', ?, ?, ?, ?, ?)`, + app_code, order_id, command_id, user_id, target_level, previous_level, + previous_expires_at_ms, new_expires_at_ms, price_coin, duration_ms, + program_type, config_version, expiry_policy, status, wallet_transaction_id, + resource_grant_id, request_hash, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'succeeded', ?, ?, ?, ?, ?)`, command.AppCode, orderID, command.CommandID, command.UserID, command.Level, previousLevel, - level.PriceCoin, transactionID, activation.ResourceGrantID, requestHash, nowMs, nowMs, + current.ExpiresAtMS, activation.VIP.ExpiresAtMS, level.PriceCoin, level.DurationMS, + program.ProgramType, program.ConfigVersion, expiryPolicy, transactionID, + activation.ResourceGrantID, requestHash, nowMs, nowMs, ); err != nil { return ledger.PurchaseVipReceipt{}, err } @@ -147,7 +185,7 @@ func (r *Repository) PurchaseVip(ctx context.Context, command ledger.PurchaseVip Payload: metadata, CreatedAtMS: nowMs, }, - vipActivatedEvent(transactionID, command.CommandID, command.UserID, ledger.VipGrantSourcePurchase, activation.Action, activation.PreviousLevel, activation.VIP, level.RewardResourceGroupID, activation.ResourceGrantID, activation.RewardItems, nowMs), + vipActivatedEvent(transactionID, command.CommandID, command.UserID, ledger.VipGrantSourcePurchase, activation.Action, activation.PreviousLevel, activation.PreviousExpiresAtMS, activation.ExpiryPolicy, activation.VIP, level.RewardResourceGroupID, activation.ResourceGrantID, activation.RewardItems, nowMs), }); err != nil { return ledger.PurchaseVipReceipt{}, err } @@ -155,6 +193,10 @@ func (r *Repository) PurchaseVip(ctx context.Context, command ledger.PurchaseVip return ledger.PurchaseVipReceipt{}, err } + state, err := r.GetVipState(ctx, command.UserID) + if err != nil { + return ledger.PurchaseVipReceipt{}, err + } return ledger.PurchaseVipReceipt{ OrderID: orderID, TransactionID: transactionID, @@ -162,22 +204,44 @@ func (r *Repository) PurchaseVip(ctx context.Context, command ledger.PurchaseVip CoinSpent: level.PriceCoin, CoinBalanceAfter: coinBalanceAfter, RewardItems: activation.RewardItems, + State: state, }, nil } +func (r *Repository) replayVipPurchase(ctx context.Context, command ledger.PurchaseVipCommand, requestHash string) (ledger.PurchaseVipReceipt, error) { + tx, err := r.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true, Isolation: sql.LevelRepeatableRead}) + if err != nil { + return ledger.PurchaseVipReceipt{}, err + } + defer func() { _ = tx.Rollback() }() + txRow, exists, err := r.lookupTransactionConsistent(ctx, tx, command.CommandID, requestHash, bizTypeVIPPurchase, xerr.LedgerConflict) + if err != nil { + return ledger.PurchaseVipReceipt{}, err + } + if !exists { + return ledger.PurchaseVipReceipt{}, xerr.New(xerr.Unavailable, "vip purchase idempotent receipt is not visible") + } + receipt, err := r.vipPurchaseReceiptForTransaction(ctx, tx, txRow, command.UserID) + if err != nil { + return ledger.PurchaseVipReceipt{}, err + } + _ = tx.Rollback() + receipt.State, err = r.GetVipState(ctx, command.UserID) + return receipt, err +} + func (r *Repository) activateUserVip(ctx context.Context, tx *sql.Tx, command vipActivationCommand) (vipActivationResult, error) { previousLevel := int32(0) - baseExpiresAt := command.NowMS - startedAt := command.NowMS if command.Current.Active { previousLevel = command.Current.Level - baseExpiresAt = command.Current.ExpiresAtMS - startedAt = command.Current.StartedAtMS if command.Current.Level > command.Level.Level { return vipActivationResult{}, xerr.New(xerr.VIPDowngradeNotAllowed, "vip downgrade is not allowed") } } - expiresAt := baseExpiresAt + command.Level.DurationMS + startedAt, expiresAt, err := vipActivationWindow(command.Current, command.Level, command.ExpiryPolicy, command.NowMS) + if err != nil { + return vipActivationResult{}, err + } rewardItems, resourceGrantID, err := r.applyVipRewardGroup(ctx, tx, vipRewardGrantCommand{ AppCode: command.AppCode, CommandID: command.CommandID, @@ -191,38 +255,73 @@ func (r *Repository) activateUserVip(ctx context.Context, tx *sql.Tx, command vi } if _, err := tx.ExecContext(ctx, ` UPDATE user_vip_memberships - SET level = ?, name = ?, status = ?, started_at_ms = ?, expires_at_ms = ?, updated_at_ms = ? + SET level = ?, name = ?, status = ?, program_type = ?, config_version = ?, + started_at_ms = ?, expires_at_ms = ?, updated_at_ms = ? WHERE app_code = ? AND user_id = ?`, - command.Level.Level, command.Level.Name, ledger.VipStatusActive, startedAt, expiresAt, command.NowMS, command.AppCode, command.UserID, + command.Level.Level, command.Level.Name, ledger.VipStatusActive, + command.Program.ProgramType, command.Program.ConfigVersion, + startedAt, expiresAt, command.NowMS, command.AppCode, command.UserID, ); err != nil { return vipActivationResult{}, err } action := vipActivationAction(command.Source, previousLevel, command.Level.Level) if _, err := tx.ExecContext(ctx, ` INSERT INTO user_vip_history ( - app_code, user_id, action, previous_level, new_level, order_id, created_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?)`, - command.AppCode, command.UserID, action, previousLevel, command.Level.Level, command.RecordID, command.NowMS, + app_code, user_id, action, previous_level, new_level, previous_expires_at_ms, + new_expires_at_ms, program_type, config_version, order_id, created_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + command.AppCode, command.UserID, action, previousLevel, command.Level.Level, + command.Current.ExpiresAtMS, expiresAt, command.Program.ProgramType, + command.Program.ConfigVersion, command.RecordID, command.NowMS, ); err != nil { return vipActivationResult{}, err } return vipActivationResult{ VIP: ledger.UserVip{ - UserID: command.UserID, - Level: command.Level.Level, - Name: command.Level.Name, - Active: true, - StartedAtMS: startedAt, - ExpiresAtMS: expiresAt, - UpdatedAtMS: command.NowMS, + UserID: command.UserID, + Level: command.Level.Level, + Name: command.Level.Name, + Active: true, + StartedAtMS: startedAt, + ExpiresAtMS: expiresAt, + UpdatedAtMS: command.NowMS, + ProgramType: command.Program.ProgramType, + ConfigVersion: command.Program.ConfigVersion, }, - PreviousLevel: previousLevel, - Action: action, - ResourceGrantID: resourceGrantID, - RewardItems: rewardItems, + PreviousLevel: previousLevel, + PreviousExpiresAtMS: command.Current.ExpiresAtMS, + ExpiryPolicy: command.ExpiryPolicy, + Action: action, + ResourceGrantID: resourceGrantID, + RewardItems: rewardItems, }, nil } +// vipActivationWindow 把有效期策略收敛成一个纯函数,购买、后台直发和测试共享同一边界。 +// replace_from_now 只作用于有效会员的高级升级;同级续费仍始终从当前截止时间累加。 +func vipActivationWindow(current ledger.UserVip, level ledger.VipLevel, expiryPolicy string, nowMS int64) (int64, int64, error) { + if level.DurationMS <= 0 { + return 0, 0, xerr.New(xerr.InvalidArgument, "vip duration_ms must be positive") + } + startedAtMS := nowMS + baseExpiresAtMS := nowMS + if current.Active { + startedAtMS = current.StartedAtMS + baseExpiresAtMS = current.ExpiresAtMS + if level.Level > current.Level && expiryPolicy == ledger.VipExpiryPolicyReplaceFromNow { + startedAtMS = nowMS + baseExpiresAtMS = nowMS + } + } + if expiryPolicy != ledger.VipExpiryPolicyExtendRemaining && expiryPolicy != ledger.VipExpiryPolicyReplaceFromNow { + return 0, 0, xerr.New(xerr.Conflict, "vip expiry policy is invalid") + } + if level.DurationMS > int64(^uint64(0)>>1)-baseExpiresAtMS { + return 0, 0, xerr.New(xerr.InvalidArgument, "vip expiry overflow") + } + return startedAtMS, baseExpiresAtMS + level.DurationMS, nil +} + func vipActivationAction(source string, previousLevel int32, newLevel int32) string { if source != ledger.VipGrantSourcePurchase { return source @@ -295,12 +394,23 @@ func (r *Repository) applyVipRewardGroup(ctx context.Context, tx *sql.Tx, comman if err != nil { return nil, "", err } - rewards = append(rewards, ledger.VipRewardItem{Quantity: inserted.Quantity, ExpiresAtMS: 0}) + assetType := resourcedomain.NormalizeWalletAssetType(item.WalletAssetType) + // 首次回执与幂等回放都使用同一资源快照形状,wallet_asset 不能在重试时凭空多出字段。 + rewards = append(rewards, ledger.VipRewardItem{ + ResourceCode: assetType, + ResourceType: "wallet_asset", + Name: assetType, + Quantity: inserted.Quantity, + ExpiresAtMS: 0, + }) continue } if err := validateActiveResourceGroupResource(item.Resource); err != nil { return nil, "", err } + if resourcedomain.NormalizeResourceType(item.Resource.ResourceType) == resourcedomain.TypeVIPTrialCard { + return nil, "", xerr.New(xerr.InvalidArgument, "vip reward group cannot contain vip_trial_card") + } inserted, err := r.applyGrantItem(ctx, tx, grantID, rewardCommandID, command.UserID, item.Resource, item.Quantity, durationMS, nowMs) if err != nil { return nil, "", err @@ -330,7 +440,7 @@ func (r *Repository) applyVipRewardGroup(ctx context.Context, tx *sql.Tx, comman return rewards, grantID, nil } -func vipActivatedEvent(transactionID string, commandID string, userID int64, source string, action string, previousLevel int32, vip ledger.UserVip, rewardGroupID int64, resourceGrantID string, rewardItems []ledger.VipRewardItem, nowMs int64) walletOutboxEvent { +func vipActivatedEvent(transactionID string, commandID string, userID int64, source string, action string, previousLevel int32, previousExpiresAtMS int64, expiryPolicy string, vip ledger.UserVip, rewardGroupID int64, resourceGrantID string, rewardItems []ledger.VipRewardItem, nowMs int64) walletOutboxEvent { payload := map[string]any{ "event_type": "VipActivated", "transaction_id": transactionID, @@ -339,10 +449,14 @@ func vipActivatedEvent(transactionID string, commandID string, userID int64, sou "source": source, "action": action, "previous_level": previousLevel, + "previous_expires_at_ms": previousExpiresAtMS, "level": vip.Level, "vip_name": vip.Name, "started_at_ms": vip.StartedAtMS, "expires_at_ms": vip.ExpiresAtMS, + "program_type": vip.ProgramType, + "config_version": vip.ConfigVersion, + "expiry_policy": expiryPolicy, "reward_resource_group_id": rewardGroupID, "resource_grant_id": resourceGrantID, "reward_items": vipRewardItemsNoticePayload(rewardItems), @@ -380,35 +494,71 @@ func vipRewardItemsNoticePayload(items []ledger.VipRewardItem) []map[string]any return payload } -func (r *Repository) vipPurchaseReceiptForTransaction(ctx context.Context, tx *sql.Tx, transactionID string, userID int64) (ledger.PurchaseVipReceipt, error) { +func (r *Repository) vipPurchaseReceiptForTransaction(ctx context.Context, tx *sql.Tx, txRow transactionRow, userID int64) (ledger.PurchaseVipReceipt, error) { var orderID string var level int32 var priceCoin int64 var grantID string + var expiresAtMS int64 + var durationMS int64 + var programType string + var configVersion int64 + var createdAtMS int64 + var currentLevelName string err := tx.QueryRowContext(ctx, ` - SELECT order_id, target_level, price_coin, resource_grant_id - FROM vip_purchase_orders - WHERE app_code = ? AND wallet_transaction_id = ?`, - appcode.FromContext(ctx), transactionID, - ).Scan(&orderID, &level, &priceCoin, &grantID) + SELECT o.order_id, o.target_level, o.price_coin, o.resource_grant_id, + o.new_expires_at_ms, o.duration_ms, o.program_type, o.config_version, + o.created_at_ms, COALESCE(v.name, '') + FROM vip_purchase_orders o + LEFT JOIN vip_levels v ON v.app_code = o.app_code AND v.level = o.target_level + WHERE o.app_code = ? AND o.wallet_transaction_id = ?`, + appcode.FromContext(ctx), txRow.TransactionID, + ).Scan(&orderID, &level, &priceCoin, &grantID, &expiresAtMS, &durationMS, &programType, &configVersion, &createdAtMS, ¤tLevelName) if err != nil { return ledger.PurchaseVipReceipt{}, err } - vip, err := r.queryUserVip(ctx, tx, userID, time.Now().UnixMilli(), false) + var metadata struct { + VIPName string `json:"vip_name"` + StartedAtMS int64 `json:"started_at_ms"` + ExpiresAtMS int64 `json:"expires_at_ms"` + } + _ = json.Unmarshal([]byte(txRow.MetadataJSON), &metadata) + if expiresAtMS <= 0 { + expiresAtMS = metadata.ExpiresAtMS + } + startedAtMS := metadata.StartedAtMS + if startedAtMS <= 0 { + startedAtMS = createdAtMS + if durationMS > 0 && expiresAtMS > durationMS && expiresAtMS-durationMS < startedAtMS { + startedAtMS = expiresAtMS - durationMS + } + } + vipName := strings.TrimSpace(metadata.VIPName) + if vipName == "" { + vipName = currentLevelName + } + vip := ledger.UserVip{ + UserID: userID, + Level: level, + Name: vipName, + Active: level > 0 && expiresAtMS > time.Now().UnixMilli(), + StartedAtMS: startedAtMS, + ExpiresAtMS: expiresAtMS, + UpdatedAtMS: createdAtMS, + ProgramType: programType, + ConfigVersion: configVersion, + } + balance, err := r.balanceAfterTransaction(ctx, tx, txRow.TransactionID, userID, ledger.AssetCoin) if err != nil { return ledger.PurchaseVipReceipt{}, err } - balance, err := r.balanceAfterTransaction(ctx, tx, transactionID, userID, ledger.AssetCoin) - if err != nil { - return ledger.PurchaseVipReceipt{}, err - } - rewards, err := r.rewardItemsByGrant(ctx, tx, grantID, vip.ExpiresAtMS) + rewards, err := r.rewardItemsByGrant(ctx, tx, grantID, expiresAtMS) if err != nil { return ledger.PurchaseVipReceipt{}, err } return ledger.PurchaseVipReceipt{ OrderID: orderID, - TransactionID: transactionID, + TransactionID: txRow.TransactionID, Vip: vip, CoinSpent: priceCoin, CoinBalanceAfter: balance.AvailableAmount, diff --git a/services/wallet-service/internal/storage/mysql/vip_query_repository.go b/services/wallet-service/internal/storage/mysql/vip_query_repository.go index 7f2d9ca7..3d9d763d 100644 --- a/services/wallet-service/internal/storage/mysql/vip_query_repository.go +++ b/services/wallet-service/internal/storage/mysql/vip_query_repository.go @@ -3,6 +3,7 @@ package mysql import ( "context" "database/sql" + "encoding/json" "errors" "hyapp/pkg/appcode" "hyapp/pkg/xerr" @@ -23,75 +24,104 @@ func (r *Repository) GetMyVip(ctx context.Context, userID int64) (ledger.UserVip return vip, err } -// ListVipPackages 返回当前会员和全部 active VIP 包。 -func (r *Repository) ListVipPackages(ctx context.Context, userID int64) (ledger.UserVip, []ledger.VipLevel, error) { +// ListVipPackages 在一个 repeatable-read 快照中返回最终 VIP 状态和全部 active 套餐。 +// state、program、level、benefit 与奖励资源必须使用同一事务,否则后台改版瞬间可能返回跨版本组合。 +func (r *Repository) ListVipPackages(ctx context.Context, userID int64) (ledger.VipState, []ledger.VipLevel, error) { if r == nil || r.db == nil { - return ledger.UserVip{}, nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + return ledger.VipState{}, nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") } - nowMs := time.Now().UnixMilli() - current, err := r.GetMyVip(ctx, userID) + nowMS := time.Now().UnixMilli() + if err := r.pruneExpiredUserResourceEquipment(ctx, []int64{userID}, []string{resourcedomain.TypeVIPTrialCard}, nowMS); err != nil { + return ledger.VipState{}, nil, err + } + tx, err := r.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true, Isolation: sql.LevelRepeatableRead}) if err != nil { - return ledger.UserVip{}, nil, err + return ledger.VipState{}, nil, err } - levels, err := r.listVipLevels(ctx, nowMs, current.Level) - return current, levels, err + defer func() { _ = tx.Rollback() }() + config, err := r.requireVipProgramConfig(ctx, tx) + if err != nil { + return ledger.VipState{}, nil, err + } + state, err := r.getVipStateFromSnapshot(ctx, tx, userID, nowMS, config) + if err != nil { + return ledger.VipState{}, nil, err + } + levels, err := r.listVipLevelsFromSnapshot(ctx, tx, config, nowMS, state.PaidVip.Level) + return state, levels, err } -// ListAdminVipLevels 返回后台配置页所需的全部 VIP 等级。 -func (r *Repository) ListAdminVipLevels(ctx context.Context) ([]ledger.VipLevel, error) { +// ListAdminVipLevels 返回同一 repeatable-read 快照中的 program 与全部动态等级。 +func (r *Repository) ListAdminVipLevels(ctx context.Context) (ledger.VipProgramConfig, []ledger.VipLevel, error) { if r == nil || r.db == nil { - return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + return ledger.VipProgramConfig{}, nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") } - rows, err := r.db.QueryContext(ctx, ` + tx, err := r.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true, Isolation: sql.LevelRepeatableRead}) + if err != nil { + return ledger.VipProgramConfig{}, nil, err + } + defer func() { _ = tx.Rollback() }() + config, err := r.requireVipProgramConfig(ctx, tx) + if err != nil { + return ledger.VipProgramConfig{}, nil, err + } + levels, err := r.listAdminVipLevelsFromSnapshot(ctx, tx, config, time.Now().UnixMilli()) + return config, levels, err +} + +// listAdminVipLevelsFromSnapshot 在调用者事务内装配完整后台等级读模型。 +// 必须先耗尽并关闭 level rows,再查询每级 rewards/benefits;MySQL 单连接不允许在活动 rows 上嵌套查询。 +func (r *Repository) listAdminVipLevelsFromSnapshot(ctx context.Context, querier vipSnapshotQuerier, config ledger.VipProgramConfig, nowMS int64) ([]ledger.VipLevel, error) { + rows, err := querier.QueryContext(ctx, ` SELECT level, name, status, price_coin, duration_ms, reward_resource_group_id, required_recharge_coin_amount, sort_order, created_at_ms, updated_at_ms FROM vip_levels - WHERE app_code = ? + WHERE app_code = ? AND level BETWEEN 1 AND ? ORDER BY level ASC`, - appcode.FromContext(ctx), + appcode.FromContext(ctx), config.LevelCount, ) if err != nil { return nil, err } - defer rows.Close() - - levels := make([]ledger.VipLevel, 0, 10) - for rows.Next() { - level, err := r.scanVipLevel(rows) - if err != nil { - return nil, err - } + levels, err := r.scanAndCloseVipLevels(rows, int(config.LevelCount)) + if err != nil { + return nil, err + } + for index := range levels { + level := &levels[index] if level.RewardResourceGroupID > 0 { - level.RewardItems, err = r.vipRewardItems(ctx, level.RewardResourceGroupID, time.Now().UnixMilli()+level.DurationMS) + level.RewardItems, err = r.vipRewardItemsWithQuery(ctx, querier, level.RewardResourceGroupID, nowMS+level.DurationMS) if err != nil { return nil, err } } - levels = append(levels, level) + level.Benefits, err = r.listVipLevelBenefits(ctx, querier, level.Level, false) + if err != nil { + return nil, err + } + level.ConfigVersion = config.ConfigVersion } - return levels, rows.Err() + return levels, nil } -func (r *Repository) listVipLevels(ctx context.Context, nowMs int64, currentLevel int32) ([]ledger.VipLevel, error) { - rows, err := r.db.QueryContext(ctx, ` +func (r *Repository) listVipLevelsFromSnapshot(ctx context.Context, querier vipSnapshotQuerier, config ledger.VipProgramConfig, nowMS int64, currentLevel int32) ([]ledger.VipLevel, error) { + rows, err := querier.QueryContext(ctx, ` SELECT level, name, status, price_coin, duration_ms, reward_resource_group_id, required_recharge_coin_amount, sort_order, created_at_ms, updated_at_ms FROM vip_levels - WHERE app_code = ? AND status = ? + WHERE app_code = ? AND status = ? AND level BETWEEN 1 AND ? ORDER BY sort_order ASC, level ASC`, - appcode.FromContext(ctx), ledger.VipStatusActive, + appcode.FromContext(ctx), ledger.VipStatusActive, config.LevelCount, ) if err != nil { return nil, err } - defer rows.Close() - - levels := make([]ledger.VipLevel, 0) - for rows.Next() { - level, err := r.scanVipLevel(rows) - if err != nil { - return nil, err - } + levels, err := r.scanAndCloseVipLevels(rows, int(config.LevelCount)) + if err != nil { + return nil, err + } + for index := range levels { + level := &levels[index] // VIP 累充门槛已下线,旧字段只作为兼容输出保留;购买资格只看当前 VIP 等级和配置启停。 level.UserRechargeCoinAmount = 0 level.RechargeGateRequired = false @@ -100,7 +130,25 @@ func (r *Repository) listVipLevels(ctx context.Context, nowMs int64, currentLeve if !level.CanPurchase { level.PurchaseLockedReason = "current_vip_higher" } - level.RewardItems, err = r.vipRewardItems(ctx, level.RewardResourceGroupID, nowMs+level.DurationMS) + level.RewardItems, err = r.vipRewardItemsWithQuery(ctx, querier, level.RewardResourceGroupID, nowMS+level.DurationMS) + if err != nil { + return nil, err + } + level.Benefits, err = r.listVipLevelBenefits(ctx, querier, level.Level, true) + if err != nil { + return nil, err + } + level.ConfigVersion = config.ConfigVersion + } + return levels, nil +} + +// scanAndCloseVipLevels 把主游标生命周期收敛在一个函数内,返回后调用方才能安全执行同事务子查询。 +func (r *Repository) scanAndCloseVipLevels(rows *sql.Rows, capacity int) ([]ledger.VipLevel, error) { + defer rows.Close() + levels := make([]ledger.VipLevel, 0, capacity) + for rows.Next() { + level, err := r.scanVipLevel(rows) if err != nil { return nil, err } @@ -132,9 +180,12 @@ func (r *Repository) scanVipLevel(scanner scanTarget) (ledger.VipLevel, error) { func (r *Repository) ensureUserVipForUpdate(ctx context.Context, tx *sql.Tx, userID int64, nowMs int64) (ledger.UserVip, error) { if _, err := tx.ExecContext(ctx, ` INSERT IGNORE INTO user_vip_memberships ( - app_code, user_id, level, name, status, started_at_ms, expires_at_ms, created_at_ms, updated_at_ms - ) VALUES (?, ?, 0, '', 'inactive', 0, 0, ?, ?)`, - appcode.FromContext(ctx), userID, nowMs, nowMs, + app_code, user_id, level, name, status, program_type, config_version, + started_at_ms, expires_at_ms, created_at_ms, updated_at_ms + ) + SELECT ?, ?, 0, '', 'inactive', program_type, config_version, 0, 0, ?, ? + FROM vip_program_configs WHERE app_code = ?`, + appcode.FromContext(ctx), userID, nowMs, nowMs, appcode.FromContext(ctx), ); err != nil { return ledger.UserVip{}, err } @@ -145,7 +196,8 @@ func (r *Repository) queryUserVip(ctx context.Context, querier interface { QueryRowContext(context.Context, string, ...any) *sql.Row }, userID int64, nowMs int64, lock bool) (ledger.UserVip, error) { query := ` - SELECT m.user_id, m.level, COALESCE(v.name, m.name), m.started_at_ms, m.expires_at_ms, m.updated_at_ms + SELECT m.user_id, m.level, COALESCE(v.name, m.name), m.started_at_ms, m.expires_at_ms, + m.updated_at_ms, m.program_type, m.config_version FROM user_vip_memberships m LEFT JOIN vip_levels v ON v.app_code = m.app_code AND v.level = m.level WHERE m.app_code = ? AND m.user_id = ?` @@ -153,7 +205,16 @@ func (r *Repository) queryUserVip(ctx context.Context, querier interface { query += ` FOR UPDATE` } var vip ledger.UserVip - if err := querier.QueryRowContext(ctx, query, appcode.FromContext(ctx), userID).Scan(&vip.UserID, &vip.Level, &vip.Name, &vip.StartedAtMS, &vip.ExpiresAtMS, &vip.UpdatedAtMS); err != nil { + if err := querier.QueryRowContext(ctx, query, appcode.FromContext(ctx), userID).Scan( + &vip.UserID, + &vip.Level, + &vip.Name, + &vip.StartedAtMS, + &vip.ExpiresAtMS, + &vip.UpdatedAtMS, + &vip.ProgramType, + &vip.ConfigVersion, + ); err != nil { return ledger.UserVip{}, err } vip.Active = vip.Level > 0 && vip.ExpiresAtMS > nowMs @@ -165,10 +226,15 @@ func (r *Repository) queryUserVip(ctx context.Context, querier interface { } func (r *Repository) vipRewardItems(ctx context.Context, groupID int64, expiresAtMS int64) ([]ledger.VipRewardItem, error) { + return r.vipRewardItemsWithQuery(ctx, r.db, groupID, expiresAtMS) +} + +// vipRewardItemsWithQuery 必须沿用调用者给定的 DB/事务;套餐列表通过它确保资源组也来自同一配置快照。 +func (r *Repository) vipRewardItemsWithQuery(ctx context.Context, querier vipBenefitQuerier, groupID int64, expiresAtMS int64) ([]ledger.VipRewardItem, error) { if groupID <= 0 { return nil, nil } - items, err := r.listResourceGroupItemsWithQuery(ctx, r.db, groupID) + items, err := r.listResourceGroupItemsWithQuery(ctx, querier, groupID) if err != nil { return nil, err } @@ -197,13 +263,11 @@ func (r *Repository) rewardItemsByGrant(ctx context.Context, tx *sql.Tx, grantID return nil, nil } rows, err := tx.QueryContext(ctx, ` - SELECT i.resource_id, r.resource_code, r.resource_type, r.name, i.quantity, - COALESCE(r.asset_url, ''), COALESCE(r.preview_url, ''), COALESCE(r.animation_url, '') - FROM resource_grant_items i - JOIN resources r ON r.app_code = i.app_code AND r.resource_id = i.resource_id - WHERE i.app_code = ? AND i.grant_id = ? AND i.result_type = ? - ORDER BY i.grant_item_id ASC`, - appcode.FromContext(ctx), grantID, resourcedomain.ResultEntitlement, + SELECT resource_id, resource_snapshot_json, quantity, result_type + FROM resource_grant_items + WHERE app_code = ? AND grant_id = ? + ORDER BY grant_item_id ASC`, + appcode.FromContext(ctx), grantID, ) if err != nil { return nil, err @@ -211,20 +275,45 @@ func (r *Repository) rewardItemsByGrant(ctx context.Context, tx *sql.Tx, grantID defer rows.Close() items := make([]ledger.VipRewardItem, 0) for rows.Next() { - var item ledger.VipRewardItem - if err := rows.Scan( - &item.ResourceID, - &item.ResourceCode, - &item.ResourceType, - &item.Name, - &item.Quantity, - &item.AssetURL, - &item.PreviewURL, - &item.AnimationURL, - ); err != nil { + var resourceID int64 + var snapshotJSON string + var quantity int64 + var resultType string + if err := rows.Scan(&resourceID, &snapshotJSON, &quantity, &resultType); err != nil { return nil, err } - item.ExpiresAtMS = expiresAtMS + item := ledger.VipRewardItem{ResourceID: resourceID, Quantity: quantity} + switch resultType { + case resourcedomain.ResultEntitlement: + var resource resourcedomain.Resource + if err := json.Unmarshal([]byte(snapshotJSON), &resource); err != nil { + return nil, xerr.New(xerr.LedgerConflict, "vip reward resource snapshot is invalid") + } + item.ResourceID = resource.ResourceID + item.ResourceCode = resource.ResourceCode + item.ResourceType = resource.ResourceType + item.Name = resource.Name + item.ExpiresAtMS = expiresAtMS + item.AssetURL = resource.AssetURL + item.PreviewURL = resource.PreviewURL + item.AnimationURL = resource.AnimationURL + case resourcedomain.ResultWalletCredit: + var snapshot struct { + WalletAssetType string `json:"wallet_asset_type"` + WalletAssetAmount int64 `json:"wallet_asset_amount"` + } + if err := json.Unmarshal([]byte(snapshotJSON), &snapshot); err != nil || snapshot.WalletAssetType == "" { + return nil, xerr.New(xerr.LedgerConflict, "vip reward wallet snapshot is invalid") + } + item.ResourceCode = snapshot.WalletAssetType + item.ResourceType = "wallet_asset" + item.Name = snapshot.WalletAssetType + if snapshot.WalletAssetAmount > 0 { + item.Quantity = snapshot.WalletAssetAmount + } + default: + return nil, xerr.New(xerr.LedgerConflict, "vip reward result type is invalid") + } items = append(items, item) } return items, rows.Err() diff --git a/services/wallet-service/internal/storage/mysql/vip_trial_card_repository.go b/services/wallet-service/internal/storage/mysql/vip_trial_card_repository.go new file mode 100644 index 00000000..a2de54cd --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/vip_trial_card_repository.go @@ -0,0 +1,444 @@ +package mysql + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" + resourcedomain "hyapp/services/wallet-service/internal/domain/resource" + "strings" + "time" +) + +// GrantVipTrialCard 原子写入资源发放、背包 entitlement、体验卡元数据和 outbox。 +// 发卡时即开始绝对计时,但不会自动佩戴;切卡因此不会暂停、延长或销毁任何卡片。 +func (r *Repository) GrantVipTrialCard(ctx context.Context, command ledger.GrantVipTrialCardCommand) (ledger.GrantVipTrialCardReceipt, error) { + if r == nil || r.db == nil { + return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, command.AppCode) + command.AppCode = appcode.FromContext(ctx) + requestHash := stableHash(fmt.Sprintf("vip_trial_card|%s|%d|%d|%d|%d|%s|%d|%s", + command.AppCode, + command.TargetUserID, + command.Level, + command.DurationMS, + command.ResourceID, + command.GrantSource, + command.OperatorUserID, + strings.TrimSpace(command.Reason), + )) + + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return ledger.GrantVipTrialCardReceipt{}, err + } + defer func() { _ = tx.Rollback() }() + + nowMS := time.Now().UnixMilli() + if err := r.lockVipCommand(ctx, tx, command.CommandID, bizTypeVIPTrialCardGrant, requestHash, nowMS, xerr.IdempotencyConflict); err != nil { + return ledger.GrantVipTrialCardReceipt{}, err + } + existing, existingHash, err := r.queryVipTrialCardByCommand(ctx, tx, command.CommandID, nowMS, false) + if err == nil { + if existingHash != requestHash { + return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.IdempotencyConflict, "vip trial card command payload changed") + } + _ = tx.Rollback() + return r.vipTrialCardReceiptForCommand(ctx, command, requestHash, existing) + } + if !errors.Is(err, sql.ErrNoRows) { + return ledger.GrantVipTrialCardReceipt{}, err + } + + program, err := r.queryVipProgramConfig(ctx, tx.QueryRowContext(ctx, vipProgramSelectSQL()+` LOCK IN SHARE MODE`, command.AppCode)) + if errors.Is(err, sql.ErrNoRows) { + return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.NotFound, "vip program config not found") + } + if err != nil { + return ledger.GrantVipTrialCardReceipt{}, err + } + if program.Status != ledger.VipStatusActive || !program.TrialCardEnabled || program.GrantMode != ledger.VipGrantModeTrialCard { + return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.Conflict, "vip trial card is disabled for this app") + } + if command.Level > program.LevelCount { + return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.InvalidArgument, "vip trial card level exceeds program level_count") + } + level, err := r.getVipLevelForUpdate(ctx, tx, command.Level) + if err != nil { + return ledger.GrantVipTrialCardReceipt{}, err + } + if level.Status != ledger.VipStatusActive { + return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.VIPLevelDisabled, "vip level is disabled") + } + resourceID, err := r.resolveVipTrialCardResourceID(ctx, tx, command.Level, command.ResourceID) + if err != nil { + return ledger.GrantVipTrialCardReceipt{}, err + } + resource, err := r.getResourceForUpdate(ctx, tx, resourceID) + if err != nil { + return ledger.GrantVipTrialCardReceipt{}, err + } + if err := validateVipTrialCardResource(resource, command.Level); err != nil { + return ledger.GrantVipTrialCardReceipt{}, err + } + if err := validateGrantableResourceForSource(resource, vipResourceGrantSource(command.GrantSource)); err != nil { + return ledger.GrantVipTrialCardReceipt{}, err + } + + grantID := resourceGrantID(command.AppCode, command.CommandID) + if err := r.insertResourceGrant( + ctx, + tx, + grantID, + command.CommandID, + command.TargetUserID, + vipResourceGrantSource(command.GrantSource), + resourcedomain.GrantSubjectResource, + fmt.Sprintf("%d", resource.ResourceID), + requestHash, + "", + command.Reason, + command.OperatorUserID, + nowMS, + ); err != nil { + // 普通一致性读不会锁住“不存在的 command_id”。两个并发首发都可能通过前置查询, + // 但 resource_grants 唯一键会让后提交者等待首笔事务;确认 duplicate 后回滚本事务并 + // 读取已提交卡片,才能稳定返回幂等回执而不是泄漏 MySQL 1062。 + if isMySQLDuplicateError(err) { + _ = tx.Rollback() + return r.vipTrialCardReceiptForCommand(ctx, command, requestHash, ledger.VipTrialCard{}) + } + return ledger.GrantVipTrialCardReceipt{}, err + } + item, err := r.applyGrantItem(ctx, tx, grantID, command.CommandID, command.TargetUserID, resource, 1, command.DurationMS, nowMS) + if err != nil { + return ledger.GrantVipTrialCardReceipt{}, err + } + if item.EntitlementID == "" { + return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.LedgerConflict, "vip trial card did not create entitlement") + } + expiresAtMS := nowMS + command.DurationMS + trialCardID := "vip_card_" + stableHash(command.AppCode+"|"+command.CommandID) + if _, err := tx.ExecContext(ctx, ` + INSERT INTO user_vip_trial_cards ( + app_code, trial_card_id, command_id, request_hash, user_id, entitlement_id, + resource_id, level, name, status, duration_ms, effective_at_ms, expires_at_ms, + grant_source, source_grant_id, operator_user_id, reason, program_type, + config_version, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + command.AppCode, + trialCardID, + command.CommandID, + requestHash, + command.TargetUserID, + item.EntitlementID, + resource.ResourceID, + level.Level, + level.Name, + ledger.VipTrialCardStatusActive, + command.DurationMS, + nowMS, + expiresAtMS, + command.GrantSource, + grantID, + command.OperatorUserID, + command.Reason, + program.ProgramType, + program.ConfigVersion, + nowMS, + nowMS, + ); err != nil { + return ledger.GrantVipTrialCardReceipt{}, err + } + card := ledger.VipTrialCard{ + TrialCardID: trialCardID, + EntitlementID: item.EntitlementID, + ResourceID: resource.ResourceID, + UserID: command.TargetUserID, + Level: level.Level, + Name: level.Name, + Status: ledger.VipTrialCardStatusActive, + DurationMS: command.DurationMS, + EffectiveAtMS: nowMS, + ExpiresAtMS: expiresAtMS, + RemainingDurationMS: command.DurationMS, + GrantSource: command.GrantSource, + SourceGrantID: grantID, + CreatedAtMS: nowMS, + UpdatedAtMS: nowMS, + } + if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{ + resourceOutboxEvent("ResourceGranted", command.CommandID, command.TargetUserID, resource.ResourceID, map[string]any{ + "grant_id": grantID, + "resource": resource, + }, nowMS), + resourceOutboxEvent("VipTrialCardGranted", command.CommandID, command.TargetUserID, resource.ResourceID, map[string]any{ + "trial_card": card, + "program_type": program.ProgramType, + "config_version": program.ConfigVersion, + }, nowMS), + }); err != nil { + return ledger.GrantVipTrialCardReceipt{}, err + } + if err := tx.Commit(); err != nil { + return ledger.GrantVipTrialCardReceipt{}, err + } + state, err := r.GetVipState(ctx, command.TargetUserID) + if err != nil { + return ledger.GrantVipTrialCardReceipt{}, err + } + return ledger.GrantVipTrialCardReceipt{TrialCard: card, State: state, ServerTimeMS: nowMS}, nil +} + +func (r *Repository) vipTrialCardReceiptForCommand(ctx context.Context, command ledger.GrantVipTrialCardCommand, requestHash string, known ledger.VipTrialCard) (ledger.GrantVipTrialCardReceipt, error) { + nowMS := time.Now().UnixMilli() + card := known + storedHash := requestHash + if card.TrialCardID == "" { + tx, err := r.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return ledger.GrantVipTrialCardReceipt{}, err + } + defer func() { _ = tx.Rollback() }() + card, storedHash, err = r.queryVipTrialCardByCommand(ctx, tx, command.CommandID, nowMS, false) + if errors.Is(err, sql.ErrNoRows) { + return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.IdempotencyConflict, "command_id is already used by another resource grant") + } + if err != nil { + return ledger.GrantVipTrialCardReceipt{}, err + } + _ = tx.Rollback() + } + if storedHash != requestHash || card.UserID != command.TargetUserID { + return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.IdempotencyConflict, "vip trial card command payload changed") + } + state, err := r.GetVipState(ctx, command.TargetUserID) + if err != nil { + return ledger.GrantVipTrialCardReceipt{}, err + } + return ledger.GrantVipTrialCardReceipt{TrialCard: card, State: state, ServerTimeMS: nowMS}, nil +} + +// EquipVipTrialCard 只更新 vip_trial_card 单选装备槽。目标 entitlement 必须属于当前用户且仍在绝对有效期内。 +func (r *Repository) EquipVipTrialCard(ctx context.Context, command ledger.EquipVipTrialCardCommand) (ledger.VipTrialCard, ledger.VipState, error) { + if r == nil || r.db == nil { + return ledger.VipTrialCard{}, ledger.VipState{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, command.AppCode) + command.AppCode = appcode.FromContext(ctx) + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return ledger.VipTrialCard{}, ledger.VipState{}, err + } + defer func() { _ = tx.Rollback() }() + nowMS := time.Now().UnixMilli() + program, err := r.queryVipProgramConfig(ctx, tx.QueryRowContext(ctx, vipProgramSelectSQL(), command.AppCode)) + if err != nil { + return ledger.VipTrialCard{}, ledger.VipState{}, err + } + if program.Status != ledger.VipStatusActive || !program.TrialCardEnabled { + return ledger.VipTrialCard{}, ledger.VipState{}, xerr.New(xerr.Conflict, "vip trial card is disabled for this app") + } + // 锁序必须与 RevokeResourceGrant 保持 entitlement -> trial_card;反向先锁 card 会和后台撤销形成死锁。 + // entitlement 同时承担并发有效性门禁,避免撤销在校验后抢先提交又被重新写回 equipment。 + entitlement, err := r.getUserResourceEntitlementForUpdateTx(ctx, tx, command.EntitlementID) + if err != nil { + return ledger.VipTrialCard{}, ledger.VipState{}, err + } + card, err := r.queryVipTrialCardForUpdate(ctx, tx, command.UserID, command.EntitlementID, nowMS) + if errors.Is(err, sql.ErrNoRows) { + return ledger.VipTrialCard{}, ledger.VipState{}, xerr.New(xerr.NotFound, "vip trial card not found") + } + if err != nil { + return ledger.VipTrialCard{}, ledger.VipState{}, err + } + if entitlement.UserID != command.UserID || entitlement.ResourceID != card.ResourceID || + entitlement.Status != resourcedomain.StatusActive || entitlement.EffectiveAtMS > nowMS || + (entitlement.ExpiresAtMS > 0 && entitlement.ExpiresAtMS <= nowMS) || entitlement.RemainingQuantity <= 0 { + return ledger.VipTrialCard{}, ledger.VipState{}, xerr.New(xerr.Conflict, "vip trial card entitlement is inactive") + } + if resourcedomain.NormalizeResourceType(entitlement.Resource.ResourceType) != resourcedomain.TypeVIPTrialCard { + return ledger.VipTrialCard{}, ledger.VipState{}, xerr.New(xerr.Conflict, "vip trial card resource type mismatch") + } + + var currentEntitlementID string + err = tx.QueryRowContext(ctx, ` + SELECT entitlement_id FROM user_resource_equipment + WHERE app_code = ? AND user_id = ? AND resource_type = ? + LIMIT 1 FOR UPDATE`, command.AppCode, command.UserID, resourcedomain.TypeVIPTrialCard).Scan(¤tEntitlementID) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return ledger.VipTrialCard{}, ledger.VipState{}, err + } + if currentEntitlementID != card.EntitlementID { + if err := r.equipUserResourceEntitlementTx(ctx, tx, command.UserID, entitlement, nowMS); err != nil { + return ledger.VipTrialCard{}, ledger.VipState{}, err + } + if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{ + resourceOutboxEvent("UserResourceChanged", command.RequestID, command.UserID, card.ResourceID, map[string]any{ + "action": "equip", + "resource_type": resourcedomain.TypeVIPTrialCard, + "entitlement_id": card.EntitlementID, + "updated_at_ms": nowMS, + }, nowMS), + resourceOutboxEvent("VipEffectiveChanged", command.RequestID, command.UserID, card.ResourceID, map[string]any{ + "source": ledger.VipEffectiveSourceTrial, + "level": card.Level, + "entitlement_id": card.EntitlementID, + "updated_at_ms": nowMS, + }, nowMS), + }); err != nil { + return ledger.VipTrialCard{}, ledger.VipState{}, err + } + } + if err := tx.Commit(); err != nil { + return ledger.VipTrialCard{}, ledger.VipState{}, err + } + card.Equipped = true + state, err := r.GetVipState(ctx, command.UserID) + return card, state, err +} + +// UnequipVipTrialCard 清理当前装备指针,不更新卡片或 entitlement,保证之后仍可在剩余绝对有效期内重新佩戴。 +func (r *Repository) UnequipVipTrialCard(ctx context.Context, command ledger.UnequipVipTrialCardCommand) (bool, ledger.VipState, error) { + if r == nil || r.db == nil { + return false, ledger.VipState{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, command.AppCode) + command.AppCode = appcode.FromContext(ctx) + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return false, ledger.VipState{}, err + } + defer func() { _ = tx.Rollback() }() + nowMS := time.Now().UnixMilli() + result, err := tx.ExecContext(ctx, ` + DELETE FROM user_resource_equipment + WHERE app_code = ? AND user_id = ? AND resource_type = ?`, + command.AppCode, command.UserID, resourcedomain.TypeVIPTrialCard, + ) + if err != nil { + return false, ledger.VipState{}, err + } + affected, err := result.RowsAffected() + if err != nil { + return false, ledger.VipState{}, err + } + if affected > 0 { + if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{ + resourceOutboxEvent("UserResourceChanged", command.RequestID, command.UserID, 0, map[string]any{ + "action": "unequip", + "resource_type": resourcedomain.TypeVIPTrialCard, + "updated_at_ms": nowMS, + }, nowMS), + resourceOutboxEvent("VipEffectiveChanged", command.RequestID, command.UserID, 0, map[string]any{ + "source": "paid_or_none", + "updated_at_ms": nowMS, + }, nowMS), + }); err != nil { + return false, ledger.VipState{}, err + } + } + if err := tx.Commit(); err != nil { + return false, ledger.VipState{}, err + } + state, err := r.GetVipState(ctx, command.UserID) + return affected > 0, state, err +} + +func (r *Repository) resolveVipTrialCardResourceID(ctx context.Context, tx *sql.Tx, level int32, requested int64) (int64, error) { + if requested > 0 { + return requested, nil + } + var resourceID int64 + err := tx.QueryRowContext(ctx, ` + SELECT resource_id FROM resources + WHERE app_code = ? AND resource_code = ? + LIMIT 1 FOR UPDATE`, + appcode.FromContext(ctx), fmt.Sprintf("vip_trial_card_%d", level), + ).Scan(&resourceID) + if errors.Is(err, sql.ErrNoRows) { + return 0, xerr.New(xerr.NotFound, "vip trial card resource not configured") + } + return resourceID, err +} + +func validateVipTrialCardResource(resource resourcedomain.Resource, expectedLevel int32) error { + if resourcedomain.NormalizeResourceType(resource.ResourceType) != resourcedomain.TypeVIPTrialCard { + return xerr.New(xerr.InvalidArgument, "resource is not a vip trial card") + } + var metadata struct { + VIPLevel int32 `json:"vip_level"` + } + if err := json.Unmarshal([]byte(resource.MetadataJSON), &metadata); err != nil || metadata.VIPLevel != expectedLevel { + return xerr.New(xerr.Conflict, "vip trial card resource level mismatch") + } + if resourcedomain.NormalizeGrantStrategy(resource.GrantStrategy) != resourcedomain.GrantStrategyNewEntitlement { + return xerr.New(xerr.Conflict, "vip trial card must use new_entitlement strategy") + } + return nil +} + +func vipResourceGrantSource(source string) string { + switch ledger.NormalizeVipGrantSource(source) { + case ledger.VipGrantSourceManagerCenter: + return resourcedomain.GrantSourceManagerCenter + case ledger.VipGrantSourceAdmin: + return resourcedomain.GrantSourceAdmin + default: + return strings.ToLower(strings.TrimSpace(source)) + } +} + +func (r *Repository) queryVipTrialCardByCommand(ctx context.Context, tx *sql.Tx, commandID string, nowMS int64, lock bool) (ledger.VipTrialCard, string, error) { + query := ` + SELECT trial_card_id, entitlement_id, resource_id, user_id, level, name, status, + duration_ms, effective_at_ms, expires_at_ms, grant_source, source_grant_id, + created_at_ms, updated_at_ms, request_hash + FROM user_vip_trial_cards + WHERE app_code = ? AND command_id = ?` + if lock { + query += ` FOR UPDATE` + } + var card ledger.VipTrialCard + var requestHash string + err := tx.QueryRowContext(ctx, query, appcode.FromContext(ctx), commandID).Scan( + &card.TrialCardID, + &card.EntitlementID, + &card.ResourceID, + &card.UserID, + &card.Level, + &card.Name, + &card.Status, + &card.DurationMS, + &card.EffectiveAtMS, + &card.ExpiresAtMS, + &card.GrantSource, + &card.SourceGrantID, + &card.CreatedAtMS, + &card.UpdatedAtMS, + &requestHash, + ) + if err == nil && card.ExpiresAtMS > nowMS { + card.RemainingDurationMS = card.ExpiresAtMS - nowMS + } + return card, requestHash, err +} + +func (r *Repository) queryVipTrialCardForUpdate(ctx context.Context, tx *sql.Tx, userID int64, entitlementID string, nowMS int64) (ledger.VipTrialCard, error) { + row := tx.QueryRowContext(ctx, ` + SELECT trial_card_id, entitlement_id, resource_id, user_id, level, name, status, + duration_ms, effective_at_ms, expires_at_ms, grant_source, source_grant_id, + created_at_ms, updated_at_ms + FROM user_vip_trial_cards + WHERE app_code = ? AND user_id = ? AND entitlement_id = ? AND status = ? + AND effective_at_ms <= ? AND expires_at_ms > ? + FOR UPDATE`, + appcode.FromContext(ctx), userID, entitlementID, ledger.VipTrialCardStatusActive, nowMS, nowMS, + ) + return scanVipTrialCard(row, nowMS) +} diff --git a/services/wallet-service/internal/storage/mysql/vip_types.go b/services/wallet-service/internal/storage/mysql/vip_types.go index 3f8e5c68..b5686180 100644 --- a/services/wallet-service/internal/storage/mysql/vip_types.go +++ b/services/wallet-service/internal/storage/mysql/vip_types.go @@ -15,15 +15,19 @@ type vipActivationCommand struct { RecordID string Level ledger.VipLevel Current ledger.UserVip + Program ledger.VipProgramConfig + ExpiryPolicy string NowMS int64 } type vipActivationResult struct { - VIP ledger.UserVip - PreviousLevel int32 - Action string - ResourceGrantID string - RewardItems []ledger.VipRewardItem + VIP ledger.UserVip + PreviousLevel int32 + PreviousExpiresAtMS int64 + ExpiryPolicy string + Action string + ResourceGrantID string + RewardItems []ledger.VipRewardItem } type vipRewardGrantCommand struct { diff --git a/services/wallet-service/internal/storage/mysql/vip_user_settings_repository.go b/services/wallet-service/internal/storage/mysql/vip_user_settings_repository.go new file mode 100644 index 00000000..0bcd846e --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/vip_user_settings_repository.go @@ -0,0 +1,90 @@ +package mysql + +import ( + "context" + "database/sql" + "errors" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +// UpdateVipUserSettings 用一条 partial upsert 原子修改用户明确提交的开关。 +// INSERT 分支给未提交字段使用默认 true;UPDATE 分支则保留原值,避免两个并发 PATCH 互相覆盖。 +func (r *Repository) UpdateVipUserSettings(ctx context.Context, command ledger.UpdateVipUserSettingsCommand) (ledger.VipUserSettings, error) { + if r == nil || r.db == nil { + return ledger.VipUserSettings{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, command.AppCode) + command.AppCode = appcode.FromContext(ctx) + if command.UserID <= 0 || (command.RoomEntryNoticeEnabled == nil && command.OnlineGlobalNoticeEnabled == nil) { + return ledger.VipUserSettings{}, xerr.New(xerr.InvalidArgument, "vip user settings update is incomplete") + } + + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return ledger.VipUserSettings{}, err + } + defer func() { _ = tx.Rollback() }() + nowMS := time.Now().UTC().UnixMilli() + roomValue := true + roomProvided := command.RoomEntryNoticeEnabled != nil + if roomProvided { + roomValue = *command.RoomEntryNoticeEnabled + } + onlineValue := true + onlineProvided := command.OnlineGlobalNoticeEnabled != nil + if onlineProvided { + onlineValue = *command.OnlineGlobalNoticeEnabled + } + if _, err := tx.ExecContext(ctx, ` + INSERT INTO user_vip_settings ( + app_code, user_id, room_entry_notice_enabled, online_global_notice_enabled, + created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + room_entry_notice_enabled = IF(?, ?, room_entry_notice_enabled), + online_global_notice_enabled = IF(?, ?, online_global_notice_enabled), + updated_at_ms = ?`, + command.AppCode, command.UserID, roomValue, onlineValue, nowMS, nowMS, + roomProvided, roomValue, onlineProvided, onlineValue, nowMS, + ); err != nil { + return ledger.VipUserSettings{}, err + } + settings, err := r.queryVipUserSettings(ctx, tx, command.UserID, false) + if err != nil { + return ledger.VipUserSettings{}, err + } + if err := tx.Commit(); err != nil { + return ledger.VipUserSettings{}, err + } + return settings, nil +} + +// queryVipUserSettings 统一处理“无行即默认开启”。调用方可以传事务,从而让 VipState 的 +// program、会员、体验卡、benefits 和 settings 保持在同一 repeatable-read 快照。 +func (r *Repository) queryVipUserSettings(ctx context.Context, querier interface { + QueryRowContext(context.Context, string, ...any) *sql.Row +}, userID int64, lock bool) (ledger.VipUserSettings, error) { + query := ` + SELECT app_code, user_id, room_entry_notice_enabled, + online_global_notice_enabled, updated_at_ms + FROM user_vip_settings + WHERE app_code = ? AND user_id = ?` + if lock { + query += ` FOR UPDATE` + } + var settings ledger.VipUserSettings + if err := querier.QueryRowContext(ctx, query, appcode.FromContext(ctx), userID).Scan( + &settings.AppCode, &settings.UserID, &settings.RoomEntryNoticeEnabled, + &settings.OnlineGlobalNoticeEnabled, &settings.UpdatedAtMS, + ); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return ledger.DefaultVipUserSettings(appcode.FromContext(ctx), userID), nil + } + return ledger.VipUserSettings{}, err + } + return settings, nil +} diff --git a/services/wallet-service/internal/testutil/mysqltest/mysqltest.go b/services/wallet-service/internal/testutil/mysqltest/mysqltest.go index 9efa31ec..cc9a7226 100644 --- a/services/wallet-service/internal/testutil/mysqltest/mysqltest.go +++ b/services/wallet-service/internal/testutil/mysqltest/mysqltest.go @@ -4,6 +4,7 @@ package mysqltest import ( "context" "database/sql" + "encoding/json" "fmt" "strings" "testing" @@ -14,6 +15,31 @@ import ( mysqlstorage "hyapp/services/wallet-service/internal/storage/mysql" ) +// SetPointWithdrawalCoinSellerConfig seeds one app-scoped whitelist row for real ledger tests. +func (r *Repository) SetPointWithdrawalCoinSellerConfig(appCode string, sellerUserID int64, pointAmount int64, sellerCoinAmount int64, countryCodes []string, status string, sortOrder int) { + r.t.Helper() + countriesJSON, err := json.Marshal(countryCodes) + if err != nil { + r.t.Fatalf("marshal point withdrawal countries failed: %v", err) + } + if status == "" { + status = "active" + } + nowMS := time.Now().UnixMilli() + if _, err := r.schema.DB.ExecContext(context.Background(), ` + INSERT INTO point_withdrawal_coin_seller_configs ( + app_code, seller_user_id, sort_order, point_amount, seller_coin_amount, service_country_codes, + status, created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, 0, 0, ?, ?) + ON DUPLICATE KEY UPDATE + sort_order = VALUES(sort_order), point_amount = VALUES(point_amount), seller_coin_amount = VALUES(seller_coin_amount), + service_country_codes = VALUES(service_country_codes), status = VALUES(status), updated_at_ms = VALUES(updated_at_ms)`, + appCode, sellerUserID, sortOrder, pointAmount, sellerCoinAmount, countriesJSON, status, nowMS, nowMS, + ); err != nil { + r.t.Fatalf("seed point withdrawal coin seller config failed: %v", err) + } +} + // Repository wraps the production MySQL wallet repository with test seed helpers. type Repository struct { *mysqlstorage.Repository @@ -385,22 +411,28 @@ func (r *Repository) SetAssetBalanceForApp(appCode string, userID int64, assetTy // SeedWalletPolicyInstance writes the wallet runtime policy row consumed by gift settlement tests. func (r *Repository) SeedWalletPolicyInstance(appCode string, instanceCode string, regionID int64, status string, hostPointRatioPPM int64) { + r.SeedWalletPolicyInstanceWithAgency(appCode, instanceCode, regionID, status, hostPointRatioPPM, 0) +} + +// SeedWalletPolicyInstanceWithAgency 让账务测试显式声明 Agency 比例,避免普通 policy fixture 意外改变旧用例语义。 +func (r *Repository) SeedWalletPolicyInstanceWithAgency(appCode string, instanceCode string, regionID int64, status string, hostPointRatioPPM int64, agencyPointRatioPPM int64) { r.t.Helper() nowMs := time.Now().UnixMilli() _, err := r.schema.DB.ExecContext(context.Background(), ` INSERT INTO wallet_policy_instances ( app_code, instance_code, template_code, template_version, region_id, status, - effective_from_ms, effective_to_ms, host_point_asset_type, host_point_ratio_ppm, + effective_from_ms, effective_to_ms, host_point_asset_type, host_point_ratio_ppm, agency_point_ratio_ppm, points_per_usd, withdraw_fee_bps, rule_json, published_by_admin_id, published_at_ms, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, 'v1', ?, ?, 0, 0, 'POINT', ?, 100000, 500, JSON_OBJECT(), 90001, ?, ?, ?) + ) VALUES (?, ?, ?, 'v1', ?, ?, 0, 0, 'POINT', ?, ?, 100000, 500, JSON_OBJECT(), 90001, ?, ?, ?) ON DUPLICATE KEY UPDATE status = VALUES(status), host_point_ratio_ppm = VALUES(host_point_ratio_ppm), + agency_point_ratio_ppm = VALUES(agency_point_ratio_ppm), published_at_ms = VALUES(published_at_ms), updated_at_ms = VALUES(updated_at_ms) - `, strings.TrimSpace(appCode), strings.TrimSpace(instanceCode), strings.TrimSpace(instanceCode)+"_template", regionID, strings.TrimSpace(status), hostPointRatioPPM, nowMs, nowMs, nowMs) + `, strings.TrimSpace(appCode), strings.TrimSpace(instanceCode), strings.TrimSpace(instanceCode)+"_template", regionID, strings.TrimSpace(status), hostPointRatioPPM, agencyPointRatioPPM, nowMs, nowMs, nowMs) if err != nil { r.t.Fatalf("seed wallet policy instance failed: %v", err) } @@ -686,6 +718,155 @@ func (r *Repository) SetVIPLevelRechargeThreshold(level int32, totalCoinAmount i } } +// SetVIPLevelStatusForApp 只用于测试需要经过真实购买/体验卡入口的等级启停。 +func (r *Repository) SetVIPLevelStatusForApp(appCode string, level int32, status string, priceCoin int64) { + r.t.Helper() + if _, err := r.schema.DB.ExecContext(context.Background(), ` + UPDATE vip_levels + SET status = ?, price_coin = ?, updated_at_ms = ? + WHERE app_code = ? AND level = ?`, + status, priceCoin, time.Now().UTC().UnixMilli(), appCode, level, + ); err != nil { + r.t.Fatalf("set vip level status failed: %v", err) + } +} + +// SetVIPDailyCoinRebateAmount 写入测试专用等级金额并递增 program 版本,模拟后台完整配置发布。 +// amount<=0 时仍写入,供服务端校验/运行防线测试构造异常历史数据。 +func (r *Repository) SetVIPDailyCoinRebateAmount(appCode string, level int32, amount int64, status string) { + r.t.Helper() + nowMS := time.Now().UTC().UnixMilli() + _, err := r.schema.DB.ExecContext(context.Background(), ` + INSERT INTO vip_level_benefits ( + app_code, level, benefit_code, name, benefit_type, unlock_level, status, + trial_enabled, resource_id, resource_type, execution_scope, auto_equip, + sort_order, metadata_json, created_at_ms, updated_at_ms + ) VALUES (?, ?, 'daily_coin_rebate', '金币返现', 'function', ?, ?, FALSE, 0, '', 'wallet', FALSE, 250, + JSON_OBJECT('coin_amount', ?), ?, ?) + ON DUPLICATE KEY UPDATE + status = VALUES(status), trial_enabled = FALSE, execution_scope = 'wallet', + metadata_json = VALUES(metadata_json), updated_at_ms = VALUES(updated_at_ms)`, + appCode, level, level, status, amount, nowMS, nowMS, + ) + if err != nil { + r.t.Fatalf("set vip daily coin rebate amount failed: %v", err) + } + if _, err := r.schema.DB.ExecContext(context.Background(), ` + UPDATE vip_program_configs + SET config_version = config_version + 1, updated_at_ms = ? + WHERE app_code = ?`, nowMS, appCode); err != nil { + r.t.Fatalf("advance vip program version failed: %v", err) + } +} + +// SeedPaidVIPAtWindow 构造有完整 history 的付费 VIP,用于精确覆盖 UTC 日切边界。 +func (r *Repository) SeedPaidVIPAtWindow(appCode string, userID int64, level int32, startedAtMS int64, expiresAtMS int64) { + r.t.Helper() + var name string + var programType string + var configVersion int64 + if err := r.schema.DB.QueryRowContext(context.Background(), ` + SELECT l.name, p.program_type, p.config_version + FROM vip_levels l + JOIN vip_program_configs p ON p.app_code = l.app_code + WHERE l.app_code = ? AND l.level = ?`, appCode, level, + ).Scan(&name, &programType, &configVersion); err != nil { + r.t.Fatalf("read vip seed level failed: %v", err) + } + _, err := r.schema.DB.ExecContext(context.Background(), ` + INSERT INTO user_vip_memberships ( + app_code, user_id, level, name, status, program_type, config_version, + started_at_ms, expires_at_ms, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + level = VALUES(level), name = VALUES(name), status = 'active', + program_type = VALUES(program_type), config_version = VALUES(config_version), + started_at_ms = VALUES(started_at_ms), expires_at_ms = VALUES(expires_at_ms), + updated_at_ms = VALUES(updated_at_ms)`, + appCode, userID, level, name, programType, configVersion, + startedAtMS, expiresAtMS, startedAtMS, startedAtMS, + ) + if err != nil { + r.t.Fatalf("seed paid vip membership failed: %v", err) + } + if _, err := r.schema.DB.ExecContext(context.Background(), ` + INSERT INTO user_vip_history ( + app_code, user_id, action, previous_level, new_level, previous_expires_at_ms, + new_expires_at_ms, program_type, config_version, order_id, created_at_ms + ) VALUES (?, ?, 'test_seed', 0, ?, 0, ?, ?, ?, '', ?)`, + appCode, userID, level, expiresAtMS, programType, configVersion, startedAtMS, + ); err != nil { + r.t.Fatalf("seed paid vip history failed: %v", err) + } +} + +// SeedLegacyPaidVIPAtWindow 模拟 060 新增有效期列后的存量历史:会员主表仍有完整窗口, +// 但旧 history 因 ALTER DEFAULT 只有 0。运行时不得把这个 0 当作“日切时已过期”。 +func (r *Repository) SeedLegacyPaidVIPAtWindow(appCode string, userID int64, level int32, startedAtMS int64, expiresAtMS int64) { + r.t.Helper() + r.SeedPaidVIPAtWindow(appCode, userID, level, startedAtMS, expiresAtMS) + if _, err := r.schema.DB.ExecContext(context.Background(), ` + UPDATE user_vip_history + SET action = 'legacy_test_seed', previous_expires_at_ms = 0, new_expires_at_ms = 0 + WHERE app_code = ? AND user_id = ? AND action = 'test_seed' AND created_at_ms = ?`, + appCode, userID, startedAtMS, + ); err != nil { + r.t.Fatalf("convert paid vip history to legacy zero-expiry snapshot failed: %v", err) + } +} + +// ApplyVIPHistoryMigrationBaseline 在隔离真实 MySQL schema 中复制 060 的基线 INSERT。 +// baselineAtMS 可固定在待测日切前,用于验证后续日切能读取该完整快照;重复调用不应增行。 +func (r *Repository) ApplyVIPHistoryMigrationBaseline(baselineAtMS int64) { + r.t.Helper() + if _, err := r.schema.DB.ExecContext(context.Background(), ` + INSERT INTO user_vip_history ( + app_code, user_id, action, previous_level, new_level, previous_expires_at_ms, + new_expires_at_ms, program_type, config_version, order_id, created_at_ms + ) + SELECT + memberships.app_code, memberships.user_id, 'migration_baseline', 0, + memberships.level, 0, memberships.expires_at_ms, memberships.program_type, + memberships.config_version, 'vip_configurable_060_baseline', ? + FROM user_vip_memberships AS memberships + WHERE memberships.level > 0 + AND memberships.expires_at_ms > 0 + AND NOT EXISTS ( + SELECT 1 + FROM user_vip_history AS complete_history + WHERE complete_history.app_code = memberships.app_code + AND complete_history.user_id = memberships.user_id + AND complete_history.new_level > 0 + AND complete_history.new_expires_at_ms > 0 + ) + AND NOT EXISTS ( + SELECT 1 + FROM user_vip_history AS existing_baseline + WHERE existing_baseline.app_code = memberships.app_code + AND existing_baseline.user_id = memberships.user_id + AND existing_baseline.order_id = 'vip_configurable_060_baseline' + )`, baselineAtMS, + ); err != nil { + r.t.Fatalf("apply vip history migration baseline failed: %v", err) + } +} + +// WalletOutboxPayload returns the latest payload for a precise event assertion. +func (r *Repository) WalletOutboxPayload(appCode string, eventType string, userID int64) string { + r.t.Helper() + var payload string + if err := r.schema.DB.QueryRowContext(context.Background(), ` + SELECT CAST(payload AS CHAR) + FROM wallet_outbox + WHERE app_code = ? AND event_type = ? AND user_id = ? + ORDER BY created_at_ms DESC, event_id DESC LIMIT 1`, + appCode, eventType, userID, + ).Scan(&payload); err != nil { + r.t.Fatalf("read wallet outbox payload failed: %v", err) + } + return payload +} + // SetHostSalaryPolicy seeds the wallet runtime policy snapshot used by salary settlement tests. func (r *Repository) SetHostSalaryPolicy(policy ledger.HostSalaryPolicy) { r.t.Helper() @@ -766,7 +947,7 @@ func validateTableName(table string) string { "host_period_diamond_accounts", "host_period_diamond_entries", "host_agency_salary_policies", "host_agency_salary_policy_levels", "host_salary_settlement_progress", "host_salary_settlement_records", "coin_seller_stock_records", "gift_diamond_ratio_configs", "wallet_diamond_exchange_rules", - "vip_levels", "user_vip_memberships", "vip_purchase_orders", "user_vip_history", + "vip_program_configs", "vip_levels", "vip_level_benefits", "user_vip_memberships", "user_vip_settings", "vip_purchase_orders", "user_vip_history", "user_vip_trial_cards", "vip_command_locks", "vip_daily_coin_rebate_runs", "vip_daily_coin_rebates", "resources", "resource_groups", "resource_group_items", "resource_shop_items", "resource_shop_purchase_orders", "gift_type_configs", "gift_configs", "gift_config_regions", "user_gift_wall", "wallet_projection_events", "resource_grants", "resource_grant_items", "user_resource_entitlements", "user_resource_equipment": return table diff --git a/services/wallet-service/internal/transport/grpc/agency_host_gift_stats.go b/services/wallet-service/internal/transport/grpc/agency_host_gift_stats.go new file mode 100644 index 00000000..30c276ad --- /dev/null +++ b/services/wallet-service/internal/transport/grpc/agency_host_gift_stats.go @@ -0,0 +1,23 @@ +package grpc + +import ( + "context" + + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +func (s *Server) GetAgencyHostGiftStats(ctx context.Context, req *walletv1.GetAgencyHostGiftStatsRequest) (*walletv1.GetAgencyHostGiftStatsResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + stats, err := s.svc.GetAgencyHostGiftStats(ctx, req.GetAppCode(), ledger.AgencyHostGiftStatsQuery{ + HostUserIDs: req.GetHostUserIds(), StartAtMS: req.GetStartAtMs(), EndAtMS: req.GetEndAtMs(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.GetAgencyHostGiftStatsResponse{Stats: &walletv1.AgencyHostGiftStats{ + GiftIncome: stats.GiftIncome, GiftedHostCount: stats.GiftedHostCount, + }}, nil +} diff --git a/services/wallet-service/internal/transport/grpc/agency_point_share_stats.go b/services/wallet-service/internal/transport/grpc/agency_point_share_stats.go new file mode 100644 index 00000000..50c7da9f --- /dev/null +++ b/services/wallet-service/internal/transport/grpc/agency_point_share_stats.go @@ -0,0 +1,26 @@ +package grpc + +import ( + "context" + + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +func (s *Server) GetAgencyPointShareStats(ctx context.Context, req *walletv1.GetAgencyPointShareStatsRequest) (*walletv1.GetAgencyPointShareStatsResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + stats, err := s.svc.GetAgencyPointShareStats(ctx, req.GetAppCode(), ledger.AgencyPointShareStatsQuery{ + AgencyOwnerUserID: req.GetAgencyOwnerUserId(), + StartAtMS: req.GetStartAtMs(), + EndAtMS: req.GetEndAtMs(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.GetAgencyPointShareStatsResponse{Stats: &walletv1.AgencyPointShareStats{ + ShareIncome: stats.ShareIncome, + GiftedHostCount: stats.GiftedHostCount, + }}, nil +} diff --git a/services/wallet-service/internal/transport/grpc/cron_server.go b/services/wallet-service/internal/transport/grpc/cron_server.go index c9bacc4b..13682ad1 100644 --- a/services/wallet-service/internal/transport/grpc/cron_server.go +++ b/services/wallet-service/internal/transport/grpc/cron_server.go @@ -2,6 +2,7 @@ package grpc import ( "context" + "time" walletv1 "hyapp.local/api/proto/wallet/v1" "hyapp/pkg/appcode" @@ -34,6 +35,27 @@ func (s *CronServer) ProcessHostSalaryMonthEndBatch(ctx context.Context, req *wa return s.processHostSalarySettlementBatch(ctx, req, ledger.HostSalarySettlementTypeMonthEnd) } +// ProcessVipDailyCoinRebateBatch 只把 UTC task_day 和页大小交给 wallet owner;金额快照、 +// 会员日切资格、游标和 available outbox 都在 wallet 的同一事务边界内完成。 +func (s *CronServer) ProcessVipDailyCoinRebateBatch(ctx context.Context, req *walletv1.ProcessVipDailyCoinRebateBatchRequest) (*walletv1.ProcessVipDailyCoinRebateBatchResponse, error) { + if s.svc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "wallet service is not configured")) + } + appCode := appcode.Normalize(req.GetAppCode()) + ctx = appcode.WithContext(ctx, appCode) + result, err := s.svc.ProcessVipDailyCoinRebateBatch(ctx, ledger.ProcessVipDailyCoinRebateBatchCommand{ + AppCode: appCode, TaskDay: req.GetTaskDay(), BatchSize: int(req.GetBatchSize()), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.ProcessVipDailyCoinRebateBatchResponse{ + Run: vipDailyCoinRebateRunToProto(result.Run), ScannedCount: int32(result.ScannedCount), + CreatedCount: int32(result.CreatedCount), HasMore: result.HasMore, + ServerTimeMs: time.Now().UTC().UnixMilli(), + }, nil +} + func (s *CronServer) processHostSalarySettlementBatch(ctx context.Context, req *walletv1.CronBatchRequest, settlementType string) (*walletv1.CronBatchResponse, error) { if s.svc == nil { return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "wallet service is not configured")) diff --git a/services/wallet-service/internal/transport/grpc/gift.go b/services/wallet-service/internal/transport/grpc/gift.go index d53c69e7..2d53f7dc 100644 --- a/services/wallet-service/internal/transport/grpc/gift.go +++ b/services/wallet-service/internal/transport/grpc/gift.go @@ -133,6 +133,7 @@ func debitGiftResponseFromReceipt(receipt ledger.Receipt) *walletv1.DebitGiftRes return &walletv1.DebitGiftResponse{ BillingReceiptId: receipt.BillingReceiptID, BalanceAfter: receipt.BalanceAfter, + BalanceVersion: receipt.BalanceVersion, TransactionId: receipt.TransactionID, CoinSpent: receipt.CoinSpent, ChargeAssetType: receipt.ChargeAssetType, diff --git a/services/wallet-service/internal/transport/grpc/gift_balance_version_test.go b/services/wallet-service/internal/transport/grpc/gift_balance_version_test.go new file mode 100644 index 00000000..3135bdda --- /dev/null +++ b/services/wallet-service/internal/transport/grpc/gift_balance_version_test.go @@ -0,0 +1,14 @@ +package grpc + +import ( + "testing" + + "hyapp/services/wallet-service/internal/domain/ledger" +) + +func TestDebitGiftResponseFromReceiptIncludesBalanceVersion(t *testing.T) { + response := debitGiftResponseFromReceipt(ledger.Receipt{BalanceAfter: 1234, BalanceVersion: 17}) + if response.GetBalanceAfter() != 1234 || response.GetBalanceVersion() != 17 { + t.Fatalf("wallet protobuf response lost sender balance snapshot: %+v", response) + } +} diff --git a/services/wallet-service/internal/transport/grpc/host_revenue_stats.go b/services/wallet-service/internal/transport/grpc/host_revenue_stats.go new file mode 100644 index 00000000..dcdd34e5 --- /dev/null +++ b/services/wallet-service/internal/transport/grpc/host_revenue_stats.go @@ -0,0 +1,28 @@ +package grpc + +import ( + "context" + + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +// GetHostRevenueStats 返回钱包账本内的 POINT 收益、兑换和去重送礼用户。 +func (s *Server) GetHostRevenueStats(ctx context.Context, req *walletv1.GetHostRevenueStatsRequest) (*walletv1.GetHostRevenueStatsResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + stats, err := s.svc.GetHostRevenueStats(ctx, req.GetAppCode(), ledger.HostRevenueStatsQuery{ + HostUserID: req.GetHostUserId(), + StartAtMS: req.GetStartAtMs(), + EndAtMS: req.GetEndAtMs(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.GetHostRevenueStatsResponse{Stats: &walletv1.HostRevenueStats{ + DiamondEarnings: stats.DiamondEarnings, + DiamondExchanged: stats.DiamondExchanged, + GiftSenders: stats.GiftSenders, + }}, nil +} diff --git a/services/wallet-service/internal/transport/grpc/point_coin_seller.go b/services/wallet-service/internal/transport/grpc/point_coin_seller.go new file mode 100644 index 00000000..8df61b3e --- /dev/null +++ b/services/wallet-service/internal/transport/grpc/point_coin_seller.go @@ -0,0 +1,57 @@ +package grpc + +import ( + "context" + + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +// ListPointWithdrawalCoinSellers 返回 wallet 侧已执行 app/status/国家过滤的可信白名单。 +func (s *Server) ListPointWithdrawalCoinSellers(ctx context.Context, req *walletv1.ListPointWithdrawalCoinSellersRequest) (*walletv1.ListPointWithdrawalCoinSellersResponse, error) { + items, err := s.svc.ListPointWithdrawalCoinSellers(ctx, req.GetAppCode(), req.GetCountryCode(), req.GetIncludeDisabled()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + response := &walletv1.ListPointWithdrawalCoinSellersResponse{Sellers: make([]*walletv1.PointWithdrawalCoinSellerConfig, 0, len(items))} + for _, item := range items { + response.Sellers = append(response.Sellers, &walletv1.PointWithdrawalCoinSellerConfig{ + AppCode: item.AppCode, SellerUserId: item.SellerUserID, SortOrder: int32(item.SortOrder), + PointAmount: item.PointAmount, SellerCoinAmount: item.SellerCoinAmount, + ServiceCountryCodes: append([]string(nil), item.ServiceCountryCodes...), Status: item.Status, + CreatedAtMs: item.CreatedAtMS, UpdatedAtMs: item.UpdatedAtMS, + }) + } + return response, nil +} + +// TransferPointToCoinSeller 不接受兑换比例字段,确保所有调用方都只能使用数据库白名单快照。 +func (s *Server) TransferPointToCoinSeller(ctx context.Context, req *walletv1.TransferPointToCoinSellerRequest) (*walletv1.TransferPointToCoinSellerResponse, error) { + receipt, err := s.svc.TransferPointToCoinSeller(ctx, ledger.PointToCoinSellerCommand{ + AppCode: req.GetAppCode(), CommandID: req.GetCommandId(), SourceUserID: req.GetSourceUserId(), + SellerUserID: req.GetSellerUserId(), PointAmount: req.GetPointAmount(), + SourceCountryCode: req.GetSourceCountryCode(), Reason: req.GetReason(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.TransferPointToCoinSellerResponse{ + TransactionId: receipt.TransactionID, SourcePointBalanceAfter: receipt.SourcePointBalanceAfter, + SellerBalanceAfter: receipt.SellerBalanceAfter, PointAmount: receipt.PointAmount, + SellerCoinAmount: receipt.SellerCoinAmount, RatioPointAmount: receipt.RatioPointAmount, + RatioSellerCoinAmount: receipt.RatioSellerCoinAmount, + }, nil +} + +// GetPointWithdrawalConfig 暴露政策编译后的最小运行参数,H5 不解析 Admin rule_json。 +func (s *Server) GetPointWithdrawalConfig(ctx context.Context, req *walletv1.GetPointWithdrawalConfigRequest) (*walletv1.GetPointWithdrawalConfigResponse, error) { + config, err := s.svc.GetPointWithdrawalConfig(ctx, req.GetAppCode(), req.GetRegionId(), req.GetNowMs()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.GetPointWithdrawalConfigResponse{ + Found: config.Found, PointsPerUsd: config.PointsPerUSD, FeeBps: int32(config.FeeBPS), + MinimumPoints: config.MinimumPoints, PolicyInstanceCode: config.PolicyInstanceCode, + }, nil +} diff --git a/services/wallet-service/internal/transport/grpc/recharge.go b/services/wallet-service/internal/transport/grpc/recharge.go index 803a8fca..110b4f9d 100644 --- a/services/wallet-service/internal/transport/grpc/recharge.go +++ b/services/wallet-service/internal/transport/grpc/recharge.go @@ -62,6 +62,27 @@ func (s *Server) GetRechargeBillSummary(ctx context.Context, req *walletv1.GetRe }, nil } +// BatchGetUserRechargeStats 只读取用户累计聚合表,避免后台详情按用户回扫充值账单。 +func (s *Server) BatchGetUserRechargeStats(ctx context.Context, req *walletv1.BatchGetUserRechargeStatsRequest) (*walletv1.BatchGetUserRechargeStatsResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + items, err := s.svc.BatchGetUserRechargeStats(ctx, req.GetAppCode(), req.GetUserIds()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &walletv1.BatchGetUserRechargeStatsResponse{Items: make([]*walletv1.UserRechargeStats, 0, len(items))} + for _, item := range items { + resp.Items = append(resp.Items, &walletv1.UserRechargeStats{ + UserId: item.UserID, + RechargeCount: item.RechargeCount, + TotalCoinAmount: item.TotalCoinAmount, + TotalUsdMinorAmount: item.TotalUSDMinorAmount, + FirstRechargedAtMs: item.FirstRechargedAtMS, + UpdatedAtMs: item.UpdatedAtMS, + }) + } + return resp, nil +} + // GetRechargeBillOverview 暴露财务概览聚合:按日趋势、区域分布与谷歌实付覆盖度。 func (s *Server) GetRechargeBillOverview(ctx context.Context, req *walletv1.GetRechargeBillOverviewRequest) (*walletv1.GetRechargeBillOverviewResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) diff --git a/services/wallet-service/internal/transport/grpc/resource.go b/services/wallet-service/internal/transport/grpc/resource.go index 3632a885..361c3e1c 100644 --- a/services/wallet-service/internal/transport/grpc/resource.go +++ b/services/wallet-service/internal/transport/grpc/resource.go @@ -340,6 +340,7 @@ func (s *Server) ListUserResources(ctx context.Context, req *walletv1.ListUserRe func (s *Server) EquipUserResource(ctx context.Context, req *walletv1.EquipUserResourceRequest) (*walletv1.EquipUserResourceResponse, error) { item, err := s.svc.EquipUserResource(ctx, resourcedomain.EquipUserResourceCommand{ + RequestID: req.GetRequestId(), AppCode: req.GetAppCode(), UserID: req.GetUserId(), ResourceID: req.GetResourceId(), @@ -353,6 +354,7 @@ func (s *Server) EquipUserResource(ctx context.Context, req *walletv1.EquipUserR func (s *Server) UnequipUserResource(ctx context.Context, req *walletv1.UnequipUserResourceRequest) (*walletv1.UnequipUserResourceResponse, error) { result, err := s.svc.UnequipUserResource(ctx, resourcedomain.UnequipUserResourceCommand{ + RequestID: req.GetRequestId(), AppCode: req.GetAppCode(), UserID: req.GetUserId(), ResourceType: req.GetResourceType(), diff --git a/services/wallet-service/internal/transport/grpc/vip.go b/services/wallet-service/internal/transport/grpc/vip.go index 8b2d885e..5d8a2782 100644 --- a/services/wallet-service/internal/transport/grpc/vip.go +++ b/services/wallet-service/internal/transport/grpc/vip.go @@ -13,11 +13,16 @@ import ( // ListVipPackages 返回当前 VIP 和可购买包列表。 func (s *Server) ListVipPackages(ctx context.Context, req *walletv1.ListVipPackagesRequest) (*walletv1.ListVipPackagesResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) - current, levels, err := s.svc.ListVipPackages(ctx, req.GetUserId()) + state, levels, err := s.svc.ListVipPackages(ctx, req.GetUserId()) if err != nil { return nil, xerr.ToGRPCError(err) } - resp := &walletv1.ListVipPackagesResponse{CurrentVip: vipToProto(current), Packages: make([]*walletv1.VipLevel, 0, len(levels))} + resp := &walletv1.ListVipPackagesResponse{ + CurrentVip: vipToProto(state.EffectiveVip), + Packages: make([]*walletv1.VipLevel, 0, len(levels)), + State: vipStateToProto(state), + ProgramConfig: vipProgramConfigToProto(state.ProgramConfig), + } for _, level := range levels { resp.Packages = append(resp.Packages, vipLevelToProto(level)) } @@ -27,11 +32,42 @@ func (s *Server) ListVipPackages(ctx context.Context, req *walletv1.ListVipPacka // GetMyVip 返回当前登录用户 VIP 状态。 func (s *Server) GetMyVip(ctx context.Context, req *walletv1.GetMyVipRequest) (*walletv1.GetMyVipResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) - vip, err := s.svc.GetMyVip(ctx, req.GetUserId()) + state, err := s.svc.GetVipState(ctx, req.GetUserId()) if err != nil { return nil, xerr.ToGRPCError(err) } - return &walletv1.GetMyVipResponse{Vip: vipToProto(vip)}, nil + // 旧 vip 字段保留为最终生效投影,付费会员原始事实由 state.paid_vip 明确返回。 + return &walletv1.GetMyVipResponse{Vip: vipToProto(state.EffectiveVip), State: vipStateToProto(state)}, nil +} + +// GetVipProgramConfig 返回按 App 隔离的状态机配置和最高等级权益目录。 +func (s *Server) GetVipProgramConfig(ctx context.Context, req *walletv1.GetVipProgramConfigRequest) (*walletv1.GetVipProgramConfigResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + config, benefits, err := s.svc.GetVipProgramConfig(ctx) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.GetVipProgramConfigResponse{ + Config: vipProgramConfigToProto(config), + Benefits: vipBenefitsToProto(benefits), + ServerTimeMs: time.Now().UnixMilli(), + }, nil +} + +// CheckVipBenefit 供权益 owner 精确判断单项能力,不允许调用方退化成等级比较。 +func (s *Server) CheckVipBenefit(ctx context.Context, req *walletv1.CheckVipBenefitRequest) (*walletv1.CheckVipBenefitResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + result, err := s.svc.CheckVipBenefit(ctx, req.GetUserId(), req.GetBenefitCode()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.CheckVipBenefitResponse{ + Allowed: result.Allowed, + Benefit: vipBenefitToProto(result.Benefit), + State: vipStateToProto(result.State), + DenialReason: result.DenialReason, + EvaluatedAtMs: result.EvaluatedAtMS, + }, nil } // PurchaseVip 购买、续期或升级 VIP。 @@ -53,6 +89,7 @@ func (s *Server) PurchaseVip(ctx context.Context, req *walletv1.PurchaseVipReque CoinSpent: receipt.CoinSpent, CoinBalanceAfter: receipt.CoinBalanceAfter, RewardItems: vipRewardItemsToProto(receipt.RewardItems), + State: vipStateToProto(receipt.State), }, nil } @@ -76,17 +113,83 @@ func (s *Server) GrantVip(ctx context.Context, req *walletv1.GrantVipRequest) (* Vip: vipToProto(receipt.Vip), RewardItems: vipRewardItemsToProto(receipt.RewardItems), ServerTimeMs: time.Now().UnixMilli(), + State: vipStateToProto(receipt.State), + }, nil +} + +// GrantVipTrialCard 发卡但不自动佩戴,resource_id 省略时由钱包按 level 解析同 App 预置资源。 +func (s *Server) GrantVipTrialCard(ctx context.Context, req *walletv1.GrantVipTrialCardRequest) (*walletv1.GrantVipTrialCardResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + resourceID := int64(0) + if req.ResourceId != nil { + resourceID = req.GetResourceId() + } + receipt, err := s.svc.GrantVipTrialCard(ctx, ledger.GrantVipTrialCardCommand{ + AppCode: req.GetAppCode(), + CommandID: req.GetCommandId(), + TargetUserID: req.GetTargetUserId(), + Level: req.GetLevel(), + DurationMS: req.GetDurationMs(), + ResourceID: resourceID, + GrantSource: req.GetGrantSource(), + OperatorUserID: req.GetOperatorUserId(), + Reason: req.GetReason(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.GrantVipTrialCardResponse{ + TrialCard: vipTrialCardToProto(receipt.TrialCard), + State: vipStateToProto(receipt.State), + ServerTimeMs: receipt.ServerTimeMS, + }, nil +} + +// EquipVipTrialCard 精确佩戴一个 entitlement;同类型旧装备仅解除指针,库存与到期时间保持不变。 +func (s *Server) EquipVipTrialCard(ctx context.Context, req *walletv1.EquipVipTrialCardRequest) (*walletv1.EquipVipTrialCardResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + card, state, err := s.svc.EquipVipTrialCard(ctx, ledger.EquipVipTrialCardCommand{ + AppCode: req.GetAppCode(), + RequestID: req.GetRequestId(), + UserID: req.GetUserId(), + EntitlementID: req.GetEntitlementId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.EquipVipTrialCardResponse{ + TrialCard: vipTrialCardToProto(card), + State: vipStateToProto(state), + ServerTimeMs: time.Now().UnixMilli(), + }, nil +} + +// UnequipVipTrialCard 卸下当前体验卡并返回回落后的最终 VIP 状态。 +func (s *Server) UnequipVipTrialCard(ctx context.Context, req *walletv1.UnequipVipTrialCardRequest) (*walletv1.UnequipVipTrialCardResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + unequipped, state, err := s.svc.UnequipVipTrialCard(ctx, ledger.UnequipVipTrialCardCommand{ + AppCode: req.GetAppCode(), + RequestID: req.GetRequestId(), + UserID: req.GetUserId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.UnequipVipTrialCardResponse{ + Unequipped: unequipped, + State: vipStateToProto(state), + ServerTimeMs: time.Now().UnixMilli(), }, nil } // ListAdminVipLevels 返回后台 VIP 配置。 func (s *Server) ListAdminVipLevels(ctx context.Context, req *walletv1.ListAdminVipLevelsRequest) (*walletv1.ListAdminVipLevelsResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) - levels, err := s.svc.ListAdminVipLevels(ctx) + config, levels, err := s.svc.ListAdminVipLevels(ctx) if err != nil { return nil, xerr.ToGRPCError(err) } - resp := &walletv1.ListAdminVipLevelsResponse{Levels: make([]*walletv1.VipLevel, 0, len(levels)), ServerTimeMs: time.Now().UnixMilli()} + resp := &walletv1.ListAdminVipLevelsResponse{Levels: make([]*walletv1.VipLevel, 0, len(levels)), ServerTimeMs: time.Now().UnixMilli(), ProgramConfig: vipProgramConfigToProto(config)} for _, level := range levels { resp.Levels = append(resp.Levels, vipLevelToProto(level)) } @@ -110,28 +213,151 @@ func (s *Server) UpdateAdminVipLevels(ctx context.Context, req *walletv1.UpdateA RewardResourceGroupID: item.GetRewardResourceGroupId(), RequiredRechargeCoinAmount: item.GetRequiredRechargeCoinAmount(), SortOrder: item.GetSortOrder(), + Benefits: vipBenefitsFromProto(item.GetBenefits()), }) } - levels, err := s.svc.UpdateAdminVipLevels(ctx, commands, req.GetOperatorUserId()) + config, levels, err := s.svc.UpdateAdminVipLevels(ctx, commands, req.GetOperatorUserId()) if err != nil { return nil, xerr.ToGRPCError(err) } - resp := &walletv1.UpdateAdminVipLevelsResponse{Levels: make([]*walletv1.VipLevel, 0, len(levels)), ServerTimeMs: time.Now().UnixMilli()} + resp := &walletv1.UpdateAdminVipLevelsResponse{Levels: make([]*walletv1.VipLevel, 0, len(levels)), ServerTimeMs: time.Now().UnixMilli(), ProgramConfig: vipProgramConfigToProto(config)} for _, level := range levels { resp.Levels = append(resp.Levels, vipLevelToProto(level)) } return resp, nil } +// UpdateAdminVipProgramConfig 保存 App 级策略,审计操作者只接受请求外层的服务端身份字段。 +func (s *Server) UpdateAdminVipProgramConfig(ctx context.Context, req *walletv1.UpdateAdminVipProgramConfigRequest) (*walletv1.UpdateAdminVipProgramConfigResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + config := vipProgramConfigFromProto(req.GetConfig()) + config.AppCode = req.GetAppCode() + updated, err := s.svc.UpdateAdminVipProgramConfig(ctx, ledger.AdminVipProgramConfigCommand{ + Config: config, + OperatorUserID: req.GetOperatorUserId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.UpdateAdminVipProgramConfigResponse{Config: vipProgramConfigToProto(updated), ServerTimeMs: time.Now().UnixMilli()}, nil +} + +// UpdateMyVipSettings 保留 proto optional 的 partial update 语义;未提交字段不能被零值 false 覆盖。 +func (s *Server) UpdateMyVipSettings(ctx context.Context, req *walletv1.UpdateMyVipSettingsRequest) (*walletv1.UpdateMyVipSettingsResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + var roomEntryNoticeEnabled *bool + if req.RoomEntryNoticeEnabled != nil { + value := req.GetRoomEntryNoticeEnabled() + roomEntryNoticeEnabled = &value + } + var onlineGlobalNoticeEnabled *bool + if req.OnlineGlobalNoticeEnabled != nil { + value := req.GetOnlineGlobalNoticeEnabled() + onlineGlobalNoticeEnabled = &value + } + settings, err := s.svc.UpdateVipUserSettings(ctx, ledger.UpdateVipUserSettingsCommand{ + AppCode: req.GetAppCode(), UserID: req.GetUserId(), + RoomEntryNoticeEnabled: roomEntryNoticeEnabled, OnlineGlobalNoticeEnabled: onlineGlobalNoticeEnabled, + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.UpdateMyVipSettingsResponse{ + Settings: vipUserSettingsToProto(settings), ServerTimeMs: time.Now().UTC().UnixMilli(), + }, nil +} + +// GetMyCurrentVipDailyCoinRebate 返回当前 UTC 日可领取/已领取事实;未生成以 found=false 表达。 +func (s *Server) GetMyCurrentVipDailyCoinRebate(ctx context.Context, req *walletv1.GetMyCurrentVipDailyCoinRebateRequest) (*walletv1.GetMyCurrentVipDailyCoinRebateResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + rebate, found, err := s.svc.GetMyCurrentVipDailyCoinRebate(ctx, req.GetUserId()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + response := &walletv1.GetMyCurrentVipDailyCoinRebateResponse{Found: found, ServerTimeMs: time.Now().UTC().UnixMilli()} + if found { + response.Rebate = vipDailyCoinRebateToProto(rebate) + } + return response, nil +} + +// ListMyVipDailyCoinRebateStatuses 同时支持历史分页和通知按 rebate_ids 精确恢复;repository 始终附加 user_id 条件。 +func (s *Server) ListMyVipDailyCoinRebateStatuses(ctx context.Context, req *walletv1.ListMyVipDailyCoinRebateStatusesRequest) (*walletv1.ListMyVipDailyCoinRebateStatusesResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + items, total, err := s.svc.ListMyVipDailyCoinRebateStatuses(ctx, ledger.ListVipDailyCoinRebateStatusesQuery{ + AppCode: req.GetAppCode(), UserID: req.GetUserId(), Page: int(req.GetPage()), + PageSize: int(req.GetPageSize()), RebateIDs: append([]string(nil), req.GetRebateIds()...), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + response := &walletv1.ListMyVipDailyCoinRebateStatusesResponse{ + Rebates: make([]*walletv1.VipDailyCoinRebate, 0, len(items)), Total: total, + ServerTimeMs: time.Now().UTC().UnixMilli(), + } + for _, item := range items { + response.Rebates = append(response.Rebates, vipDailyCoinRebateToProto(item)) + } + return response, nil +} + +// ClaimVipDailyCoinRebate 不接收金额;wallet owner 从锁内返现行读取并返回稳定账后余额。 +func (s *Server) ClaimVipDailyCoinRebate(ctx context.Context, req *walletv1.ClaimVipDailyCoinRebateRequest) (*walletv1.ClaimVipDailyCoinRebateResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + receipt, err := s.svc.ClaimVipDailyCoinRebate(ctx, ledger.ClaimVipDailyCoinRebateCommand{ + AppCode: req.GetAppCode(), CommandID: req.GetCommandId(), UserID: req.GetUserId(), RebateID: req.GetRebateId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.ClaimVipDailyCoinRebateResponse{ + Rebate: vipDailyCoinRebateToProto(receipt.Rebate), TransactionId: receipt.TransactionID, + CoinBalance: balanceToProto(receipt.CoinBalance), ServerTimeMs: time.Now().UTC().UnixMilli(), + }, nil +} + func vipToProto(vip ledger.UserVip) *walletv1.UserVip { return &walletv1.UserVip{ - UserId: vip.UserID, - Level: vip.Level, - Name: vip.Name, - Active: vip.Active, - StartedAtMs: vip.StartedAtMS, - ExpiresAtMs: vip.ExpiresAtMS, - UpdatedAtMs: vip.UpdatedAtMS, + UserId: vip.UserID, + Level: vip.Level, + Name: vip.Name, + Active: vip.Active, + StartedAtMs: vip.StartedAtMS, + ExpiresAtMs: vip.ExpiresAtMS, + UpdatedAtMs: vip.UpdatedAtMS, + ProgramType: vip.ProgramType, + ConfigVersion: vip.ConfigVersion, + } +} + +func vipDailyCoinRebateRunToProto(run ledger.VipDailyCoinRebateRun) *walletv1.VipDailyCoinRebateRun { + return &walletv1.VipDailyCoinRebateRun{ + RunId: run.RunID, TaskDay: run.TaskDay, DayStartMs: run.DayStartMS, DayEndMs: run.DayEndMS, + ConfigVersion: run.ConfigVersion, LevelAmountsJson: run.LevelAmountsJSON, Status: run.Status, + LastUserId: run.LastUserID, ScannedCount: run.ScannedCount, CreatedCount: run.CreatedCount, + CreatedAtMs: run.CreatedAtMS, UpdatedAtMs: run.UpdatedAtMS, CompletedAtMs: run.CompletedAtMS, + } +} + +func vipDailyCoinRebateToProto(rebate ledger.VipDailyCoinRebate) *walletv1.VipDailyCoinRebate { + if rebate.RebateID == "" { + return nil + } + return &walletv1.VipDailyCoinRebate{ + RebateId: rebate.RebateID, UserId: rebate.UserID, TaskDay: rebate.TaskDay, + VipLevel: rebate.VipLevel, VipName: rebate.VipName, CoinAmount: rebate.CoinAmount, + Status: rebate.Status, AvailableAtMs: rebate.AvailableAtMS, ExpiresAtMs: rebate.ExpiresAtMS, + ConfigVersion: rebate.ConfigVersion, ClaimedAtMs: rebate.ClaimedAtMS, + WalletTransactionId: rebate.WalletTransactionID, CreatedAtMs: rebate.CreatedAtMS, UpdatedAtMs: rebate.UpdatedAtMS, + } +} + +func vipUserSettingsToProto(settings ledger.VipUserSettings) *walletv1.VipUserSettings { + return &walletv1.VipUserSettings{ + AppCode: settings.AppCode, UserId: settings.UserID, + RoomEntryNoticeEnabled: settings.RoomEntryNoticeEnabled, + OnlineGlobalNoticeEnabled: settings.OnlineGlobalNoticeEnabled, + UpdatedAtMs: settings.UpdatedAtMS, } } @@ -152,6 +378,143 @@ func vipLevelToProto(level ledger.VipLevel) *walletv1.VipLevel { PurchaseLockedReason: level.PurchaseLockedReason, CreatedAtMs: level.CreatedAtMS, UpdatedAtMs: level.UpdatedAtMS, + Benefits: vipBenefitsToProto(level.Benefits), + ConfigVersion: level.ConfigVersion, + } +} + +func vipProgramConfigToProto(config ledger.VipProgramConfig) *walletv1.VipProgramConfig { + return &walletv1.VipProgramConfig{ + AppCode: config.AppCode, + ProgramType: config.ProgramType, + LevelCount: config.LevelCount, + SameLevelExpiryPolicy: config.SameLevelExpiryPolicy, + UpgradeExpiryPolicy: config.UpgradeExpiryPolicy, + DowngradePurchasePolicy: config.DowngradePurchasePolicy, + BenefitInheritancePolicy: config.BenefitInheritancePolicy, + GrantMode: config.GrantMode, + TrialCardEnabled: config.TrialCardEnabled, + Status: config.Status, + ConfigVersion: config.ConfigVersion, + UpdatedByAdminId: config.UpdatedByAdminID, + CreatedAtMs: config.CreatedAtMS, + UpdatedAtMs: config.UpdatedAtMS, + } +} + +func vipProgramConfigFromProto(config *walletv1.VipProgramConfig) ledger.VipProgramConfig { + if config == nil { + return ledger.VipProgramConfig{} + } + return ledger.VipProgramConfig{ + AppCode: config.GetAppCode(), + ProgramType: config.GetProgramType(), + LevelCount: config.GetLevelCount(), + SameLevelExpiryPolicy: config.GetSameLevelExpiryPolicy(), + UpgradeExpiryPolicy: config.GetUpgradeExpiryPolicy(), + DowngradePurchasePolicy: config.GetDowngradePurchasePolicy(), + BenefitInheritancePolicy: config.GetBenefitInheritancePolicy(), + GrantMode: config.GetGrantMode(), + TrialCardEnabled: config.GetTrialCardEnabled(), + Status: config.GetStatus(), + } +} + +func vipBenefitToProto(benefit ledger.VipBenefit) *walletv1.VipBenefit { + if benefit.BenefitCode == "" { + return nil + } + return &walletv1.VipBenefit{ + BenefitCode: benefit.BenefitCode, + Name: benefit.Name, + BenefitType: benefit.BenefitType, + UnlockLevel: benefit.UnlockLevel, + Status: benefit.Status, + TrialEnabled: benefit.TrialEnabled, + ResourceId: benefit.ResourceID, + ResourceType: benefit.ResourceType, + ExecutionScope: benefit.ExecutionScope, + AutoEquip: benefit.AutoEquip, + SortOrder: benefit.SortOrder, + MetadataJson: benefit.MetadataJSON, + CreatedAtMs: benefit.CreatedAtMS, + UpdatedAtMs: benefit.UpdatedAtMS, + } +} + +func vipBenefitsToProto(benefits []ledger.VipBenefit) []*walletv1.VipBenefit { + items := make([]*walletv1.VipBenefit, 0, len(benefits)) + for _, benefit := range benefits { + if item := vipBenefitToProto(benefit); item != nil { + items = append(items, item) + } + } + return items +} + +func vipBenefitsFromProto(benefits []*walletv1.VipBenefit) []ledger.VipBenefit { + items := make([]ledger.VipBenefit, 0, len(benefits)) + for _, benefit := range benefits { + if benefit == nil { + continue + } + items = append(items, ledger.VipBenefit{ + BenefitCode: benefit.GetBenefitCode(), + Name: benefit.GetName(), + BenefitType: benefit.GetBenefitType(), + UnlockLevel: benefit.GetUnlockLevel(), + Status: benefit.GetStatus(), + TrialEnabled: benefit.GetTrialEnabled(), + ResourceID: benefit.GetResourceId(), + ResourceType: benefit.GetResourceType(), + ExecutionScope: benefit.GetExecutionScope(), + AutoEquip: benefit.GetAutoEquip(), + SortOrder: benefit.GetSortOrder(), + MetadataJSON: benefit.GetMetadataJson(), + }) + } + return items +} + +func vipTrialCardToProto(card ledger.VipTrialCard) *walletv1.VipTrialCard { + if card.TrialCardID == "" { + return nil + } + return &walletv1.VipTrialCard{ + TrialCardId: card.TrialCardID, + EntitlementId: card.EntitlementID, + ResourceId: card.ResourceID, + UserId: card.UserID, + Level: card.Level, + Name: card.Name, + Status: card.Status, + Equipped: card.Equipped, + DurationMs: card.DurationMS, + EffectiveAtMs: card.EffectiveAtMS, + ExpiresAtMs: card.ExpiresAtMS, + RemainingDurationMs: card.RemainingDurationMS, + GrantSource: card.GrantSource, + SourceGrantId: card.SourceGrantID, + CreatedAtMs: card.CreatedAtMS, + UpdatedAtMs: card.UpdatedAtMS, + } +} + +func vipStateToProto(state ledger.VipState) *walletv1.VipState { + return &walletv1.VipState{ + PaidVip: vipToProto(state.PaidVip), + EquippedTrialCard: func() *walletv1.VipTrialCard { + if state.EquippedTrialCard == nil { + return nil + } + return vipTrialCardToProto(*state.EquippedTrialCard) + }(), + EffectiveVip: vipToProto(state.EffectiveVip), + EffectiveSource: state.EffectiveSource, + EffectiveBenefits: vipBenefitsToProto(state.EffectiveBenefits), + EvaluatedAtMs: state.EvaluatedAtMS, + ProgramConfig: vipProgramConfigToProto(state.ProgramConfig), + UserSettings: vipUserSettingsToProto(state.UserSettings), } }