From 441237fab7d1f381504771447d81492508364c69 Mon Sep 17 00:00:00 2001 From: zhx Date: Tue, 12 May 2026 19:34:47 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E8=AF=AD=E9=9F=B3=E6=88=BF?= =?UTF-8?q?=E7=9B=B8=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 13 +- api/proto/activity/v1/activity.pb.go | 2 +- api/proto/activity/v1/activity_grpc.pb.go | 2 +- api/proto/events/room/v1/events.pb.go | 170 +- api/proto/events/room/v1/events.proto | 1 + api/proto/game/v1/game.pb.go | 1883 +++++++++++++ api/proto/game/v1/game.proto | 175 ++ api/proto/game/v1/game_grpc.pb.go | 515 ++++ api/proto/room/v1/room.pb.go | 2337 ++++++++++------- api/proto/room/v1/room.proto | 41 + api/proto/room/v1/room_grpc.pb.go | 88 +- api/proto/user/v1/auth.pb.go | 2 +- api/proto/user/v1/auth_grpc.pb.go | 2 +- api/proto/user/v1/host.pb.go | 2 +- api/proto/user/v1/host_grpc.pb.go | 2 +- api/proto/user/v1/user.pb.go | 2 +- api/proto/user/v1/user_grpc.pb.go | 2 +- api/proto/wallet/v1/wallet.pb.go | 776 ++++-- api/proto/wallet/v1/wallet.proto | 24 + api/proto/wallet/v1/wallet_grpc.pb.go | 40 +- deploy/mysql/initdb/999_local_grants.sql | 1 + docker-compose.yml | 27 + docs/接口清单.md | 27 + docs/游戏接入架构.md | 574 ++++ docs/语音房功能清单.md | 9 +- docs/语音房客户端接口流程.md | 135 +- scripts/apply-local-mysql-initdb.sh | 1 + server/admin/cmd/server/main.go | 9 + server/admin/configs/config.yaml | 3 + server/admin/internal/config/config.go | 35 + .../internal/integration/gameclient/client.go | 46 + .../internal/modules/gamemanagement/dto.go | 73 + .../modules/gamemanagement/handler.go | 171 ++ .../modules/gamemanagement/request.go | 101 + .../internal/modules/gamemanagement/routes.go | 21 + server/admin/internal/repository/seed.go | 19 +- server/admin/internal/router/router.go | 3 + .../configs/config.docker.yaml | 14 +- services/activity-service/configs/config.yaml | 14 +- services/game-service/Dockerfile | 24 + services/game-service/cmd/server/main.go | 54 + .../game-service/configs/config.docker.yaml | 14 + .../configs/config.tencent.example.yaml | 14 + services/game-service/configs/config.yaml | 14 + .../deploy/mysql/initdb/001_game_service.sql | 161 ++ services/game-service/internal/app/app.go | 97 + .../internal/client/user_client.go | 21 + .../internal/client/wallet_client.go | 24 + .../game-service/internal/config/config.go | 92 + .../game-service/internal/domain/game/game.go | 131 + .../internal/service/game/service.go | 478 ++++ .../internal/service/game/service_test.go | 205 ++ .../internal/storage/mysql/repository.go | 546 ++++ .../internal/transport/grpc/server.go | 333 +++ .../configs/config.docker.yaml | 1 + services/gateway-service/configs/config.yaml | 1 + services/gateway-service/internal/app/app.go | 20 + .../internal/client/game_client.go | 39 + .../internal/client/room_client.go | 10 + .../gateway-service/internal/config/config.go | 6 + .../internal/transport/http/game_handler.go | 154 ++ .../transport/http/game_handler_test.go | 131 + .../internal/transport/http/game_view.go | 70 + .../internal/transport/http/handler.go | 6 + .../transport/http/httproutes/router.go | 27 + .../internal/transport/http/response_test.go | 20 + .../internal/transport/http/room_handler.go | 340 ++- .../internal/transport/http/room_view.go | 186 ++ .../internal/transport/http/router.go | 11 + .../room-service/configs/config.docker.yaml | 9 +- .../configs/config.tencent.example.yaml | 13 +- services/room-service/configs/config.yaml | 11 +- .../deploy/mysql/initdb/001_room_service.sql | 4 +- services/room-service/internal/app/app.go | 10 +- .../room-service/internal/config/config.go | 31 +- .../internal/config/config_test.go | 9 +- .../internal/integration/clients.go | 4 +- .../internal/integration/outbox_composite.go | 45 + .../internal/integration/tencent_im.go | 11 +- .../internal/room/command/command.go | 18 + .../internal/room/outbox/outbox.go | 2 + .../internal/room/service/create_room.go | 60 +- .../internal/room/service/gift.go | 52 +- .../room-service/internal/room/service/mic.go | 89 + .../room/service/online_users_query.go | 76 + .../internal/room/service/outbox_worker.go | 87 +- .../internal/room/service/pipeline.go | 67 +- .../internal/room/service/presence.go | 4 +- .../internal/room/service/recovery.go | 12 + .../internal/room/service/repository.go | 24 +- .../internal/room/service/service.go | 13 +- .../internal/room/service/service_test.go | 66 +- .../room-service/internal/room/state/state.go | 101 +- .../internal/storage/mysql/repository.go | 169 +- .../internal/testutil/mysqltest/mysqltest.go | 4 +- .../internal/transport/grpc/server.go | 14 + .../internal/domain/ledger/ledger.go | 46 + .../internal/service/wallet/service.go | 31 + .../internal/service/wallet/service_test.go | 105 + .../internal/storage/mysql/repository.go | 148 ++ .../internal/transport/grpc/server.go | 26 + tools/trtc-smoke/README.md | 30 + tools/trtc-smoke/main.go | 366 +++ 103 files changed, 10797 insertions(+), 1552 deletions(-) create mode 100644 api/proto/game/v1/game.pb.go create mode 100644 api/proto/game/v1/game.proto create mode 100644 api/proto/game/v1/game_grpc.pb.go create mode 100644 docs/游戏接入架构.md create mode 100644 server/admin/internal/integration/gameclient/client.go create mode 100644 server/admin/internal/modules/gamemanagement/dto.go create mode 100644 server/admin/internal/modules/gamemanagement/handler.go create mode 100644 server/admin/internal/modules/gamemanagement/request.go create mode 100644 server/admin/internal/modules/gamemanagement/routes.go create mode 100644 services/game-service/Dockerfile create mode 100644 services/game-service/cmd/server/main.go create mode 100644 services/game-service/configs/config.docker.yaml create mode 100644 services/game-service/configs/config.tencent.example.yaml create mode 100644 services/game-service/configs/config.yaml create mode 100644 services/game-service/deploy/mysql/initdb/001_game_service.sql create mode 100644 services/game-service/internal/app/app.go create mode 100644 services/game-service/internal/client/user_client.go create mode 100644 services/game-service/internal/client/wallet_client.go create mode 100644 services/game-service/internal/config/config.go create mode 100644 services/game-service/internal/domain/game/game.go create mode 100644 services/game-service/internal/service/game/service.go create mode 100644 services/game-service/internal/service/game/service_test.go create mode 100644 services/game-service/internal/storage/mysql/repository.go create mode 100644 services/game-service/internal/transport/grpc/server.go create mode 100644 services/gateway-service/internal/client/game_client.go create mode 100644 services/gateway-service/internal/transport/http/game_handler.go create mode 100644 services/gateway-service/internal/transport/http/game_handler_test.go create mode 100644 services/gateway-service/internal/transport/http/game_view.go create mode 100644 services/room-service/internal/integration/outbox_composite.go create mode 100644 services/room-service/internal/room/service/online_users_query.go create mode 100644 tools/trtc-smoke/README.md create mode 100644 tools/trtc-smoke/main.go diff --git a/Makefile b/Makefile index 1ba95eec..2ecb8f4a 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -COMPOSE_SERVICES := mysql redis gateway-service room-service wallet-service user-service activity-service cron-service -SERVICE_ALIASES := gs gateway rs room ws wallet us user as activity cs cron mysql redis gateway-service room-service wallet-service user-service activity-service cron-service +COMPOSE_SERVICES := mysql redis gateway-service room-service wallet-service user-service activity-service cron-service game-service +SERVICE_ALIASES := gs gateway rs room ws wallet us user as activity cs cron game games mysql redis gateway-service room-service wallet-service user-service activity-service cron-service game-service SERVICE_TOKEN := $(firstword $(filter $(SERVICE_ALIASES),$(MAKECMDGOALS))) -SERVICE_ID := $(strip $(or $(SERVICE),$(if $(filter gs gateway,$(SERVICE_TOKEN)),gateway-service,$(if $(filter rs room,$(SERVICE_TOKEN)),room-service,$(if $(filter ws wallet,$(SERVICE_TOKEN)),wallet-service,$(if $(filter us user,$(SERVICE_TOKEN)),user-service,$(if $(filter as activity,$(SERVICE_TOKEN)),activity-service,$(if $(filter cs cron,$(SERVICE_TOKEN)),cron-service,$(SERVICE_TOKEN))))))))) +SERVICE_ID := $(strip $(or $(SERVICE),$(if $(filter gs gateway,$(SERVICE_TOKEN)),gateway-service,$(if $(filter rs room,$(SERVICE_TOKEN)),room-service,$(if $(filter ws wallet,$(SERVICE_TOKEN)),wallet-service,$(if $(filter us user,$(SERVICE_TOKEN)),user-service,$(if $(filter as activity,$(SERVICE_TOKEN)),activity-service,$(if $(filter cs cron,$(SERVICE_TOKEN)),cron-service,$(if $(filter game games,$(SERVICE_TOKEN)),game-service,$(SERVICE_TOKEN)))))))))) ADMIN_CONFIG ?= configs/config.yaml .PHONY: proto vet test build admin admin-up admin-deps admin-bootstrap admin-test admin-build up db-init rebuild restart stop logs ps addr down up-service check-service resolve-compose-conflicts $(SERVICE_ALIASES) @@ -19,6 +19,7 @@ proto: proto/user/v1/host.proto \ proto/activity/v1/activity.proto \ proto/room/v1/room.proto \ + proto/game/v1/game.proto \ proto/wallet/v1/wallet.proto \ proto/events/room/v1/events.proto @@ -77,7 +78,7 @@ up: @if [ -z "$(SERVICE_ID)" ]; then \ docker compose up -d mysql redis; \ ./scripts/apply-local-mysql-initdb.sh; \ - docker compose up --build -d gateway-service room-service wallet-service user-service activity-service cron-service; \ + docker compose up --build -d gateway-service room-service wallet-service user-service activity-service cron-service game-service; \ elif [ "$(SERVICE_ID)" = "mysql" ] || [ "$(SERVICE_ID)" = "redis" ]; then \ docker compose up -d $(SERVICE_ID); \ else \ @@ -139,13 +140,13 @@ down: check-service: @if [ -z "$(SERVICE_ID)" ]; then \ echo "Service is required. Example: make restart rs"; \ - echo "Aliases: gs/gateway rs/room ws/wallet us/user as/activity cs/cron mysql redis"; \ + echo "Aliases: gs/gateway rs/room ws/wallet us/user as/activity cs/cron game/games mysql redis"; \ echo "Services: $(COMPOSE_SERVICES)"; \ exit 1; \ fi @if ! printf '%s\n' $(COMPOSE_SERVICES) | grep -qx "$(SERVICE_ID)"; then \ echo "Unknown service: $(SERVICE_ID)"; \ - echo "Aliases: gs/gateway rs/room ws/wallet us/user as/activity cs/cron mysql redis"; \ + echo "Aliases: gs/gateway rs/room ws/wallet us/user as/activity cs/cron game/games mysql redis"; \ echo "Services: $(COMPOSE_SERVICES)"; \ exit 1; \ fi diff --git a/api/proto/activity/v1/activity.pb.go b/api/proto/activity/v1/activity.pb.go index 13294f18..220147ae 100644 --- a/api/proto/activity/v1/activity.pb.go +++ b/api/proto/activity/v1/activity.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.35.1 -// protoc v5.27.3 +// protoc v5.29.3 // source: proto/activity/v1/activity.proto package activityv1 diff --git a/api/proto/activity/v1/activity_grpc.pb.go b/api/proto/activity/v1/activity_grpc.pb.go index 471cfefb..dd9868a8 100644 --- a/api/proto/activity/v1/activity_grpc.pb.go +++ b/api/proto/activity/v1/activity_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.27.3 +// - protoc v5.29.3 // source: proto/activity/v1/activity.proto package activityv1 diff --git a/api/proto/events/room/v1/events.pb.go b/api/proto/events/room/v1/events.pb.go index 699a90ad..ad3fb7ee 100644 --- a/api/proto/events/room/v1/events.pb.go +++ b/api/proto/events/room/v1/events.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.35.1 -// protoc v5.27.3 +// protoc v5.29.3 // source: proto/events/room/v1/events.proto package roomeventsv1 @@ -461,6 +461,7 @@ type RoomMicChanged struct { Reason string `protobuf:"bytes,8,opt,name=reason,proto3" json:"reason,omitempty"` PublishDeadlineMs int64 `protobuf:"varint,9,opt,name=publish_deadline_ms,json=publishDeadlineMs,proto3" json:"publish_deadline_ms,omitempty"` PublishEventTimeMs int64 `protobuf:"varint,10,opt,name=publish_event_time_ms,json=publishEventTimeMs,proto3" json:"publish_event_time_ms,omitempty"` + MicMuted bool `protobuf:"varint,11,opt,name=mic_muted,json=micMuted,proto3" json:"mic_muted,omitempty"` } func (x *RoomMicChanged) Reset() { @@ -563,6 +564,13 @@ func (x *RoomMicChanged) GetPublishEventTimeMs() int64 { return 0 } +func (x *RoomMicChanged) GetMicMuted() bool { + if x != nil { + return x.MicMuted + } + return false +} + // RoomMicSeatLocked 表达单个麦位锁定状态变更。 type RoomMicSeatLocked struct { state protoimpl.MessageState @@ -1249,7 +1257,7 @@ var file_proto_events_room_v1_events_proto_rawDesc = []byte{ 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, 0xee, 0x02, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x8b, 0x03, 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, @@ -1272,88 +1280,90 @@ var file_proto_events_room_v1_events_proto_rawDesc = []byte{ 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, 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, 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, + 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, 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, 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, 0x8b, 0x01, 0x0a, 0x13, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x6f, + 0x73, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x72, 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, 0x27, 0x0a, 0x10, 0x6f, 0x6c, 0x64, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6f, 0x6c, 0x64, + 0x48, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x10, 0x6e, 0x65, + 0x77, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6e, 0x65, 0x77, 0x48, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x49, 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, 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, 0x8b, 0x01, 0x0a, 0x13, 0x52, 0x6f, 0x6f, - 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x72, 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, 0x27, 0x0a, 0x10, 0x6f, 0x6c, 0x64, 0x5f, 0x68, 0x6f, 0x73, 0x74, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, - 0x6f, 0x6c, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x27, 0x0a, - 0x10, 0x6e, 0x65, 0x77, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6e, 0x65, 0x77, 0x48, 0x6f, 0x73, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x49, 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, 0xaa, 0x02, 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, + 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, 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, 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, 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, + 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xaa, + 0x02, 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, 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, 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 ( diff --git a/api/proto/events/room/v1/events.proto b/api/proto/events/room/v1/events.proto index 4d6175f0..3d6c3c73 100644 --- a/api/proto/events/room/v1/events.proto +++ b/api/proto/events/room/v1/events.proto @@ -69,6 +69,7 @@ message RoomMicChanged { string reason = 8; int64 publish_deadline_ms = 9; int64 publish_event_time_ms = 10; + bool mic_muted = 11; } // RoomMicSeatLocked 表达单个麦位锁定状态变更。 diff --git a/api/proto/game/v1/game.pb.go b/api/proto/game/v1/game.pb.go new file mode 100644 index 00000000..c2fba1d9 --- /dev/null +++ b/api/proto/game/v1/game.pb.go @@ -0,0 +1,1883 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.1 +// protoc v5.29.3 +// source: proto/game/v1/game.proto + +package gamev1 + +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) +) + +// RequestMeta 是 game-service 内部 RPC 的最小追踪和租户上下文。 +type RequestMeta 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"` + Caller string `protobuf:"bytes,2,opt,name=caller,proto3" json:"caller,omitempty"` + GatewayNodeId string `protobuf:"bytes,3,opt,name=gateway_node_id,json=gatewayNodeId,proto3" json:"gateway_node_id,omitempty"` + ActorUserId int64 `protobuf:"varint,4,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"` + SentAtMs int64 `protobuf:"varint,5,opt,name=sent_at_ms,json=sentAtMs,proto3" json:"sent_at_ms,omitempty"` + AppCode string `protobuf:"bytes,6,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` +} + +func (x *RequestMeta) Reset() { + *x = RequestMeta{} + mi := &file_proto_game_v1_game_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RequestMeta) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequestMeta) ProtoMessage() {} + +func (x *RequestMeta) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_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 RequestMeta.ProtoReflect.Descriptor instead. +func (*RequestMeta) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{0} +} + +func (x *RequestMeta) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *RequestMeta) GetCaller() string { + if x != nil { + return x.Caller + } + return "" +} + +func (x *RequestMeta) GetGatewayNodeId() string { + if x != nil { + return x.GatewayNodeId + } + return "" +} + +func (x *RequestMeta) GetActorUserId() int64 { + if x != nil { + return x.ActorUserId + } + return 0 +} + +func (x *RequestMeta) GetSentAtMs() int64 { + if x != nil { + return x.SentAtMs + } + return 0 +} + +func (x *RequestMeta) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +type GamePlatform 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"` + PlatformCode string `protobuf:"bytes,2,opt,name=platform_code,json=platformCode,proto3" json:"platform_code,omitempty"` + PlatformName string `protobuf:"bytes,3,opt,name=platform_name,json=platformName,proto3" json:"platform_name,omitempty"` + Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` + ApiBaseUrl string `protobuf:"bytes,5,opt,name=api_base_url,json=apiBaseUrl,proto3" json:"api_base_url,omitempty"` + SortOrder int32 `protobuf:"varint,6,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + CreatedAtMs int64 `protobuf:"varint,7,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,8,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` +} + +func (x *GamePlatform) Reset() { + *x = GamePlatform{} + mi := &file_proto_game_v1_game_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GamePlatform) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GamePlatform) ProtoMessage() {} + +func (x *GamePlatform) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_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 GamePlatform.ProtoReflect.Descriptor instead. +func (*GamePlatform) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{1} +} + +func (x *GamePlatform) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *GamePlatform) GetPlatformCode() string { + if x != nil { + return x.PlatformCode + } + return "" +} + +func (x *GamePlatform) GetPlatformName() string { + if x != nil { + return x.PlatformName + } + return "" +} + +func (x *GamePlatform) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *GamePlatform) GetApiBaseUrl() string { + if x != nil { + return x.ApiBaseUrl + } + return "" +} + +func (x *GamePlatform) GetSortOrder() int32 { + if x != nil { + return x.SortOrder + } + return 0 +} + +func (x *GamePlatform) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *GamePlatform) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +type GameCatalogItem 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"` + GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` + PlatformCode string `protobuf:"bytes,3,opt,name=platform_code,json=platformCode,proto3" json:"platform_code,omitempty"` + ProviderGameId string `protobuf:"bytes,4,opt,name=provider_game_id,json=providerGameId,proto3" json:"provider_game_id,omitempty"` + GameName string `protobuf:"bytes,5,opt,name=game_name,json=gameName,proto3" json:"game_name,omitempty"` + Category string `protobuf:"bytes,6,opt,name=category,proto3" json:"category,omitempty"` + IconUrl string `protobuf:"bytes,7,opt,name=icon_url,json=iconUrl,proto3" json:"icon_url,omitempty"` + CoverUrl string `protobuf:"bytes,8,opt,name=cover_url,json=coverUrl,proto3" json:"cover_url,omitempty"` + LaunchMode string `protobuf:"bytes,9,opt,name=launch_mode,json=launchMode,proto3" json:"launch_mode,omitempty"` + Orientation string `protobuf:"bytes,10,opt,name=orientation,proto3" json:"orientation,omitempty"` + MinCoin int64 `protobuf:"varint,11,opt,name=min_coin,json=minCoin,proto3" json:"min_coin,omitempty"` + Status string `protobuf:"bytes,12,opt,name=status,proto3" json:"status,omitempty"` + SortOrder int32 `protobuf:"varint,13,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + Tags []string `protobuf:"bytes,14,rep,name=tags,proto3" json:"tags,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 *GameCatalogItem) Reset() { + *x = GameCatalogItem{} + mi := &file_proto_game_v1_game_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GameCatalogItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GameCatalogItem) ProtoMessage() {} + +func (x *GameCatalogItem) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[2] + 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 GameCatalogItem.ProtoReflect.Descriptor instead. +func (*GameCatalogItem) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{2} +} + +func (x *GameCatalogItem) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *GameCatalogItem) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *GameCatalogItem) GetPlatformCode() string { + if x != nil { + return x.PlatformCode + } + return "" +} + +func (x *GameCatalogItem) GetProviderGameId() string { + if x != nil { + return x.ProviderGameId + } + return "" +} + +func (x *GameCatalogItem) GetGameName() string { + if x != nil { + return x.GameName + } + return "" +} + +func (x *GameCatalogItem) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +func (x *GameCatalogItem) GetIconUrl() string { + if x != nil { + return x.IconUrl + } + return "" +} + +func (x *GameCatalogItem) GetCoverUrl() string { + if x != nil { + return x.CoverUrl + } + return "" +} + +func (x *GameCatalogItem) GetLaunchMode() string { + if x != nil { + return x.LaunchMode + } + return "" +} + +func (x *GameCatalogItem) GetOrientation() string { + if x != nil { + return x.Orientation + } + return "" +} + +func (x *GameCatalogItem) GetMinCoin() int64 { + if x != nil { + return x.MinCoin + } + return 0 +} + +func (x *GameCatalogItem) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *GameCatalogItem) GetSortOrder() int32 { + if x != nil { + return x.SortOrder + } + return 0 +} + +func (x *GameCatalogItem) GetTags() []string { + if x != nil { + return x.Tags + } + return nil +} + +func (x *GameCatalogItem) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *GameCatalogItem) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +type AppGame struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + GameId string `protobuf:"bytes,1,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` + PlatformCode string `protobuf:"bytes,2,opt,name=platform_code,json=platformCode,proto3" json:"platform_code,omitempty"` + NameKey string `protobuf:"bytes,3,opt,name=name_key,json=nameKey,proto3" json:"name_key,omitempty"` + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + IconUrl string `protobuf:"bytes,5,opt,name=icon_url,json=iconUrl,proto3" json:"icon_url,omitempty"` + CoverUrl string `protobuf:"bytes,6,opt,name=cover_url,json=coverUrl,proto3" json:"cover_url,omitempty"` + Category string `protobuf:"bytes,7,opt,name=category,proto3" json:"category,omitempty"` + LaunchMode string `protobuf:"bytes,8,opt,name=launch_mode,json=launchMode,proto3" json:"launch_mode,omitempty"` + Orientation string `protobuf:"bytes,9,opt,name=orientation,proto3" json:"orientation,omitempty"` + MinCoin int64 `protobuf:"varint,10,opt,name=min_coin,json=minCoin,proto3" json:"min_coin,omitempty"` + Enabled bool `protobuf:"varint,11,opt,name=enabled,proto3" json:"enabled,omitempty"` + Maintenance bool `protobuf:"varint,12,opt,name=maintenance,proto3" json:"maintenance,omitempty"` + SortOrder int32 `protobuf:"varint,13,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` +} + +func (x *AppGame) Reset() { + *x = AppGame{} + mi := &file_proto_game_v1_game_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AppGame) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AppGame) ProtoMessage() {} + +func (x *AppGame) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[3] + 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 AppGame.ProtoReflect.Descriptor instead. +func (*AppGame) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{3} +} + +func (x *AppGame) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *AppGame) GetPlatformCode() string { + if x != nil { + return x.PlatformCode + } + return "" +} + +func (x *AppGame) GetNameKey() string { + if x != nil { + return x.NameKey + } + return "" +} + +func (x *AppGame) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *AppGame) GetIconUrl() string { + if x != nil { + return x.IconUrl + } + return "" +} + +func (x *AppGame) GetCoverUrl() string { + if x != nil { + return x.CoverUrl + } + return "" +} + +func (x *AppGame) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +func (x *AppGame) GetLaunchMode() string { + if x != nil { + return x.LaunchMode + } + return "" +} + +func (x *AppGame) GetOrientation() string { + if x != nil { + return x.Orientation + } + return "" +} + +func (x *AppGame) GetMinCoin() int64 { + if x != nil { + return x.MinCoin + } + return 0 +} + +func (x *AppGame) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *AppGame) GetMaintenance() bool { + if x != nil { + return x.Maintenance + } + return false +} + +func (x *AppGame) GetSortOrder() int32 { + if x != nil { + return x.SortOrder + } + return 0 +} + +type ListGamesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Scene string `protobuf:"bytes,3,opt,name=scene,proto3" json:"scene,omitempty"` + RoomId string `protobuf:"bytes,4,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + RegionId int64 `protobuf:"varint,5,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` + Language string `protobuf:"bytes,6,opt,name=language,proto3" json:"language,omitempty"` + ClientPlatform string `protobuf:"bytes,7,opt,name=client_platform,json=clientPlatform,proto3" json:"client_platform,omitempty"` +} + +func (x *ListGamesRequest) Reset() { + *x = ListGamesRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListGamesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListGamesRequest) ProtoMessage() {} + +func (x *ListGamesRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListGamesRequest.ProtoReflect.Descriptor instead. +func (*ListGamesRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{4} +} + +func (x *ListGamesRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *ListGamesRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *ListGamesRequest) GetScene() string { + if x != nil { + return x.Scene + } + return "" +} + +func (x *ListGamesRequest) GetRoomId() string { + if x != nil { + return x.RoomId + } + return "" +} + +func (x *ListGamesRequest) GetRegionId() int64 { + if x != nil { + return x.RegionId + } + return 0 +} + +func (x *ListGamesRequest) GetLanguage() string { + if x != nil { + return x.Language + } + return "" +} + +func (x *ListGamesRequest) GetClientPlatform() string { + if x != nil { + return x.ClientPlatform + } + return "" +} + +type ListGamesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Games []*AppGame `protobuf:"bytes,1,rep,name=games,proto3" json:"games,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *ListGamesResponse) Reset() { + *x = ListGamesResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListGamesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListGamesResponse) ProtoMessage() {} + +func (x *ListGamesResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListGamesResponse.ProtoReflect.Descriptor instead. +func (*ListGamesResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{5} +} + +func (x *ListGamesResponse) GetGames() []*AppGame { + if x != nil { + return x.Games + } + return nil +} + +func (x *ListGamesResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type LaunchGameRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + DisplayUserId string `protobuf:"bytes,3,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"` + GameId string `protobuf:"bytes,4,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` + Scene string `protobuf:"bytes,5,opt,name=scene,proto3" json:"scene,omitempty"` + RoomId string `protobuf:"bytes,6,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` +} + +func (x *LaunchGameRequest) Reset() { + *x = LaunchGameRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LaunchGameRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LaunchGameRequest) ProtoMessage() {} + +func (x *LaunchGameRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[6] + 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 LaunchGameRequest.ProtoReflect.Descriptor instead. +func (*LaunchGameRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{6} +} + +func (x *LaunchGameRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *LaunchGameRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *LaunchGameRequest) GetDisplayUserId() string { + if x != nil { + return x.DisplayUserId + } + return "" +} + +func (x *LaunchGameRequest) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *LaunchGameRequest) GetScene() string { + if x != nil { + return x.Scene + } + return "" +} + +func (x *LaunchGameRequest) GetRoomId() string { + if x != nil { + return x.RoomId + } + return "" +} + +type LaunchGameResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + LaunchUrl string `protobuf:"bytes,2,opt,name=launch_url,json=launchUrl,proto3" json:"launch_url,omitempty"` + ExpiresAtMs int64 `protobuf:"varint,3,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` + Orientation string `protobuf:"bytes,4,opt,name=orientation,proto3" json:"orientation,omitempty"` + ServerTimeMs int64 `protobuf:"varint,5,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *LaunchGameResponse) Reset() { + *x = LaunchGameResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LaunchGameResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LaunchGameResponse) ProtoMessage() {} + +func (x *LaunchGameResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[7] + 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 LaunchGameResponse.ProtoReflect.Descriptor instead. +func (*LaunchGameResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{7} +} + +func (x *LaunchGameResponse) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +func (x *LaunchGameResponse) GetLaunchUrl() string { + if x != nil { + return x.LaunchUrl + } + return "" +} + +func (x *LaunchGameResponse) GetExpiresAtMs() int64 { + if x != nil { + return x.ExpiresAtMs + } + return 0 +} + +func (x *LaunchGameResponse) GetOrientation() string { + if x != nil { + return x.Orientation + } + return "" +} + +func (x *LaunchGameResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type CallbackRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + PlatformCode string `protobuf:"bytes,2,opt,name=platform_code,json=platformCode,proto3" json:"platform_code,omitempty"` + Operation string `protobuf:"bytes,3,opt,name=operation,proto3" json:"operation,omitempty"` + RawBody []byte `protobuf:"bytes,4,opt,name=raw_body,json=rawBody,proto3" json:"raw_body,omitempty"` + Headers map[string]string `protobuf:"bytes,5,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Query map[string]string `protobuf:"bytes,6,rep,name=query,proto3" json:"query,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + RemoteAddr string `protobuf:"bytes,7,opt,name=remote_addr,json=remoteAddr,proto3" json:"remote_addr,omitempty"` +} + +func (x *CallbackRequest) Reset() { + *x = CallbackRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CallbackRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CallbackRequest) ProtoMessage() {} + +func (x *CallbackRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[8] + 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 CallbackRequest.ProtoReflect.Descriptor instead. +func (*CallbackRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{8} +} + +func (x *CallbackRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *CallbackRequest) GetPlatformCode() string { + if x != nil { + return x.PlatformCode + } + return "" +} + +func (x *CallbackRequest) GetOperation() string { + if x != nil { + return x.Operation + } + return "" +} + +func (x *CallbackRequest) GetRawBody() []byte { + if x != nil { + return x.RawBody + } + return nil +} + +func (x *CallbackRequest) GetHeaders() map[string]string { + if x != nil { + return x.Headers + } + return nil +} + +func (x *CallbackRequest) GetQuery() map[string]string { + if x != nil { + return x.Query + } + return nil +} + +func (x *CallbackRequest) GetRemoteAddr() string { + if x != nil { + return x.RemoteAddr + } + return "" +} + +type CallbackResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RawBody []byte `protobuf:"bytes,1,opt,name=raw_body,json=rawBody,proto3" json:"raw_body,omitempty"` + ContentType string `protobuf:"bytes,2,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"` +} + +func (x *CallbackResponse) Reset() { + *x = CallbackResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CallbackResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CallbackResponse) ProtoMessage() {} + +func (x *CallbackResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[9] + 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 CallbackResponse.ProtoReflect.Descriptor instead. +func (*CallbackResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{9} +} + +func (x *CallbackResponse) GetRawBody() []byte { + if x != nil { + return x.RawBody + } + return nil +} + +func (x *CallbackResponse) GetContentType() string { + if x != nil { + return x.ContentType + } + return "" +} + +type ListPlatformsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *ListPlatformsRequest) Reset() { + *x = ListPlatformsRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListPlatformsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPlatformsRequest) ProtoMessage() {} + +func (x *ListPlatformsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[10] + 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 ListPlatformsRequest.ProtoReflect.Descriptor instead. +func (*ListPlatformsRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{10} +} + +func (x *ListPlatformsRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *ListPlatformsRequest) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +type ListPlatformsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Platforms []*GamePlatform `protobuf:"bytes,1,rep,name=platforms,proto3" json:"platforms,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *ListPlatformsResponse) Reset() { + *x = ListPlatformsResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListPlatformsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPlatformsResponse) ProtoMessage() {} + +func (x *ListPlatformsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[11] + 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 ListPlatformsResponse.ProtoReflect.Descriptor instead. +func (*ListPlatformsResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{11} +} + +func (x *ListPlatformsResponse) GetPlatforms() []*GamePlatform { + if x != nil { + return x.Platforms + } + return nil +} + +func (x *ListPlatformsResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type UpsertPlatformRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + Platform *GamePlatform `protobuf:"bytes,2,opt,name=platform,proto3" json:"platform,omitempty"` +} + +func (x *UpsertPlatformRequest) Reset() { + *x = UpsertPlatformRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpsertPlatformRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpsertPlatformRequest) ProtoMessage() {} + +func (x *UpsertPlatformRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[12] + 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 UpsertPlatformRequest.ProtoReflect.Descriptor instead. +func (*UpsertPlatformRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{12} +} + +func (x *UpsertPlatformRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *UpsertPlatformRequest) GetPlatform() *GamePlatform { + if x != nil { + return x.Platform + } + return nil +} + +type PlatformResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Platform *GamePlatform `protobuf:"bytes,1,opt,name=platform,proto3" json:"platform,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *PlatformResponse) Reset() { + *x = PlatformResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PlatformResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PlatformResponse) ProtoMessage() {} + +func (x *PlatformResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[13] + 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 PlatformResponse.ProtoReflect.Descriptor instead. +func (*PlatformResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{13} +} + +func (x *PlatformResponse) GetPlatform() *GamePlatform { + if x != nil { + return x.Platform + } + return nil +} + +func (x *PlatformResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type ListCatalogRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + PlatformCode string `protobuf:"bytes,2,opt,name=platform_code,json=platformCode,proto3" json:"platform_code,omitempty"` + Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` + PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + Cursor string `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"` +} + +func (x *ListCatalogRequest) Reset() { + *x = ListCatalogRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListCatalogRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListCatalogRequest) ProtoMessage() {} + +func (x *ListCatalogRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[14] + 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 ListCatalogRequest.ProtoReflect.Descriptor instead. +func (*ListCatalogRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{14} +} + +func (x *ListCatalogRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *ListCatalogRequest) GetPlatformCode() string { + if x != nil { + return x.PlatformCode + } + return "" +} + +func (x *ListCatalogRequest) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *ListCatalogRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListCatalogRequest) GetCursor() string { + if x != nil { + return x.Cursor + } + return "" +} + +type ListCatalogResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Games []*GameCatalogItem `protobuf:"bytes,1,rep,name=games,proto3" json:"games,omitempty"` + NextCursor string `protobuf:"bytes,2,opt,name=next_cursor,json=nextCursor,proto3" json:"next_cursor,omitempty"` + ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *ListCatalogResponse) Reset() { + *x = ListCatalogResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListCatalogResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListCatalogResponse) ProtoMessage() {} + +func (x *ListCatalogResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[15] + 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 ListCatalogResponse.ProtoReflect.Descriptor instead. +func (*ListCatalogResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{15} +} + +func (x *ListCatalogResponse) GetGames() []*GameCatalogItem { + if x != nil { + return x.Games + } + return nil +} + +func (x *ListCatalogResponse) GetNextCursor() string { + if x != nil { + return x.NextCursor + } + return "" +} + +func (x *ListCatalogResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type UpsertCatalogRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + Game *GameCatalogItem `protobuf:"bytes,2,opt,name=game,proto3" json:"game,omitempty"` +} + +func (x *UpsertCatalogRequest) Reset() { + *x = UpsertCatalogRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpsertCatalogRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpsertCatalogRequest) ProtoMessage() {} + +func (x *UpsertCatalogRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[16] + 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 UpsertCatalogRequest.ProtoReflect.Descriptor instead. +func (*UpsertCatalogRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{16} +} + +func (x *UpsertCatalogRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *UpsertCatalogRequest) GetGame() *GameCatalogItem { + if x != nil { + return x.Game + } + return nil +} + +type CatalogResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Game *GameCatalogItem `protobuf:"bytes,1,opt,name=game,proto3" json:"game,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *CatalogResponse) Reset() { + *x = CatalogResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CatalogResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CatalogResponse) ProtoMessage() {} + +func (x *CatalogResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[17] + 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 CatalogResponse.ProtoReflect.Descriptor instead. +func (*CatalogResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{17} +} + +func (x *CatalogResponse) GetGame() *GameCatalogItem { + if x != nil { + return x.Game + } + return nil +} + +func (x *CatalogResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type SetGameStatusRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` + Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *SetGameStatusRequest) Reset() { + *x = SetGameStatusRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetGameStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetGameStatusRequest) ProtoMessage() {} + +func (x *SetGameStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[18] + 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 SetGameStatusRequest.ProtoReflect.Descriptor instead. +func (*SetGameStatusRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{18} +} + +func (x *SetGameStatusRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *SetGameStatusRequest) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *SetGameStatusRequest) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +var File_proto_game_v1_game_proto protoreflect.FileDescriptor + +var file_proto_game_v1_game_proto_rawDesc = []byte{ + 0x0a, 0x18, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x61, 0x6d, 0x65, 0x2f, 0x76, 0x31, 0x2f, + 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x22, 0xc9, 0x01, 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, + 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, + 0x12, 0x26, 0x0a, 0x0f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x6e, 0x6f, 0x64, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x67, 0x61, 0x74, 0x65, 0x77, + 0x61, 0x79, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, + 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x0a, + 0x73, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 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, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, + 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x94, 0x02, 0x0a, 0x0c, 0x47, 0x61, 0x6d, 0x65, 0x50, 0x6c, + 0x61, 0x74, 0x66, 0x6f, 0x72, 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, 0x23, 0x0a, 0x0d, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, + 0x72, 0x6d, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, + 0x72, 0x6d, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, + 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 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, 0x20, 0x0a, 0x0c, 0x61, 0x70, 0x69, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, + 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x42, 0x61, + 0x73, 0x65, 0x55, 0x72, 0x6c, 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, 0x22, 0xf6, 0x03, 0x0a, + 0x0f, 0x47, 0x61, 0x6d, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 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, 0x17, 0x0a, 0x07, 0x67, + 0x61, 0x6d, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x61, + 0x6d, 0x65, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, + 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x6c, 0x61, + 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x47, 0x61, 0x6d, + 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x61, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, + 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x69, 0x63, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x76, 0x65, 0x72, + 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x76, 0x65, + 0x72, 0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x5f, 0x6d, + 0x6f, 0x64, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x61, 0x75, 0x6e, 0x63, + 0x68, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x6f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x72, 0x69, 0x65, + 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x69, 0x6e, 0x5f, 0x63, + 0x6f, 0x69, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, 0x69, 0x6e, 0x43, 0x6f, + 0x69, 0x6e, 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, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, + 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, + 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x67, + 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, 0x67, 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, 0x83, 0x03, 0x0a, 0x07, 0x41, 0x70, 0x70, 0x47, 0x61, 0x6d, + 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x67, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x6c, + 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x19, 0x0a, 0x08, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6e, 0x61, 0x6d, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 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, 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, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, + 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, + 0x72, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x5f, 0x6d, 0x6f, 0x64, + 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x4d, + 0x6f, 0x64, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x6f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x72, 0x69, 0x65, 0x6e, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x69, + 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x69, 0x6e, + 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x6d, 0x61, + 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0b, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22, 0xec, 0x01, 0x0a, 0x10, + 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x6d, 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, 0x67, 0x61, 0x6d, 0x65, 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, 0x73, 0x63, 0x65, + 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x65, 0x6e, 0x65, 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, 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, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, + 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x6c, 0x61, 0x74, + 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x22, 0x67, 0x0a, 0x11, 0x4c, 0x69, + 0x73, 0x74, 0x47, 0x61, 0x6d, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2c, 0x0a, 0x05, 0x67, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, + 0x70, 0x70, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x05, 0x67, 0x61, 0x6d, 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, 0xcc, 0x01, 0x0a, 0x11, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x47, 0x61, + 0x6d, 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, + 0x67, 0x61, 0x6d, 0x65, 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, 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, 0x17, 0x0a, 0x07, 0x67, 0x61, + 0x6d, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x61, 0x6d, + 0x65, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x65, 0x6e, 0x65, 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, 0x22, 0xbe, 0x01, 0x0a, 0x12, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x47, 0x61, 0x6d, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x75, 0x6e, + 0x63, 0x68, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x61, + 0x75, 0x6e, 0x63, 0x68, 0x55, 0x72, 0x6c, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x6f, + 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x6f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 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, 0xbe, 0x03, 0x0a, 0x0f, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 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, 0x67, 0x61, + 0x6d, 0x65, 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, 0x70, 0x6c, 0x61, 0x74, 0x66, + 0x6f, 0x72, 0x6d, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1c, 0x0a, 0x09, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x61, + 0x77, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x72, 0x61, + 0x77, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x45, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, + 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x67, + 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x3f, 0x0a, 0x05, + 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6c, 0x6c, + 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x1f, 0x0a, + 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x1a, 0x3a, + 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 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, 0x1a, 0x38, 0x0a, 0x0a, 0x51, 0x75, + 0x65, 0x72, 0x79, 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, 0x50, 0x0a, 0x10, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x61, 0x77, 0x5f, + 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x72, 0x61, 0x77, 0x42, + 0x6f, 0x64, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x5e, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, + 0x61, 0x74, 0x66, 0x6f, 0x72, 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, 0x67, 0x61, 0x6d, 0x65, 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, 0x78, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, + 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x39, 0x0a, 0x09, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x61, 0x6d, 0x65, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x52, + 0x09, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 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, 0x80, 0x01, 0x0a, 0x15, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x50, 0x6c, 0x61, 0x74, 0x66, + 0x6f, 0x72, 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, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x37, 0x0a, 0x08, 0x70, 0x6c, + 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x61, 0x6d, + 0x65, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, + 0x6f, 0x72, 0x6d, 0x22, 0x71, 0x0a, 0x10, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, + 0x6f, 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x61, 0x6d, 0x65, 0x50, 0x6c, + 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 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, 0xb6, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x61, 0x74, 0x61, 0x6c, 0x6f, 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, 0x67, 0x61, 0x6d, 0x65, 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, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 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, 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, 0x16, 0x0a, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, + 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x22, + 0x92, 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x05, 0x67, 0x61, 0x6d, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x67, + 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x61, 0x6d, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, + 0x6f, 0x67, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x67, 0x61, 0x6d, 0x65, 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, 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, 0x7a, 0x0a, 0x14, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x43, 0x61, + 0x74, 0x61, 0x6c, 0x6f, 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, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x32, 0x0a, 0x04, + 0x67, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x61, 0x6d, 0x65, 0x43, + 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x67, 0x61, 0x6d, 0x65, + 0x22, 0x6b, 0x0a, 0x0f, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x04, 0x67, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x61, 0x6d, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x74, 0x65, + 0x6d, 0x52, 0x04, 0x67, 0x61, 0x6d, 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, 0x77, 0x0a, + 0x14, 0x53, 0x65, 0x74, 0x47, 0x61, 0x6d, 0x65, 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, 0x67, 0x61, 0x6d, 0x65, + 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, 0x61, 0x6d, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x61, 0x6d, 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, 0x32, 0xb3, 0x01, 0x0a, 0x0e, 0x47, 0x61, 0x6d, 0x65, 0x41, + 0x70, 0x70, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x4c, 0x69, 0x73, + 0x74, 0x47, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x67, + 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x6d, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x6d, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0a, 0x4c, 0x61, 0x75, + 0x6e, 0x63, 0x68, 0x47, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x47, 0x61, + 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, + 0x47, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x68, 0x0a, 0x13, + 0x47, 0x61, 0x6d, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x51, 0x0a, 0x0e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x43, 0x61, 0x6c, + 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x67, 0x61, + 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x67, 0x61, + 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc9, 0x03, 0x0a, 0x10, 0x47, 0x61, 0x6d, 0x65, 0x41, + 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x12, 0x23, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0e, 0x55, 0x70, 0x73, 0x65, 0x72, + 0x74, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, + 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x54, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, + 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0d, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, + 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x43, 0x61, + 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x74, + 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0d, + 0x53, 0x65, 0x74, 0x47, 0x61, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, + 0x74, 0x47, 0x61, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 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, 0x67, 0x61, 0x6d, 0x65, + 0x2f, 0x76, 0x31, 0x3b, 0x67, 0x61, 0x6d, 0x65, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_proto_game_v1_game_proto_rawDescOnce sync.Once + file_proto_game_v1_game_proto_rawDescData = file_proto_game_v1_game_proto_rawDesc +) + +func file_proto_game_v1_game_proto_rawDescGZIP() []byte { + file_proto_game_v1_game_proto_rawDescOnce.Do(func() { + file_proto_game_v1_game_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_game_v1_game_proto_rawDescData) + }) + return file_proto_game_v1_game_proto_rawDescData +} + +var file_proto_game_v1_game_proto_msgTypes = make([]protoimpl.MessageInfo, 21) +var file_proto_game_v1_game_proto_goTypes = []any{ + (*RequestMeta)(nil), // 0: hyapp.game.v1.RequestMeta + (*GamePlatform)(nil), // 1: hyapp.game.v1.GamePlatform + (*GameCatalogItem)(nil), // 2: hyapp.game.v1.GameCatalogItem + (*AppGame)(nil), // 3: hyapp.game.v1.AppGame + (*ListGamesRequest)(nil), // 4: hyapp.game.v1.ListGamesRequest + (*ListGamesResponse)(nil), // 5: hyapp.game.v1.ListGamesResponse + (*LaunchGameRequest)(nil), // 6: hyapp.game.v1.LaunchGameRequest + (*LaunchGameResponse)(nil), // 7: hyapp.game.v1.LaunchGameResponse + (*CallbackRequest)(nil), // 8: hyapp.game.v1.CallbackRequest + (*CallbackResponse)(nil), // 9: hyapp.game.v1.CallbackResponse + (*ListPlatformsRequest)(nil), // 10: hyapp.game.v1.ListPlatformsRequest + (*ListPlatformsResponse)(nil), // 11: hyapp.game.v1.ListPlatformsResponse + (*UpsertPlatformRequest)(nil), // 12: hyapp.game.v1.UpsertPlatformRequest + (*PlatformResponse)(nil), // 13: hyapp.game.v1.PlatformResponse + (*ListCatalogRequest)(nil), // 14: hyapp.game.v1.ListCatalogRequest + (*ListCatalogResponse)(nil), // 15: hyapp.game.v1.ListCatalogResponse + (*UpsertCatalogRequest)(nil), // 16: hyapp.game.v1.UpsertCatalogRequest + (*CatalogResponse)(nil), // 17: hyapp.game.v1.CatalogResponse + (*SetGameStatusRequest)(nil), // 18: hyapp.game.v1.SetGameStatusRequest + nil, // 19: hyapp.game.v1.CallbackRequest.HeadersEntry + nil, // 20: hyapp.game.v1.CallbackRequest.QueryEntry +} +var file_proto_game_v1_game_proto_depIdxs = []int32{ + 0, // 0: hyapp.game.v1.ListGamesRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 3, // 1: hyapp.game.v1.ListGamesResponse.games:type_name -> hyapp.game.v1.AppGame + 0, // 2: hyapp.game.v1.LaunchGameRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 3: hyapp.game.v1.CallbackRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 19, // 4: hyapp.game.v1.CallbackRequest.headers:type_name -> hyapp.game.v1.CallbackRequest.HeadersEntry + 20, // 5: hyapp.game.v1.CallbackRequest.query:type_name -> hyapp.game.v1.CallbackRequest.QueryEntry + 0, // 6: hyapp.game.v1.ListPlatformsRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 1, // 7: hyapp.game.v1.ListPlatformsResponse.platforms:type_name -> hyapp.game.v1.GamePlatform + 0, // 8: hyapp.game.v1.UpsertPlatformRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 1, // 9: hyapp.game.v1.UpsertPlatformRequest.platform:type_name -> hyapp.game.v1.GamePlatform + 1, // 10: hyapp.game.v1.PlatformResponse.platform:type_name -> hyapp.game.v1.GamePlatform + 0, // 11: hyapp.game.v1.ListCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 2, // 12: hyapp.game.v1.ListCatalogResponse.games:type_name -> hyapp.game.v1.GameCatalogItem + 0, // 13: hyapp.game.v1.UpsertCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 2, // 14: hyapp.game.v1.UpsertCatalogRequest.game:type_name -> hyapp.game.v1.GameCatalogItem + 2, // 15: hyapp.game.v1.CatalogResponse.game:type_name -> hyapp.game.v1.GameCatalogItem + 0, // 16: hyapp.game.v1.SetGameStatusRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 4, // 17: hyapp.game.v1.GameAppService.ListGames:input_type -> hyapp.game.v1.ListGamesRequest + 6, // 18: hyapp.game.v1.GameAppService.LaunchGame:input_type -> hyapp.game.v1.LaunchGameRequest + 8, // 19: hyapp.game.v1.GameCallbackService.HandleCallback:input_type -> hyapp.game.v1.CallbackRequest + 10, // 20: hyapp.game.v1.GameAdminService.ListPlatforms:input_type -> hyapp.game.v1.ListPlatformsRequest + 12, // 21: hyapp.game.v1.GameAdminService.UpsertPlatform:input_type -> hyapp.game.v1.UpsertPlatformRequest + 14, // 22: hyapp.game.v1.GameAdminService.ListCatalog:input_type -> hyapp.game.v1.ListCatalogRequest + 16, // 23: hyapp.game.v1.GameAdminService.UpsertCatalog:input_type -> hyapp.game.v1.UpsertCatalogRequest + 18, // 24: hyapp.game.v1.GameAdminService.SetGameStatus:input_type -> hyapp.game.v1.SetGameStatusRequest + 5, // 25: hyapp.game.v1.GameAppService.ListGames:output_type -> hyapp.game.v1.ListGamesResponse + 7, // 26: hyapp.game.v1.GameAppService.LaunchGame:output_type -> hyapp.game.v1.LaunchGameResponse + 9, // 27: hyapp.game.v1.GameCallbackService.HandleCallback:output_type -> hyapp.game.v1.CallbackResponse + 11, // 28: hyapp.game.v1.GameAdminService.ListPlatforms:output_type -> hyapp.game.v1.ListPlatformsResponse + 13, // 29: hyapp.game.v1.GameAdminService.UpsertPlatform:output_type -> hyapp.game.v1.PlatformResponse + 15, // 30: hyapp.game.v1.GameAdminService.ListCatalog:output_type -> hyapp.game.v1.ListCatalogResponse + 17, // 31: hyapp.game.v1.GameAdminService.UpsertCatalog:output_type -> hyapp.game.v1.CatalogResponse + 17, // 32: hyapp.game.v1.GameAdminService.SetGameStatus:output_type -> hyapp.game.v1.CatalogResponse + 25, // [25:33] is the sub-list for method output_type + 17, // [17:25] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name +} + +func init() { file_proto_game_v1_game_proto_init() } +func file_proto_game_v1_game_proto_init() { + if File_proto_game_v1_game_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto_game_v1_game_proto_rawDesc, + NumEnums: 0, + NumMessages: 21, + NumExtensions: 0, + NumServices: 3, + }, + GoTypes: file_proto_game_v1_game_proto_goTypes, + DependencyIndexes: file_proto_game_v1_game_proto_depIdxs, + MessageInfos: file_proto_game_v1_game_proto_msgTypes, + }.Build() + File_proto_game_v1_game_proto = out.File + file_proto_game_v1_game_proto_rawDesc = nil + file_proto_game_v1_game_proto_goTypes = nil + file_proto_game_v1_game_proto_depIdxs = nil +} diff --git a/api/proto/game/v1/game.proto b/api/proto/game/v1/game.proto new file mode 100644 index 00000000..58eda390 --- /dev/null +++ b/api/proto/game/v1/game.proto @@ -0,0 +1,175 @@ +syntax = "proto3"; + +package hyapp.game.v1; + +option go_package = "hyapp.local/api/proto/game/v1;gamev1"; + +// RequestMeta 是 game-service 内部 RPC 的最小追踪和租户上下文。 +message RequestMeta { + string request_id = 1; + string caller = 2; + string gateway_node_id = 3; + int64 actor_user_id = 4; + int64 sent_at_ms = 5; + string app_code = 6; +} + +message GamePlatform { + string app_code = 1; + string platform_code = 2; + string platform_name = 3; + string status = 4; + string api_base_url = 5; + int32 sort_order = 6; + int64 created_at_ms = 7; + int64 updated_at_ms = 8; +} + +message GameCatalogItem { + string app_code = 1; + string game_id = 2; + string platform_code = 3; + string provider_game_id = 4; + string game_name = 5; + string category = 6; + string icon_url = 7; + string cover_url = 8; + string launch_mode = 9; + string orientation = 10; + int64 min_coin = 11; + string status = 12; + int32 sort_order = 13; + repeated string tags = 14; + int64 created_at_ms = 15; + int64 updated_at_ms = 16; +} + +message AppGame { + string game_id = 1; + string platform_code = 2; + string name_key = 3; + string name = 4; + string icon_url = 5; + string cover_url = 6; + string category = 7; + string launch_mode = 8; + string orientation = 9; + int64 min_coin = 10; + bool enabled = 11; + bool maintenance = 12; + int32 sort_order = 13; +} + +message ListGamesRequest { + RequestMeta meta = 1; + int64 user_id = 2; + string scene = 3; + string room_id = 4; + int64 region_id = 5; + string language = 6; + string client_platform = 7; +} + +message ListGamesResponse { + repeated AppGame games = 1; + int64 server_time_ms = 2; +} + +message LaunchGameRequest { + RequestMeta meta = 1; + int64 user_id = 2; + string display_user_id = 3; + string game_id = 4; + string scene = 5; + string room_id = 6; +} + +message LaunchGameResponse { + string session_id = 1; + string launch_url = 2; + int64 expires_at_ms = 3; + string orientation = 4; + int64 server_time_ms = 5; +} + +message CallbackRequest { + RequestMeta meta = 1; + string platform_code = 2; + string operation = 3; + bytes raw_body = 4; + map headers = 5; + map query = 6; + string remote_addr = 7; +} + +message CallbackResponse { + bytes raw_body = 1; + string content_type = 2; +} + +message ListPlatformsRequest { + RequestMeta meta = 1; + string status = 2; +} + +message ListPlatformsResponse { + repeated GamePlatform platforms = 1; + int64 server_time_ms = 2; +} + +message UpsertPlatformRequest { + RequestMeta meta = 1; + GamePlatform platform = 2; +} + +message PlatformResponse { + GamePlatform platform = 1; + int64 server_time_ms = 2; +} + +message ListCatalogRequest { + RequestMeta meta = 1; + string platform_code = 2; + string status = 3; + int32 page_size = 4; + string cursor = 5; +} + +message ListCatalogResponse { + repeated GameCatalogItem games = 1; + string next_cursor = 2; + int64 server_time_ms = 3; +} + +message UpsertCatalogRequest { + RequestMeta meta = 1; + GameCatalogItem game = 2; +} + +message CatalogResponse { + GameCatalogItem game = 1; + int64 server_time_ms = 2; +} + +message SetGameStatusRequest { + RequestMeta meta = 1; + string game_id = 2; + string status = 3; +} + +service GameAppService { + rpc ListGames(ListGamesRequest) returns (ListGamesResponse); + rpc LaunchGame(LaunchGameRequest) returns (LaunchGameResponse); +} + +service GameCallbackService { + rpc HandleCallback(CallbackRequest) returns (CallbackResponse); +} + +service GameAdminService { + rpc ListPlatforms(ListPlatformsRequest) returns (ListPlatformsResponse); + rpc UpsertPlatform(UpsertPlatformRequest) returns (PlatformResponse); + rpc ListCatalog(ListCatalogRequest) returns (ListCatalogResponse); + rpc UpsertCatalog(UpsertCatalogRequest) returns (CatalogResponse); + rpc SetGameStatus(SetGameStatusRequest) returns (CatalogResponse); +} diff --git a/api/proto/game/v1/game_grpc.pb.go b/api/proto/game/v1/game_grpc.pb.go new file mode 100644 index 00000000..c05a6c31 --- /dev/null +++ b/api/proto/game/v1/game_grpc.pb.go @@ -0,0 +1,515 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v5.29.3 +// source: proto/game/v1/game.proto + +package gamev1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + GameAppService_ListGames_FullMethodName = "/hyapp.game.v1.GameAppService/ListGames" + GameAppService_LaunchGame_FullMethodName = "/hyapp.game.v1.GameAppService/LaunchGame" +) + +// GameAppServiceClient is the client API for GameAppService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type GameAppServiceClient interface { + ListGames(ctx context.Context, in *ListGamesRequest, opts ...grpc.CallOption) (*ListGamesResponse, error) + LaunchGame(ctx context.Context, in *LaunchGameRequest, opts ...grpc.CallOption) (*LaunchGameResponse, error) +} + +type gameAppServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewGameAppServiceClient(cc grpc.ClientConnInterface) GameAppServiceClient { + return &gameAppServiceClient{cc} +} + +func (c *gameAppServiceClient) ListGames(ctx context.Context, in *ListGamesRequest, opts ...grpc.CallOption) (*ListGamesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListGamesResponse) + err := c.cc.Invoke(ctx, GameAppService_ListGames_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAppServiceClient) LaunchGame(ctx context.Context, in *LaunchGameRequest, opts ...grpc.CallOption) (*LaunchGameResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(LaunchGameResponse) + err := c.cc.Invoke(ctx, GameAppService_LaunchGame_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GameAppServiceServer is the server API for GameAppService service. +// All implementations must embed UnimplementedGameAppServiceServer +// for forward compatibility. +type GameAppServiceServer interface { + ListGames(context.Context, *ListGamesRequest) (*ListGamesResponse, error) + LaunchGame(context.Context, *LaunchGameRequest) (*LaunchGameResponse, error) + mustEmbedUnimplementedGameAppServiceServer() +} + +// UnimplementedGameAppServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedGameAppServiceServer struct{} + +func (UnimplementedGameAppServiceServer) ListGames(context.Context, *ListGamesRequest) (*ListGamesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListGames not implemented") +} +func (UnimplementedGameAppServiceServer) LaunchGame(context.Context, *LaunchGameRequest) (*LaunchGameResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method LaunchGame not implemented") +} +func (UnimplementedGameAppServiceServer) mustEmbedUnimplementedGameAppServiceServer() {} +func (UnimplementedGameAppServiceServer) testEmbeddedByValue() {} + +// UnsafeGameAppServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GameAppServiceServer will +// result in compilation errors. +type UnsafeGameAppServiceServer interface { + mustEmbedUnimplementedGameAppServiceServer() +} + +func RegisterGameAppServiceServer(s grpc.ServiceRegistrar, srv GameAppServiceServer) { + // If the following call pancis, it indicates UnimplementedGameAppServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&GameAppService_ServiceDesc, srv) +} + +func _GameAppService_ListGames_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListGamesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAppServiceServer).ListGames(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAppService_ListGames_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAppServiceServer).ListGames(ctx, req.(*ListGamesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAppService_LaunchGame_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LaunchGameRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAppServiceServer).LaunchGame(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAppService_LaunchGame_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAppServiceServer).LaunchGame(ctx, req.(*LaunchGameRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// GameAppService_ServiceDesc is the grpc.ServiceDesc for GameAppService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var GameAppService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hyapp.game.v1.GameAppService", + HandlerType: (*GameAppServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListGames", + Handler: _GameAppService_ListGames_Handler, + }, + { + MethodName: "LaunchGame", + Handler: _GameAppService_LaunchGame_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "proto/game/v1/game.proto", +} + +const ( + GameCallbackService_HandleCallback_FullMethodName = "/hyapp.game.v1.GameCallbackService/HandleCallback" +) + +// GameCallbackServiceClient is the client API for GameCallbackService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type GameCallbackServiceClient interface { + HandleCallback(ctx context.Context, in *CallbackRequest, opts ...grpc.CallOption) (*CallbackResponse, error) +} + +type gameCallbackServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewGameCallbackServiceClient(cc grpc.ClientConnInterface) GameCallbackServiceClient { + return &gameCallbackServiceClient{cc} +} + +func (c *gameCallbackServiceClient) HandleCallback(ctx context.Context, in *CallbackRequest, opts ...grpc.CallOption) (*CallbackResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CallbackResponse) + err := c.cc.Invoke(ctx, GameCallbackService_HandleCallback_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GameCallbackServiceServer is the server API for GameCallbackService service. +// All implementations must embed UnimplementedGameCallbackServiceServer +// for forward compatibility. +type GameCallbackServiceServer interface { + HandleCallback(context.Context, *CallbackRequest) (*CallbackResponse, error) + mustEmbedUnimplementedGameCallbackServiceServer() +} + +// UnimplementedGameCallbackServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedGameCallbackServiceServer struct{} + +func (UnimplementedGameCallbackServiceServer) HandleCallback(context.Context, *CallbackRequest) (*CallbackResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method HandleCallback not implemented") +} +func (UnimplementedGameCallbackServiceServer) mustEmbedUnimplementedGameCallbackServiceServer() {} +func (UnimplementedGameCallbackServiceServer) testEmbeddedByValue() {} + +// UnsafeGameCallbackServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GameCallbackServiceServer will +// result in compilation errors. +type UnsafeGameCallbackServiceServer interface { + mustEmbedUnimplementedGameCallbackServiceServer() +} + +func RegisterGameCallbackServiceServer(s grpc.ServiceRegistrar, srv GameCallbackServiceServer) { + // If the following call pancis, it indicates UnimplementedGameCallbackServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&GameCallbackService_ServiceDesc, srv) +} + +func _GameCallbackService_HandleCallback_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CallbackRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameCallbackServiceServer).HandleCallback(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameCallbackService_HandleCallback_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameCallbackServiceServer).HandleCallback(ctx, req.(*CallbackRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// GameCallbackService_ServiceDesc is the grpc.ServiceDesc for GameCallbackService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var GameCallbackService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hyapp.game.v1.GameCallbackService", + HandlerType: (*GameCallbackServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "HandleCallback", + Handler: _GameCallbackService_HandleCallback_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "proto/game/v1/game.proto", +} + +const ( + GameAdminService_ListPlatforms_FullMethodName = "/hyapp.game.v1.GameAdminService/ListPlatforms" + GameAdminService_UpsertPlatform_FullMethodName = "/hyapp.game.v1.GameAdminService/UpsertPlatform" + GameAdminService_ListCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/ListCatalog" + GameAdminService_UpsertCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/UpsertCatalog" + GameAdminService_SetGameStatus_FullMethodName = "/hyapp.game.v1.GameAdminService/SetGameStatus" +) + +// GameAdminServiceClient is the client API for GameAdminService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type GameAdminServiceClient interface { + ListPlatforms(ctx context.Context, in *ListPlatformsRequest, opts ...grpc.CallOption) (*ListPlatformsResponse, error) + UpsertPlatform(ctx context.Context, in *UpsertPlatformRequest, opts ...grpc.CallOption) (*PlatformResponse, error) + ListCatalog(ctx context.Context, in *ListCatalogRequest, opts ...grpc.CallOption) (*ListCatalogResponse, error) + UpsertCatalog(ctx context.Context, in *UpsertCatalogRequest, opts ...grpc.CallOption) (*CatalogResponse, error) + SetGameStatus(ctx context.Context, in *SetGameStatusRequest, opts ...grpc.CallOption) (*CatalogResponse, error) +} + +type gameAdminServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewGameAdminServiceClient(cc grpc.ClientConnInterface) GameAdminServiceClient { + return &gameAdminServiceClient{cc} +} + +func (c *gameAdminServiceClient) ListPlatforms(ctx context.Context, in *ListPlatformsRequest, opts ...grpc.CallOption) (*ListPlatformsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListPlatformsResponse) + err := c.cc.Invoke(ctx, GameAdminService_ListPlatforms_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAdminServiceClient) UpsertPlatform(ctx context.Context, in *UpsertPlatformRequest, opts ...grpc.CallOption) (*PlatformResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PlatformResponse) + err := c.cc.Invoke(ctx, GameAdminService_UpsertPlatform_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAdminServiceClient) ListCatalog(ctx context.Context, in *ListCatalogRequest, opts ...grpc.CallOption) (*ListCatalogResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListCatalogResponse) + err := c.cc.Invoke(ctx, GameAdminService_ListCatalog_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAdminServiceClient) UpsertCatalog(ctx context.Context, in *UpsertCatalogRequest, opts ...grpc.CallOption) (*CatalogResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CatalogResponse) + err := c.cc.Invoke(ctx, GameAdminService_UpsertCatalog_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAdminServiceClient) SetGameStatus(ctx context.Context, in *SetGameStatusRequest, opts ...grpc.CallOption) (*CatalogResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CatalogResponse) + err := c.cc.Invoke(ctx, GameAdminService_SetGameStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GameAdminServiceServer is the server API for GameAdminService service. +// All implementations must embed UnimplementedGameAdminServiceServer +// for forward compatibility. +type GameAdminServiceServer interface { + ListPlatforms(context.Context, *ListPlatformsRequest) (*ListPlatformsResponse, error) + UpsertPlatform(context.Context, *UpsertPlatformRequest) (*PlatformResponse, error) + ListCatalog(context.Context, *ListCatalogRequest) (*ListCatalogResponse, error) + UpsertCatalog(context.Context, *UpsertCatalogRequest) (*CatalogResponse, error) + SetGameStatus(context.Context, *SetGameStatusRequest) (*CatalogResponse, error) + mustEmbedUnimplementedGameAdminServiceServer() +} + +// UnimplementedGameAdminServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedGameAdminServiceServer struct{} + +func (UnimplementedGameAdminServiceServer) ListPlatforms(context.Context, *ListPlatformsRequest) (*ListPlatformsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListPlatforms not implemented") +} +func (UnimplementedGameAdminServiceServer) UpsertPlatform(context.Context, *UpsertPlatformRequest) (*PlatformResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpsertPlatform not implemented") +} +func (UnimplementedGameAdminServiceServer) ListCatalog(context.Context, *ListCatalogRequest) (*ListCatalogResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListCatalog not implemented") +} +func (UnimplementedGameAdminServiceServer) UpsertCatalog(context.Context, *UpsertCatalogRequest) (*CatalogResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpsertCatalog not implemented") +} +func (UnimplementedGameAdminServiceServer) SetGameStatus(context.Context, *SetGameStatusRequest) (*CatalogResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetGameStatus not implemented") +} +func (UnimplementedGameAdminServiceServer) mustEmbedUnimplementedGameAdminServiceServer() {} +func (UnimplementedGameAdminServiceServer) testEmbeddedByValue() {} + +// UnsafeGameAdminServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GameAdminServiceServer will +// result in compilation errors. +type UnsafeGameAdminServiceServer interface { + mustEmbedUnimplementedGameAdminServiceServer() +} + +func RegisterGameAdminServiceServer(s grpc.ServiceRegistrar, srv GameAdminServiceServer) { + // If the following call pancis, it indicates UnimplementedGameAdminServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&GameAdminService_ServiceDesc, srv) +} + +func _GameAdminService_ListPlatforms_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListPlatformsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).ListPlatforms(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_ListPlatforms_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).ListPlatforms(ctx, req.(*ListPlatformsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAdminService_UpsertPlatform_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpsertPlatformRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).UpsertPlatform(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_UpsertPlatform_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).UpsertPlatform(ctx, req.(*UpsertPlatformRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAdminService_ListCatalog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListCatalogRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).ListCatalog(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_ListCatalog_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).ListCatalog(ctx, req.(*ListCatalogRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAdminService_UpsertCatalog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpsertCatalogRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).UpsertCatalog(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_UpsertCatalog_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).UpsertCatalog(ctx, req.(*UpsertCatalogRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAdminService_SetGameStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetGameStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).SetGameStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_SetGameStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).SetGameStatus(ctx, req.(*SetGameStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// GameAdminService_ServiceDesc is the grpc.ServiceDesc for GameAdminService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var GameAdminService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hyapp.game.v1.GameAdminService", + HandlerType: (*GameAdminServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListPlatforms", + Handler: _GameAdminService_ListPlatforms_Handler, + }, + { + MethodName: "UpsertPlatform", + Handler: _GameAdminService_UpsertPlatform_Handler, + }, + { + MethodName: "ListCatalog", + Handler: _GameAdminService_ListCatalog_Handler, + }, + { + MethodName: "UpsertCatalog", + Handler: _GameAdminService_UpsertCatalog_Handler, + }, + { + MethodName: "SetGameStatus", + Handler: _GameAdminService_SetGameStatus_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "proto/game/v1/game.proto", +} diff --git a/api/proto/room/v1/room.pb.go b/api/proto/room/v1/room.pb.go index 6a35df1c..1fed6c64 100644 --- a/api/proto/room/v1/room.pb.go +++ b/api/proto/room/v1/room.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.35.1 -// protoc v5.27.3 +// protoc v5.29.3 // source: proto/room/v1/room.proto package roomv1 @@ -276,6 +276,8 @@ type SeatState struct { MicSessionRoomVersion int64 `protobuf:"varint,7,opt,name=mic_session_room_version,json=micSessionRoomVersion,proto3" json:"mic_session_room_version,omitempty"` // last_publish_event_time_ms 记录已接受的最新 RTC/客户端发流事件时间。 LastPublishEventTimeMs int64 `protobuf:"varint,8,opt,name=last_publish_event_time_ms,json=lastPublishEventTimeMs,proto3" json:"last_publish_event_time_ms,omitempty"` + // mic_muted 是服务端可见的麦克风静音状态,用于同步其他用户 UI。 + MicMuted bool `protobuf:"varint,9,opt,name=mic_muted,json=micMuted,proto3" json:"mic_muted,omitempty"` } func (x *SeatState) Reset() { @@ -364,6 +366,13 @@ func (x *SeatState) GetLastPublishEventTimeMs() int64 { return 0 } +func (x *SeatState) GetMicMuted() bool { + if x != nil { + return x.MicMuted + } + return false +} + // RankItem 表达房间内本地礼物榜项目。 type RankItem struct { state protoimpl.MessageState @@ -440,23 +449,24 @@ type RoomSnapshot struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - RoomId string `protobuf:"bytes,1,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` - OwnerUserId int64 `protobuf:"varint,2,opt,name=owner_user_id,json=ownerUserId,proto3" json:"owner_user_id,omitempty"` - HostUserId int64 `protobuf:"varint,3,opt,name=host_user_id,json=hostUserId,proto3" json:"host_user_id,omitempty"` - Mode string `protobuf:"bytes,4,opt,name=mode,proto3" json:"mode,omitempty"` - Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` - ChatEnabled bool `protobuf:"varint,6,opt,name=chat_enabled,json=chatEnabled,proto3" json:"chat_enabled,omitempty"` - MicSeats []*SeatState `protobuf:"bytes,7,rep,name=mic_seats,json=micSeats,proto3" json:"mic_seats,omitempty"` - OnlineUsers []*RoomUser `protobuf:"bytes,8,rep,name=online_users,json=onlineUsers,proto3" json:"online_users,omitempty"` - AdminUserIds []int64 `protobuf:"varint,9,rep,packed,name=admin_user_ids,json=adminUserIds,proto3" json:"admin_user_ids,omitempty"` - BanUserIds []int64 `protobuf:"varint,10,rep,packed,name=ban_user_ids,json=banUserIds,proto3" json:"ban_user_ids,omitempty"` - MuteUserIds []int64 `protobuf:"varint,11,rep,packed,name=mute_user_ids,json=muteUserIds,proto3" json:"mute_user_ids,omitempty"` - GiftRank []*RankItem `protobuf:"bytes,12,rep,name=gift_rank,json=giftRank,proto3" json:"gift_rank,omitempty"` - RoomExt map[string]string `protobuf:"bytes,13,rep,name=room_ext,json=roomExt,proto3" json:"room_ext,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - Heat int64 `protobuf:"varint,14,opt,name=heat,proto3" json:"heat,omitempty"` - Version int64 `protobuf:"varint,15,opt,name=version,proto3" json:"version,omitempty"` - AppCode string `protobuf:"bytes,16,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` - RoomShortId string `protobuf:"bytes,17,opt,name=room_short_id,json=roomShortId,proto3" json:"room_short_id,omitempty"` + RoomId string `protobuf:"bytes,1,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + OwnerUserId int64 `protobuf:"varint,2,opt,name=owner_user_id,json=ownerUserId,proto3" json:"owner_user_id,omitempty"` + HostUserId int64 `protobuf:"varint,3,opt,name=host_user_id,json=hostUserId,proto3" json:"host_user_id,omitempty"` + Mode string `protobuf:"bytes,4,opt,name=mode,proto3" json:"mode,omitempty"` + Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` + ChatEnabled bool `protobuf:"varint,6,opt,name=chat_enabled,json=chatEnabled,proto3" json:"chat_enabled,omitempty"` + MicSeats []*SeatState `protobuf:"bytes,7,rep,name=mic_seats,json=micSeats,proto3" json:"mic_seats,omitempty"` + OnlineUsers []*RoomUser `protobuf:"bytes,8,rep,name=online_users,json=onlineUsers,proto3" json:"online_users,omitempty"` + AdminUserIds []int64 `protobuf:"varint,9,rep,packed,name=admin_user_ids,json=adminUserIds,proto3" json:"admin_user_ids,omitempty"` + BanUserIds []int64 `protobuf:"varint,10,rep,packed,name=ban_user_ids,json=banUserIds,proto3" json:"ban_user_ids,omitempty"` + MuteUserIds []int64 `protobuf:"varint,11,rep,packed,name=mute_user_ids,json=muteUserIds,proto3" json:"mute_user_ids,omitempty"` + GiftRank []*RankItem `protobuf:"bytes,12,rep,name=gift_rank,json=giftRank,proto3" json:"gift_rank,omitempty"` + RoomExt map[string]string `protobuf:"bytes,13,rep,name=room_ext,json=roomExt,proto3" json:"room_ext,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Heat int64 `protobuf:"varint,14,opt,name=heat,proto3" json:"heat,omitempty"` + Version int64 `protobuf:"varint,15,opt,name=version,proto3" json:"version,omitempty"` + AppCode string `protobuf:"bytes,16,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + RoomShortId string `protobuf:"bytes,17,opt,name=room_short_id,json=roomShortId,proto3" json:"room_short_id,omitempty"` + VisibleRegionId int64 `protobuf:"varint,18,opt,name=visible_region_id,json=visibleRegionId,proto3" json:"visible_region_id,omitempty"` } func (x *RoomSnapshot) Reset() { @@ -608,6 +618,13 @@ func (x *RoomSnapshot) GetRoomShortId() string { return "" } +func (x *RoomSnapshot) GetVisibleRegionId() int64 { + if x != nil { + return x.VisibleRegionId + } + return 0 +} + // CreateRoomRequest 创建一个新的房间执行单元。 // 必填语义:meta.room_id、meta.command_id、meta.actor_user_id、mode 和 room_name。 // 同一个 meta.actor_user_id 只能作为 owner 创建一个房间;重复创建返回 Conflict。 @@ -1862,6 +1879,131 @@ func (x *ConfirmMicPublishingResponse) GetRoom() *RoomSnapshot { return nil } +// SetMicMuteRequest 修改麦位上的服务端可见静音态。 +type SetMicMuteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + // target_user_id 为空时默认修改 actor_user_id 自己的麦克风静音态。 + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + Muted bool `protobuf:"varint,3,opt,name=muted,proto3" json:"muted,omitempty"` +} + +func (x *SetMicMuteRequest) Reset() { + *x = SetMicMuteRequest{} + mi := &file_proto_room_v1_room_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetMicMuteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetMicMuteRequest) ProtoMessage() {} + +func (x *SetMicMuteRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_room_v1_room_proto_msgTypes[26] + 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 SetMicMuteRequest.ProtoReflect.Descriptor instead. +func (*SetMicMuteRequest) Descriptor() ([]byte, []int) { + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{26} +} + +func (x *SetMicMuteRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *SetMicMuteRequest) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *SetMicMuteRequest) GetMuted() bool { + if x != nil { + return x.Muted + } + return false +} + +// SetMicMuteResponse 返回静音态变更后的最新房间快照。 +type SetMicMuteResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + SeatNo int32 `protobuf:"varint,2,opt,name=seat_no,json=seatNo,proto3" json:"seat_no,omitempty"` + Room *RoomSnapshot `protobuf:"bytes,3,opt,name=room,proto3" json:"room,omitempty"` +} + +func (x *SetMicMuteResponse) Reset() { + *x = SetMicMuteResponse{} + mi := &file_proto_room_v1_room_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetMicMuteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetMicMuteResponse) ProtoMessage() {} + +func (x *SetMicMuteResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_room_v1_room_proto_msgTypes[27] + 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 SetMicMuteResponse.ProtoReflect.Descriptor instead. +func (*SetMicMuteResponse) Descriptor() ([]byte, []int) { + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{27} +} + +func (x *SetMicMuteResponse) GetResult() *CommandResult { + if x != nil { + return x.Result + } + return nil +} + +func (x *SetMicMuteResponse) GetSeatNo() int32 { + if x != nil { + return x.SeatNo + } + return 0 +} + +func (x *SetMicMuteResponse) GetRoom() *RoomSnapshot { + if x != nil { + return x.Room + } + return nil +} + // ApplyRTCEventRequest 把可信 RTC 服务端回调转换成 Room Cell 命令。 // event_type 当前只接受 audio_started、audio_stopped、room_exited。 type ApplyRTCEventRequest struct { @@ -1880,7 +2022,7 @@ type ApplyRTCEventRequest struct { func (x *ApplyRTCEventRequest) Reset() { *x = ApplyRTCEventRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[26] + mi := &file_proto_room_v1_room_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1892,7 +2034,7 @@ func (x *ApplyRTCEventRequest) String() string { func (*ApplyRTCEventRequest) ProtoMessage() {} func (x *ApplyRTCEventRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[26] + mi := &file_proto_room_v1_room_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1905,7 +2047,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{26} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{28} } func (x *ApplyRTCEventRequest) GetMeta() *RequestMeta { @@ -1970,7 +2112,7 @@ type ApplyRTCEventResponse struct { func (x *ApplyRTCEventResponse) Reset() { *x = ApplyRTCEventResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[27] + mi := &file_proto_room_v1_room_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1982,7 +2124,7 @@ func (x *ApplyRTCEventResponse) String() string { func (*ApplyRTCEventResponse) ProtoMessage() {} func (x *ApplyRTCEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[27] + mi := &file_proto_room_v1_room_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1995,7 +2137,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{27} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{29} } func (x *ApplyRTCEventResponse) GetResult() *CommandResult { @@ -2032,7 +2174,7 @@ type SetMicSeatLockRequest struct { func (x *SetMicSeatLockRequest) Reset() { *x = SetMicSeatLockRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[28] + mi := &file_proto_room_v1_room_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2044,7 +2186,7 @@ func (x *SetMicSeatLockRequest) String() string { func (*SetMicSeatLockRequest) ProtoMessage() {} func (x *SetMicSeatLockRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[28] + mi := &file_proto_room_v1_room_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2057,7 +2199,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{28} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{30} } func (x *SetMicSeatLockRequest) GetMeta() *RequestMeta { @@ -2093,7 +2235,7 @@ type SetMicSeatLockResponse struct { func (x *SetMicSeatLockResponse) Reset() { *x = SetMicSeatLockResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[29] + mi := &file_proto_room_v1_room_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2105,7 +2247,7 @@ func (x *SetMicSeatLockResponse) String() string { func (*SetMicSeatLockResponse) ProtoMessage() {} func (x *SetMicSeatLockResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[29] + mi := &file_proto_room_v1_room_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2118,7 +2260,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{29} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{31} } func (x *SetMicSeatLockResponse) GetResult() *CommandResult { @@ -2147,7 +2289,7 @@ type SetChatEnabledRequest struct { func (x *SetChatEnabledRequest) Reset() { *x = SetChatEnabledRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[30] + mi := &file_proto_room_v1_room_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2159,7 +2301,7 @@ func (x *SetChatEnabledRequest) String() string { func (*SetChatEnabledRequest) ProtoMessage() {} func (x *SetChatEnabledRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[30] + mi := &file_proto_room_v1_room_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2172,7 +2314,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{30} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{32} } func (x *SetChatEnabledRequest) GetMeta() *RequestMeta { @@ -2201,7 +2343,7 @@ type SetChatEnabledResponse struct { func (x *SetChatEnabledResponse) Reset() { *x = SetChatEnabledResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[31] + mi := &file_proto_room_v1_room_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2213,7 +2355,7 @@ func (x *SetChatEnabledResponse) String() string { func (*SetChatEnabledResponse) ProtoMessage() {} func (x *SetChatEnabledResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[31] + mi := &file_proto_room_v1_room_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2226,7 +2368,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{31} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{33} } func (x *SetChatEnabledResponse) GetResult() *CommandResult { @@ -2256,7 +2398,7 @@ type SetRoomAdminRequest struct { func (x *SetRoomAdminRequest) Reset() { *x = SetRoomAdminRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[32] + mi := &file_proto_room_v1_room_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2268,7 +2410,7 @@ func (x *SetRoomAdminRequest) String() string { func (*SetRoomAdminRequest) ProtoMessage() {} func (x *SetRoomAdminRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[32] + mi := &file_proto_room_v1_room_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2281,7 +2423,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{32} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{34} } func (x *SetRoomAdminRequest) GetMeta() *RequestMeta { @@ -2317,7 +2459,7 @@ type SetRoomAdminResponse struct { func (x *SetRoomAdminResponse) Reset() { *x = SetRoomAdminResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[33] + mi := &file_proto_room_v1_room_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2329,7 +2471,7 @@ func (x *SetRoomAdminResponse) String() string { func (*SetRoomAdminResponse) ProtoMessage() {} func (x *SetRoomAdminResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[33] + mi := &file_proto_room_v1_room_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2342,7 +2484,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{33} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{35} } func (x *SetRoomAdminResponse) GetResult() *CommandResult { @@ -2371,7 +2513,7 @@ type TransferRoomHostRequest struct { func (x *TransferRoomHostRequest) Reset() { *x = TransferRoomHostRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[34] + mi := &file_proto_room_v1_room_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2383,7 +2525,7 @@ func (x *TransferRoomHostRequest) String() string { func (*TransferRoomHostRequest) ProtoMessage() {} func (x *TransferRoomHostRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[34] + mi := &file_proto_room_v1_room_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2396,7 +2538,7 @@ func (x *TransferRoomHostRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use TransferRoomHostRequest.ProtoReflect.Descriptor instead. func (*TransferRoomHostRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{34} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{36} } func (x *TransferRoomHostRequest) GetMeta() *RequestMeta { @@ -2425,7 +2567,7 @@ type TransferRoomHostResponse struct { func (x *TransferRoomHostResponse) Reset() { *x = TransferRoomHostResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[35] + mi := &file_proto_room_v1_room_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2437,7 +2579,7 @@ func (x *TransferRoomHostResponse) String() string { func (*TransferRoomHostResponse) ProtoMessage() {} func (x *TransferRoomHostResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[35] + mi := &file_proto_room_v1_room_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2450,7 +2592,7 @@ func (x *TransferRoomHostResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use TransferRoomHostResponse.ProtoReflect.Descriptor instead. func (*TransferRoomHostResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{35} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{37} } func (x *TransferRoomHostResponse) GetResult() *CommandResult { @@ -2480,7 +2622,7 @@ type MuteUserRequest struct { func (x *MuteUserRequest) Reset() { *x = MuteUserRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[36] + mi := &file_proto_room_v1_room_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2492,7 +2634,7 @@ func (x *MuteUserRequest) String() string { func (*MuteUserRequest) ProtoMessage() {} func (x *MuteUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[36] + mi := &file_proto_room_v1_room_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2505,7 +2647,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{36} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{38} } func (x *MuteUserRequest) GetMeta() *RequestMeta { @@ -2541,7 +2683,7 @@ type MuteUserResponse struct { func (x *MuteUserResponse) Reset() { *x = MuteUserResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[37] + mi := &file_proto_room_v1_room_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2553,7 +2695,7 @@ func (x *MuteUserResponse) String() string { func (*MuteUserResponse) ProtoMessage() {} func (x *MuteUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[37] + mi := &file_proto_room_v1_room_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2566,7 +2708,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{37} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{39} } func (x *MuteUserResponse) GetResult() *CommandResult { @@ -2595,7 +2737,7 @@ type KickUserRequest struct { func (x *KickUserRequest) Reset() { *x = KickUserRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[38] + mi := &file_proto_room_v1_room_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2607,7 +2749,7 @@ func (x *KickUserRequest) String() string { func (*KickUserRequest) ProtoMessage() {} func (x *KickUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[38] + mi := &file_proto_room_v1_room_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2620,7 +2762,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{38} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{40} } func (x *KickUserRequest) GetMeta() *RequestMeta { @@ -2649,7 +2791,7 @@ type KickUserResponse struct { func (x *KickUserResponse) Reset() { *x = KickUserResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[39] + mi := &file_proto_room_v1_room_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2661,7 +2803,7 @@ func (x *KickUserResponse) String() string { func (*KickUserResponse) ProtoMessage() {} func (x *KickUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[39] + mi := &file_proto_room_v1_room_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2674,7 +2816,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{39} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{41} } func (x *KickUserResponse) GetResult() *CommandResult { @@ -2703,7 +2845,7 @@ type UnbanUserRequest struct { func (x *UnbanUserRequest) Reset() { *x = UnbanUserRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[40] + mi := &file_proto_room_v1_room_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2715,7 +2857,7 @@ func (x *UnbanUserRequest) String() string { func (*UnbanUserRequest) ProtoMessage() {} func (x *UnbanUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[40] + mi := &file_proto_room_v1_room_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2728,7 +2870,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{40} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{42} } func (x *UnbanUserRequest) GetMeta() *RequestMeta { @@ -2757,7 +2899,7 @@ type UnbanUserResponse struct { func (x *UnbanUserResponse) Reset() { *x = UnbanUserResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[41] + mi := &file_proto_room_v1_room_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2769,7 +2911,7 @@ func (x *UnbanUserResponse) String() string { func (*UnbanUserResponse) ProtoMessage() {} func (x *UnbanUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[41] + mi := &file_proto_room_v1_room_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2782,7 +2924,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{41} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{43} } func (x *UnbanUserResponse) GetResult() *CommandResult { @@ -2805,15 +2947,19 @@ type SendGiftRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` - GiftId string `protobuf:"bytes,3,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"` - GiftCount int32 `protobuf:"varint,4,opt,name=gift_count,json=giftCount,proto3" json:"gift_count,omitempty"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + // target_user_id 是兼容旧客户端的单目标字段;新客户端优先使用 target_user_ids。 + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + GiftId string `protobuf:"bytes,3,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"` + GiftCount int32 `protobuf:"varint,4,opt,name=gift_count,json=giftCount,proto3" json:"gift_count,omitempty"` + // target_type 当前只提交 user;后续扩展 all_mic、all_room、couple 时不再改 HTTP 契约。 + TargetType string `protobuf:"bytes,5,opt,name=target_type,json=targetType,proto3" json:"target_type,omitempty"` + TargetUserIds []int64 `protobuf:"varint,6,rep,packed,name=target_user_ids,json=targetUserIds,proto3" json:"target_user_ids,omitempty"` } func (x *SendGiftRequest) Reset() { *x = SendGiftRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[42] + mi := &file_proto_room_v1_room_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2825,7 +2971,7 @@ func (x *SendGiftRequest) String() string { func (*SendGiftRequest) ProtoMessage() {} func (x *SendGiftRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[42] + mi := &file_proto_room_v1_room_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2838,7 +2984,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{42} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{44} } func (x *SendGiftRequest) GetMeta() *RequestMeta { @@ -2869,6 +3015,20 @@ func (x *SendGiftRequest) GetGiftCount() int32 { return 0 } +func (x *SendGiftRequest) GetTargetType() string { + if x != nil { + return x.TargetType + } + return "" +} + +func (x *SendGiftRequest) GetTargetUserIds() []int64 { + if x != nil { + return x.TargetUserIds + } + return nil +} + // SendGiftResponse 返回扣费成功并落到房间后的状态结果。 type SendGiftResponse struct { state protoimpl.MessageState @@ -2884,7 +3044,7 @@ type SendGiftResponse struct { func (x *SendGiftResponse) Reset() { *x = SendGiftResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[43] + mi := &file_proto_room_v1_room_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2896,7 +3056,7 @@ func (x *SendGiftResponse) String() string { func (*SendGiftResponse) ProtoMessage() {} func (x *SendGiftResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[43] + mi := &file_proto_room_v1_room_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2909,7 +3069,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{43} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{45} } func (x *SendGiftResponse) GetResult() *CommandResult { @@ -2960,7 +3120,7 @@ type CheckSpeakPermissionRequest struct { func (x *CheckSpeakPermissionRequest) Reset() { *x = CheckSpeakPermissionRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[44] + mi := &file_proto_room_v1_room_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2972,7 +3132,7 @@ func (x *CheckSpeakPermissionRequest) String() string { func (*CheckSpeakPermissionRequest) ProtoMessage() {} func (x *CheckSpeakPermissionRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[44] + mi := &file_proto_room_v1_room_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2985,7 +3145,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{44} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{46} } func (x *CheckSpeakPermissionRequest) GetRoomId() string { @@ -3022,7 +3182,7 @@ type CheckSpeakPermissionResponse struct { func (x *CheckSpeakPermissionResponse) Reset() { *x = CheckSpeakPermissionResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[45] + mi := &file_proto_room_v1_room_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3034,7 +3194,7 @@ func (x *CheckSpeakPermissionResponse) String() string { func (*CheckSpeakPermissionResponse) ProtoMessage() {} func (x *CheckSpeakPermissionResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[45] + mi := &file_proto_room_v1_room_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3047,7 +3207,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{45} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{47} } func (x *CheckSpeakPermissionResponse) GetAllowed() bool { @@ -3086,7 +3246,7 @@ type VerifyRoomPresenceRequest struct { func (x *VerifyRoomPresenceRequest) Reset() { *x = VerifyRoomPresenceRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[46] + mi := &file_proto_room_v1_room_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3098,7 +3258,7 @@ func (x *VerifyRoomPresenceRequest) String() string { func (*VerifyRoomPresenceRequest) ProtoMessage() {} func (x *VerifyRoomPresenceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[46] + mi := &file_proto_room_v1_room_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3111,7 +3271,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{46} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{48} } func (x *VerifyRoomPresenceRequest) GetRoomId() string { @@ -3162,7 +3322,7 @@ type VerifyRoomPresenceResponse struct { func (x *VerifyRoomPresenceResponse) Reset() { *x = VerifyRoomPresenceResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[47] + mi := &file_proto_room_v1_room_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3174,7 +3334,7 @@ func (x *VerifyRoomPresenceResponse) String() string { func (*VerifyRoomPresenceResponse) ProtoMessage() {} func (x *VerifyRoomPresenceResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[47] + mi := &file_proto_room_v1_room_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3187,7 +3347,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{47} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{49} } func (x *VerifyRoomPresenceResponse) GetPresent() bool { @@ -3229,7 +3389,7 @@ type ListRoomsRequest struct { func (x *ListRoomsRequest) Reset() { *x = ListRoomsRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[48] + mi := &file_proto_room_v1_room_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3241,7 +3401,7 @@ func (x *ListRoomsRequest) String() string { func (*ListRoomsRequest) ProtoMessage() {} func (x *ListRoomsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[48] + mi := &file_proto_room_v1_room_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3254,7 +3414,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{48} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{50} } func (x *ListRoomsRequest) GetMeta() *RequestMeta { @@ -3326,7 +3486,7 @@ type ListRoomFeedsRequest struct { func (x *ListRoomFeedsRequest) Reset() { *x = ListRoomFeedsRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[49] + mi := &file_proto_room_v1_room_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3338,7 +3498,7 @@ func (x *ListRoomFeedsRequest) String() string { func (*ListRoomFeedsRequest) ProtoMessage() {} func (x *ListRoomFeedsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[49] + mi := &file_proto_room_v1_room_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3351,7 +3511,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{49} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{51} } func (x *ListRoomFeedsRequest) GetMeta() *RequestMeta { @@ -3422,7 +3582,7 @@ type RoomFeedRelatedUser struct { func (x *RoomFeedRelatedUser) Reset() { *x = RoomFeedRelatedUser{} - mi := &file_proto_room_v1_room_proto_msgTypes[50] + mi := &file_proto_room_v1_room_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3434,7 +3594,7 @@ func (x *RoomFeedRelatedUser) String() string { func (*RoomFeedRelatedUser) ProtoMessage() {} func (x *RoomFeedRelatedUser) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[50] + mi := &file_proto_room_v1_room_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3447,7 +3607,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{50} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{52} } func (x *RoomFeedRelatedUser) GetUserId() int64 { @@ -3488,7 +3648,7 @@ type RoomListItem struct { func (x *RoomListItem) Reset() { *x = RoomListItem{} - mi := &file_proto_room_v1_room_proto_msgTypes[51] + mi := &file_proto_room_v1_room_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3500,7 +3660,7 @@ func (x *RoomListItem) String() string { func (*RoomListItem) ProtoMessage() {} func (x *RoomListItem) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[51] + mi := &file_proto_room_v1_room_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3513,7 +3673,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{51} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{53} } func (x *RoomListItem) GetRoomId() string { @@ -3626,7 +3786,7 @@ type ListRoomsResponse struct { func (x *ListRoomsResponse) Reset() { *x = ListRoomsResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[52] + mi := &file_proto_room_v1_room_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3638,7 +3798,7 @@ func (x *ListRoomsResponse) String() string { func (*ListRoomsResponse) ProtoMessage() {} func (x *ListRoomsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[52] + mi := &file_proto_room_v1_room_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3651,7 +3811,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{52} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{54} } func (x *ListRoomsResponse) GetRooms() []*RoomListItem { @@ -3681,7 +3841,7 @@ type GetMyRoomRequest struct { func (x *GetMyRoomRequest) Reset() { *x = GetMyRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[53] + mi := &file_proto_room_v1_room_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3693,7 +3853,7 @@ func (x *GetMyRoomRequest) String() string { func (*GetMyRoomRequest) ProtoMessage() {} func (x *GetMyRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[53] + mi := &file_proto_room_v1_room_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3706,7 +3866,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{53} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{55} } func (x *GetMyRoomRequest) GetMeta() *RequestMeta { @@ -3737,7 +3897,7 @@ type GetMyRoomResponse struct { func (x *GetMyRoomResponse) Reset() { *x = GetMyRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[54] + mi := &file_proto_room_v1_room_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3749,7 +3909,7 @@ func (x *GetMyRoomResponse) String() string { func (*GetMyRoomResponse) ProtoMessage() {} func (x *GetMyRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[54] + mi := &file_proto_room_v1_room_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3762,7 +3922,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{54} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{56} } func (x *GetMyRoomResponse) GetHasRoom() bool { @@ -3799,7 +3959,7 @@ type GetCurrentRoomRequest struct { func (x *GetCurrentRoomRequest) Reset() { *x = GetCurrentRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[55] + mi := &file_proto_room_v1_room_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3811,7 +3971,7 @@ func (x *GetCurrentRoomRequest) String() string { func (*GetCurrentRoomRequest) ProtoMessage() {} func (x *GetCurrentRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[55] + mi := &file_proto_room_v1_room_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3824,7 +3984,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{55} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{57} } func (x *GetCurrentRoomRequest) GetMeta() *RequestMeta { @@ -3861,7 +4021,7 @@ type GetCurrentRoomResponse struct { func (x *GetCurrentRoomResponse) Reset() { *x = GetCurrentRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[56] + mi := &file_proto_room_v1_room_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3873,7 +4033,7 @@ func (x *GetCurrentRoomResponse) String() string { func (*GetCurrentRoomResponse) ProtoMessage() {} func (x *GetCurrentRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[56] + mi := &file_proto_room_v1_room_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3886,7 +4046,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{56} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{58} } func (x *GetCurrentRoomResponse) GetHasCurrentRoom() bool { @@ -3966,7 +4126,7 @@ type GetRoomSnapshotRequest struct { func (x *GetRoomSnapshotRequest) Reset() { *x = GetRoomSnapshotRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[57] + mi := &file_proto_room_v1_room_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3978,7 +4138,7 @@ func (x *GetRoomSnapshotRequest) String() string { func (*GetRoomSnapshotRequest) ProtoMessage() {} func (x *GetRoomSnapshotRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[57] + mi := &file_proto_room_v1_room_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3991,7 +4151,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{57} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{59} } func (x *GetRoomSnapshotRequest) GetMeta() *RequestMeta { @@ -4027,7 +4187,7 @@ type GetRoomSnapshotResponse struct { func (x *GetRoomSnapshotResponse) Reset() { *x = GetRoomSnapshotResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[58] + mi := &file_proto_room_v1_room_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4039,7 +4199,7 @@ func (x *GetRoomSnapshotResponse) String() string { func (*GetRoomSnapshotResponse) ProtoMessage() {} func (x *GetRoomSnapshotResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[58] + mi := &file_proto_room_v1_room_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4052,7 +4212,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{58} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{60} } func (x *GetRoomSnapshotResponse) GetRoom() *RoomSnapshot { @@ -4069,6 +4229,161 @@ func (x *GetRoomSnapshotResponse) GetServerTimeMs() int64 { return 0 } +// ListRoomOnlineUsersRequest 分页查询房间在线用户,不让客户端拉完整 RoomSnapshot 做列表分页。 +type ListRoomOnlineUsersRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + ViewerUserId int64 `protobuf:"varint,3,opt,name=viewer_user_id,json=viewerUserId,proto3" json:"viewer_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"` +} + +func (x *ListRoomOnlineUsersRequest) Reset() { + *x = ListRoomOnlineUsersRequest{} + mi := &file_proto_room_v1_room_proto_msgTypes[61] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRoomOnlineUsersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRoomOnlineUsersRequest) ProtoMessage() {} + +func (x *ListRoomOnlineUsersRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_room_v1_room_proto_msgTypes[61] + 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 ListRoomOnlineUsersRequest.ProtoReflect.Descriptor instead. +func (*ListRoomOnlineUsersRequest) Descriptor() ([]byte, []int) { + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{61} +} + +func (x *ListRoomOnlineUsersRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *ListRoomOnlineUsersRequest) GetRoomId() string { + if x != nil { + return x.RoomId + } + return "" +} + +func (x *ListRoomOnlineUsersRequest) GetViewerUserId() int64 { + if x != nil { + return x.ViewerUserId + } + return 0 +} + +func (x *ListRoomOnlineUsersRequest) GetPage() int32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListRoomOnlineUsersRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +type ListRoomOnlineUsersResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Users []*RoomUser `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"` + Total int64 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + Page int32 `protobuf:"varint,3,opt,name=page,proto3" json:"page,omitempty"` + PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + ServerTimeMs int64 `protobuf:"varint,5,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` +} + +func (x *ListRoomOnlineUsersResponse) Reset() { + *x = ListRoomOnlineUsersResponse{} + mi := &file_proto_room_v1_room_proto_msgTypes[62] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRoomOnlineUsersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRoomOnlineUsersResponse) ProtoMessage() {} + +func (x *ListRoomOnlineUsersResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_room_v1_room_proto_msgTypes[62] + 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 ListRoomOnlineUsersResponse.ProtoReflect.Descriptor instead. +func (*ListRoomOnlineUsersResponse) Descriptor() ([]byte, []int) { + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{62} +} + +func (x *ListRoomOnlineUsersResponse) GetUsers() []*RoomUser { + if x != nil { + return x.Users + } + return nil +} + +func (x *ListRoomOnlineUsersResponse) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *ListRoomOnlineUsersResponse) GetPage() int32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListRoomOnlineUsersResponse) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListRoomOnlineUsersResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + var File_proto_room_v1_room_proto protoreflect.FileDescriptor var file_proto_room_v1_room_proto_rawDesc = []byte{ @@ -4106,7 +4421,7 @@ var file_proto_room_v1_room_proto_rawDesc = []byte{ 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x25, 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, - 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xc5, 0x02, 0x0a, 0x09, + 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xe2, 0x02, 0x0a, 0x09, 0x53, 0x65, 0x61, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, @@ -4127,724 +4442,787 @@ var file_proto_room_v1_room_proto_rawDesc = []byte{ 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x16, 0x6c, 0x61, 0x73, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, - 0x65, 0x4d, 0x73, 0x22, 0x7c, 0x0a, 0x08, 0x52, 0x61, 0x6e, 0x6b, 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, 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, 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, 0xbf, 0x05, 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, - 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, 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, 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, 0xaf, 0x02, 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, 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, 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, 0x55, 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, 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, + 0x65, 0x4d, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x63, 0x5f, 0x6d, 0x75, 0x74, 0x65, 0x64, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6d, 0x69, 0x63, 0x4d, 0x75, 0x74, 0x65, 0x64, + 0x22, 0x7c, 0x0a, 0x08, 0x52, 0x61, 0x6e, 0x6b, 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, 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, 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, 0xeb, + 0x05, 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, 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, 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, + 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, 0xaf, 0x02, 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, 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, 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, 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, 0x57, 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, 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, + 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, 0x55, 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, 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, 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, 0x7e, 0x0a, 0x0e, 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, + 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, 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, 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, 0x85, 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, 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, 0xf8, 0x01, - 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, 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, 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, + 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, 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, 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, + 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, 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, 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, 0x6f, 0x0a, 0x17, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x66, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 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, 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, 0x81, 0x01, 0x0a, 0x18, 0x54, 0x72, 0x61, - 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x6f, 0x73, 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, 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, + 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, 0x57, 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, 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, 0x7e, 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, 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, 0x85, + 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, 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, 0x67, 0x0a, 0x0f, 0x4b, 0x69, 0x63, 0x6b, 0x55, 0x73, - 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, + 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0xf8, 0x01, 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, 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, 0x7f, 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, 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, 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, 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, 0x22, - 0x79, 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, 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, 0x9f, 0x01, 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, 0x22, 0xfa, 0x01, 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, 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, 0xea, 0x01, 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, 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, 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, 0xbd, 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, 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, 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, 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, 0x66, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, + 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, 0x6f, 0x0a, 0x17, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x66, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 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, 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, 0x81, 0x01, 0x0a, 0x18, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x6f, 0x73, 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, 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, 0x67, 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, + 0x22, 0x79, 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, 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, 0xe8, 0x01, 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, 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, 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, 0x70, 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, 0x32, 0x82, 0x0d, 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, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x66, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, - 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, - 0x6f, 0x6d, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 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, 0x4b, 0x0a, 0x08, 0x4a, 0x6f, - 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, - 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, - 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x52, 0x6f, 0x6f, 0x6d, 0x48, - 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, - 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, + 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, 0x22, 0xfa, 0x01, 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, 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, 0xea, 0x01, 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, 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, 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, 0xbd, 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, 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, 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, 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, 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, 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, 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, 0x70, 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, 0x22, 0xbc, 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, 0x22, 0xb9, 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, 0x32, 0xd5, 0x0d, + 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, 0x6f, 0x6f, 0x6d, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x27, 0x2e, 0x68, + 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, + 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 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, + 0x4b, 0x0a, 0x08, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x1e, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4a, 0x6f, 0x69, 0x6e, + 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4a, 0x6f, 0x69, 0x6e, + 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, + 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, - 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x52, 0x6f, 0x6f, 0x6d, - 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x65, 0x61, 0x76, 0x65, 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, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x6d, - 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 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, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x05, 0x4d, 0x69, 0x63, 0x55, 0x70, 0x12, 0x1b, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x63, - 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x63, 0x55, 0x70, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x07, 0x4d, 0x69, 0x63, 0x44, 0x6f, - 0x77, 0x6e, 0x12, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, - 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, - 0x31, 0x2e, 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x69, 0x63, 0x53, 0x65, - 0x61, 0x74, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 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, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x4c, 0x65, 0x61, 0x76, + 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, + 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x65, 0x61, 0x76, 0x65, 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, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x52, 0x6f, 0x6f, 0x6d, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x43, 0x6c, 0x6f, 0x73, + 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, + 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 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, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x6d, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x05, 0x4d, 0x69, 0x63, 0x55, + 0x70, 0x12, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x4d, 0x69, 0x63, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, + 0x69, 0x63, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x07, + 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, + 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x69, - 0x63, 0x53, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, - 0x14, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x4d, 0x69, 0x63, 0x50, 0x75, 0x62, 0x6c, 0x69, - 0x73, 0x68, 0x69, 0x6e, 0x67, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 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, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, - 0x31, 0x2e, 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, 0x5a, - 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x54, 0x43, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x54, 0x43, 0x45, 0x76, 0x65, 0x6e, 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, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x54, 0x43, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x53, 0x65, - 0x74, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x4c, 0x6f, 0x63, 0x6b, 0x12, 0x24, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, - 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x4c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x4c, 0x6f, 0x63, - 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x53, 0x65, 0x74, - 0x43, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x24, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, - 0x68, 0x61, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x53, 0x65, 0x74, 0x52, - 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, - 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, - 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x63, 0x0a, 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x6f, 0x6f, - 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x6f, - 0x6f, 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, + 0x63, 0x53, 0x65, 0x61, 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, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x4d, 0x69, 0x63, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 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, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, + 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 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, 0x51, 0x0a, 0x0a, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x63, 0x4d, 0x75, 0x74, + 0x65, 0x12, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x63, 0x4d, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x63, 0x4d, 0x75, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, + 0x54, 0x43, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x54, 0x43, + 0x45, 0x76, 0x65, 0x6e, 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, 0x41, 0x70, 0x70, + 0x6c, 0x79, 0x52, 0x54, 0x43, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, + 0x4c, 0x6f, 0x63, 0x6b, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x4c, + 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, + 0x63, 0x53, 0x65, 0x61, 0x74, 0x4c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x53, 0x65, 0x74, 0x43, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x68, 0x61, + 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x57, 0x0a, 0x0c, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x69, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x10, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x4d, 0x75, 0x74, 0x65, 0x55, 0x73, - 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, - 0x76, 0x31, 0x2e, 0x4d, 0x75, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, - 0x76, 0x31, 0x2e, 0x4d, 0x75, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x4b, 0x69, 0x63, 0x6b, 0x55, 0x73, 0x65, 0x72, 0x12, - 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x4b, 0x69, 0x63, 0x6b, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x4b, 0x69, 0x63, 0x6b, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4e, 0x0a, 0x09, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1f, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, - 0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x6e, 0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4b, 0x0a, 0x08, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x12, 0x1e, 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, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xee, 0x01, - 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, 0x2e, 0x76, 0x31, 0x2e, 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, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, - 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 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, 0x69, 0x0a, 0x12, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x6f, 0x6f, - 0x6d, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x28, 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, 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, 0xcb, - 0x03, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 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, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, + 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x6f, + 0x6f, 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, + 0x0a, 0x08, 0x4d, 0x75, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x75, 0x74, 0x65, 0x55, + 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x75, 0x74, 0x65, 0x55, + 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x4b, + 0x69, 0x63, 0x6b, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x69, 0x63, 0x6b, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, + 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x69, 0x63, 0x6b, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x55, 0x6e, 0x62, 0x61, + 0x6e, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, + 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, + 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x53, 0x65, 0x6e, 0x64, + 0x47, 0x69, 0x66, 0x74, 0x12, 0x1e, 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, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 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, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xee, 0x01, 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, 0x2e, + 0x76, 0x31, 0x2e, 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, 0x1a, 0x2b, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 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, 0x69, 0x0a, 0x12, 0x56, + 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, + 0x65, 0x12, 0x28, 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, 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, 0xb9, 0x04, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 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, 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, 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, 0x73, 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, 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, + 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, + 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, 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, 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 ( @@ -4859,7 +5237,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, 60) +var file_proto_room_v1_room_proto_msgTypes = make([]protoimpl.MessageInfo, 64) var file_proto_room_v1_room_proto_goTypes = []any{ (*RequestMeta)(nil), // 0: hyapp.room.v1.RequestMeta (*CommandResult)(nil), // 1: hyapp.room.v1.CommandResult @@ -4887,172 +5265,185 @@ var file_proto_room_v1_room_proto_goTypes = []any{ (*ChangeMicSeatResponse)(nil), // 23: hyapp.room.v1.ChangeMicSeatResponse (*ConfirmMicPublishingRequest)(nil), // 24: hyapp.room.v1.ConfirmMicPublishingRequest (*ConfirmMicPublishingResponse)(nil), // 25: hyapp.room.v1.ConfirmMicPublishingResponse - (*ApplyRTCEventRequest)(nil), // 26: hyapp.room.v1.ApplyRTCEventRequest - (*ApplyRTCEventResponse)(nil), // 27: hyapp.room.v1.ApplyRTCEventResponse - (*SetMicSeatLockRequest)(nil), // 28: hyapp.room.v1.SetMicSeatLockRequest - (*SetMicSeatLockResponse)(nil), // 29: hyapp.room.v1.SetMicSeatLockResponse - (*SetChatEnabledRequest)(nil), // 30: hyapp.room.v1.SetChatEnabledRequest - (*SetChatEnabledResponse)(nil), // 31: hyapp.room.v1.SetChatEnabledResponse - (*SetRoomAdminRequest)(nil), // 32: hyapp.room.v1.SetRoomAdminRequest - (*SetRoomAdminResponse)(nil), // 33: hyapp.room.v1.SetRoomAdminResponse - (*TransferRoomHostRequest)(nil), // 34: hyapp.room.v1.TransferRoomHostRequest - (*TransferRoomHostResponse)(nil), // 35: hyapp.room.v1.TransferRoomHostResponse - (*MuteUserRequest)(nil), // 36: hyapp.room.v1.MuteUserRequest - (*MuteUserResponse)(nil), // 37: hyapp.room.v1.MuteUserResponse - (*KickUserRequest)(nil), // 38: hyapp.room.v1.KickUserRequest - (*KickUserResponse)(nil), // 39: hyapp.room.v1.KickUserResponse - (*UnbanUserRequest)(nil), // 40: hyapp.room.v1.UnbanUserRequest - (*UnbanUserResponse)(nil), // 41: hyapp.room.v1.UnbanUserResponse - (*SendGiftRequest)(nil), // 42: hyapp.room.v1.SendGiftRequest - (*SendGiftResponse)(nil), // 43: hyapp.room.v1.SendGiftResponse - (*CheckSpeakPermissionRequest)(nil), // 44: hyapp.room.v1.CheckSpeakPermissionRequest - (*CheckSpeakPermissionResponse)(nil), // 45: hyapp.room.v1.CheckSpeakPermissionResponse - (*VerifyRoomPresenceRequest)(nil), // 46: hyapp.room.v1.VerifyRoomPresenceRequest - (*VerifyRoomPresenceResponse)(nil), // 47: hyapp.room.v1.VerifyRoomPresenceResponse - (*ListRoomsRequest)(nil), // 48: hyapp.room.v1.ListRoomsRequest - (*ListRoomFeedsRequest)(nil), // 49: hyapp.room.v1.ListRoomFeedsRequest - (*RoomFeedRelatedUser)(nil), // 50: hyapp.room.v1.RoomFeedRelatedUser - (*RoomListItem)(nil), // 51: hyapp.room.v1.RoomListItem - (*ListRoomsResponse)(nil), // 52: hyapp.room.v1.ListRoomsResponse - (*GetMyRoomRequest)(nil), // 53: hyapp.room.v1.GetMyRoomRequest - (*GetMyRoomResponse)(nil), // 54: hyapp.room.v1.GetMyRoomResponse - (*GetCurrentRoomRequest)(nil), // 55: hyapp.room.v1.GetCurrentRoomRequest - (*GetCurrentRoomResponse)(nil), // 56: hyapp.room.v1.GetCurrentRoomResponse - (*GetRoomSnapshotRequest)(nil), // 57: hyapp.room.v1.GetRoomSnapshotRequest - (*GetRoomSnapshotResponse)(nil), // 58: hyapp.room.v1.GetRoomSnapshotResponse - nil, // 59: hyapp.room.v1.RoomSnapshot.RoomExtEntry + (*SetMicMuteRequest)(nil), // 26: hyapp.room.v1.SetMicMuteRequest + (*SetMicMuteResponse)(nil), // 27: hyapp.room.v1.SetMicMuteResponse + (*ApplyRTCEventRequest)(nil), // 28: hyapp.room.v1.ApplyRTCEventRequest + (*ApplyRTCEventResponse)(nil), // 29: hyapp.room.v1.ApplyRTCEventResponse + (*SetMicSeatLockRequest)(nil), // 30: hyapp.room.v1.SetMicSeatLockRequest + (*SetMicSeatLockResponse)(nil), // 31: hyapp.room.v1.SetMicSeatLockResponse + (*SetChatEnabledRequest)(nil), // 32: hyapp.room.v1.SetChatEnabledRequest + (*SetChatEnabledResponse)(nil), // 33: hyapp.room.v1.SetChatEnabledResponse + (*SetRoomAdminRequest)(nil), // 34: hyapp.room.v1.SetRoomAdminRequest + (*SetRoomAdminResponse)(nil), // 35: hyapp.room.v1.SetRoomAdminResponse + (*TransferRoomHostRequest)(nil), // 36: hyapp.room.v1.TransferRoomHostRequest + (*TransferRoomHostResponse)(nil), // 37: hyapp.room.v1.TransferRoomHostResponse + (*MuteUserRequest)(nil), // 38: hyapp.room.v1.MuteUserRequest + (*MuteUserResponse)(nil), // 39: hyapp.room.v1.MuteUserResponse + (*KickUserRequest)(nil), // 40: hyapp.room.v1.KickUserRequest + (*KickUserResponse)(nil), // 41: hyapp.room.v1.KickUserResponse + (*UnbanUserRequest)(nil), // 42: hyapp.room.v1.UnbanUserRequest + (*UnbanUserResponse)(nil), // 43: hyapp.room.v1.UnbanUserResponse + (*SendGiftRequest)(nil), // 44: hyapp.room.v1.SendGiftRequest + (*SendGiftResponse)(nil), // 45: hyapp.room.v1.SendGiftResponse + (*CheckSpeakPermissionRequest)(nil), // 46: hyapp.room.v1.CheckSpeakPermissionRequest + (*CheckSpeakPermissionResponse)(nil), // 47: hyapp.room.v1.CheckSpeakPermissionResponse + (*VerifyRoomPresenceRequest)(nil), // 48: hyapp.room.v1.VerifyRoomPresenceRequest + (*VerifyRoomPresenceResponse)(nil), // 49: hyapp.room.v1.VerifyRoomPresenceResponse + (*ListRoomsRequest)(nil), // 50: hyapp.room.v1.ListRoomsRequest + (*ListRoomFeedsRequest)(nil), // 51: hyapp.room.v1.ListRoomFeedsRequest + (*RoomFeedRelatedUser)(nil), // 52: hyapp.room.v1.RoomFeedRelatedUser + (*RoomListItem)(nil), // 53: hyapp.room.v1.RoomListItem + (*ListRoomsResponse)(nil), // 54: hyapp.room.v1.ListRoomsResponse + (*GetMyRoomRequest)(nil), // 55: hyapp.room.v1.GetMyRoomRequest + (*GetMyRoomResponse)(nil), // 56: hyapp.room.v1.GetMyRoomResponse + (*GetCurrentRoomRequest)(nil), // 57: hyapp.room.v1.GetCurrentRoomRequest + (*GetCurrentRoomResponse)(nil), // 58: hyapp.room.v1.GetCurrentRoomResponse + (*GetRoomSnapshotRequest)(nil), // 59: hyapp.room.v1.GetRoomSnapshotRequest + (*GetRoomSnapshotResponse)(nil), // 60: hyapp.room.v1.GetRoomSnapshotResponse + (*ListRoomOnlineUsersRequest)(nil), // 61: hyapp.room.v1.ListRoomOnlineUsersRequest + (*ListRoomOnlineUsersResponse)(nil), // 62: hyapp.room.v1.ListRoomOnlineUsersResponse + nil, // 63: hyapp.room.v1.RoomSnapshot.RoomExtEntry } var file_proto_room_v1_room_proto_depIdxs = []int32{ - 3, // 0: hyapp.room.v1.RoomSnapshot.mic_seats:type_name -> hyapp.room.v1.SeatState - 2, // 1: hyapp.room.v1.RoomSnapshot.online_users:type_name -> hyapp.room.v1.RoomUser - 4, // 2: hyapp.room.v1.RoomSnapshot.gift_rank:type_name -> hyapp.room.v1.RankItem - 59, // 3: hyapp.room.v1.RoomSnapshot.room_ext:type_name -> hyapp.room.v1.RoomSnapshot.RoomExtEntry - 0, // 4: hyapp.room.v1.CreateRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 5: hyapp.room.v1.CreateRoomResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 6: hyapp.room.v1.CreateRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 7: hyapp.room.v1.UpdateRoomProfileRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 8: hyapp.room.v1.UpdateRoomProfileResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 9: hyapp.room.v1.UpdateRoomProfileResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 10: hyapp.room.v1.JoinRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 11: hyapp.room.v1.JoinRoomResponse.result:type_name -> hyapp.room.v1.CommandResult - 2, // 12: hyapp.room.v1.JoinRoomResponse.user:type_name -> hyapp.room.v1.RoomUser - 5, // 13: hyapp.room.v1.JoinRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 14: hyapp.room.v1.RoomHeartbeatRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 15: hyapp.room.v1.RoomHeartbeatResponse.result:type_name -> hyapp.room.v1.CommandResult - 2, // 16: hyapp.room.v1.RoomHeartbeatResponse.user:type_name -> hyapp.room.v1.RoomUser - 5, // 17: hyapp.room.v1.RoomHeartbeatResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 18: hyapp.room.v1.LeaveRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 19: hyapp.room.v1.LeaveRoomResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 20: hyapp.room.v1.LeaveRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 21: hyapp.room.v1.CloseRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 22: hyapp.room.v1.CloseRoomResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 23: hyapp.room.v1.CloseRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 24: hyapp.room.v1.MicUpRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 25: hyapp.room.v1.MicUpResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 26: hyapp.room.v1.MicUpResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 27: hyapp.room.v1.MicDownRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 28: hyapp.room.v1.MicDownResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 29: hyapp.room.v1.MicDownResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 30: hyapp.room.v1.ChangeMicSeatRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 31: hyapp.room.v1.ChangeMicSeatResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 32: hyapp.room.v1.ChangeMicSeatResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 33: hyapp.room.v1.ConfirmMicPublishingRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 34: hyapp.room.v1.ConfirmMicPublishingResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 35: hyapp.room.v1.ConfirmMicPublishingResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 36: hyapp.room.v1.ApplyRTCEventRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 37: hyapp.room.v1.ApplyRTCEventResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 38: hyapp.room.v1.ApplyRTCEventResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 39: hyapp.room.v1.SetMicSeatLockRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 40: hyapp.room.v1.SetMicSeatLockResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 41: hyapp.room.v1.SetMicSeatLockResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 42: hyapp.room.v1.SetChatEnabledRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 43: hyapp.room.v1.SetChatEnabledResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 44: hyapp.room.v1.SetChatEnabledResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 45: hyapp.room.v1.SetRoomAdminRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 46: hyapp.room.v1.SetRoomAdminResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 47: hyapp.room.v1.SetRoomAdminResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 48: hyapp.room.v1.TransferRoomHostRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 49: hyapp.room.v1.TransferRoomHostResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 50: hyapp.room.v1.TransferRoomHostResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 51: hyapp.room.v1.MuteUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 52: hyapp.room.v1.MuteUserResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 53: hyapp.room.v1.MuteUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 54: hyapp.room.v1.KickUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 55: hyapp.room.v1.KickUserResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 56: hyapp.room.v1.KickUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 57: hyapp.room.v1.UnbanUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 58: hyapp.room.v1.UnbanUserResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 59: hyapp.room.v1.UnbanUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 60: hyapp.room.v1.SendGiftRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 61: hyapp.room.v1.SendGiftResponse.result:type_name -> hyapp.room.v1.CommandResult - 4, // 62: hyapp.room.v1.SendGiftResponse.gift_rank:type_name -> hyapp.room.v1.RankItem - 5, // 63: hyapp.room.v1.SendGiftResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 64: hyapp.room.v1.ListRoomsRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 0, // 65: hyapp.room.v1.ListRoomFeedsRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 50, // 66: hyapp.room.v1.ListRoomFeedsRequest.related_users:type_name -> hyapp.room.v1.RoomFeedRelatedUser - 51, // 67: hyapp.room.v1.ListRoomsResponse.rooms:type_name -> hyapp.room.v1.RoomListItem - 0, // 68: hyapp.room.v1.GetMyRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 51, // 69: hyapp.room.v1.GetMyRoomResponse.room:type_name -> hyapp.room.v1.RoomListItem - 0, // 70: hyapp.room.v1.GetCurrentRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 0, // 71: hyapp.room.v1.GetRoomSnapshotRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 5, // 72: hyapp.room.v1.GetRoomSnapshotResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 6, // 73: hyapp.room.v1.RoomCommandService.CreateRoom:input_type -> hyapp.room.v1.CreateRoomRequest - 8, // 74: hyapp.room.v1.RoomCommandService.UpdateRoomProfile:input_type -> hyapp.room.v1.UpdateRoomProfileRequest - 10, // 75: hyapp.room.v1.RoomCommandService.JoinRoom:input_type -> hyapp.room.v1.JoinRoomRequest - 12, // 76: hyapp.room.v1.RoomCommandService.RoomHeartbeat:input_type -> hyapp.room.v1.RoomHeartbeatRequest - 14, // 77: hyapp.room.v1.RoomCommandService.LeaveRoom:input_type -> hyapp.room.v1.LeaveRoomRequest - 16, // 78: hyapp.room.v1.RoomCommandService.CloseRoom:input_type -> hyapp.room.v1.CloseRoomRequest - 18, // 79: hyapp.room.v1.RoomCommandService.MicUp:input_type -> hyapp.room.v1.MicUpRequest - 20, // 80: hyapp.room.v1.RoomCommandService.MicDown:input_type -> hyapp.room.v1.MicDownRequest - 22, // 81: hyapp.room.v1.RoomCommandService.ChangeMicSeat:input_type -> hyapp.room.v1.ChangeMicSeatRequest - 24, // 82: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:input_type -> hyapp.room.v1.ConfirmMicPublishingRequest - 26, // 83: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:input_type -> hyapp.room.v1.ApplyRTCEventRequest - 28, // 84: hyapp.room.v1.RoomCommandService.SetMicSeatLock:input_type -> hyapp.room.v1.SetMicSeatLockRequest - 30, // 85: hyapp.room.v1.RoomCommandService.SetChatEnabled:input_type -> hyapp.room.v1.SetChatEnabledRequest - 32, // 86: hyapp.room.v1.RoomCommandService.SetRoomAdmin:input_type -> hyapp.room.v1.SetRoomAdminRequest - 34, // 87: hyapp.room.v1.RoomCommandService.TransferRoomHost:input_type -> hyapp.room.v1.TransferRoomHostRequest - 36, // 88: hyapp.room.v1.RoomCommandService.MuteUser:input_type -> hyapp.room.v1.MuteUserRequest - 38, // 89: hyapp.room.v1.RoomCommandService.KickUser:input_type -> hyapp.room.v1.KickUserRequest - 40, // 90: hyapp.room.v1.RoomCommandService.UnbanUser:input_type -> hyapp.room.v1.UnbanUserRequest - 42, // 91: hyapp.room.v1.RoomCommandService.SendGift:input_type -> hyapp.room.v1.SendGiftRequest - 44, // 92: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:input_type -> hyapp.room.v1.CheckSpeakPermissionRequest - 46, // 93: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:input_type -> hyapp.room.v1.VerifyRoomPresenceRequest - 48, // 94: hyapp.room.v1.RoomQueryService.ListRooms:input_type -> hyapp.room.v1.ListRoomsRequest - 49, // 95: hyapp.room.v1.RoomQueryService.ListRoomFeeds:input_type -> hyapp.room.v1.ListRoomFeedsRequest - 53, // 96: hyapp.room.v1.RoomQueryService.GetMyRoom:input_type -> hyapp.room.v1.GetMyRoomRequest - 55, // 97: hyapp.room.v1.RoomQueryService.GetCurrentRoom:input_type -> hyapp.room.v1.GetCurrentRoomRequest - 57, // 98: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:input_type -> hyapp.room.v1.GetRoomSnapshotRequest - 7, // 99: hyapp.room.v1.RoomCommandService.CreateRoom:output_type -> hyapp.room.v1.CreateRoomResponse - 9, // 100: hyapp.room.v1.RoomCommandService.UpdateRoomProfile:output_type -> hyapp.room.v1.UpdateRoomProfileResponse - 11, // 101: hyapp.room.v1.RoomCommandService.JoinRoom:output_type -> hyapp.room.v1.JoinRoomResponse - 13, // 102: hyapp.room.v1.RoomCommandService.RoomHeartbeat:output_type -> hyapp.room.v1.RoomHeartbeatResponse - 15, // 103: hyapp.room.v1.RoomCommandService.LeaveRoom:output_type -> hyapp.room.v1.LeaveRoomResponse - 17, // 104: hyapp.room.v1.RoomCommandService.CloseRoom:output_type -> hyapp.room.v1.CloseRoomResponse - 19, // 105: hyapp.room.v1.RoomCommandService.MicUp:output_type -> hyapp.room.v1.MicUpResponse - 21, // 106: hyapp.room.v1.RoomCommandService.MicDown:output_type -> hyapp.room.v1.MicDownResponse - 23, // 107: hyapp.room.v1.RoomCommandService.ChangeMicSeat:output_type -> hyapp.room.v1.ChangeMicSeatResponse - 25, // 108: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:output_type -> hyapp.room.v1.ConfirmMicPublishingResponse - 27, // 109: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:output_type -> hyapp.room.v1.ApplyRTCEventResponse - 29, // 110: hyapp.room.v1.RoomCommandService.SetMicSeatLock:output_type -> hyapp.room.v1.SetMicSeatLockResponse - 31, // 111: hyapp.room.v1.RoomCommandService.SetChatEnabled:output_type -> hyapp.room.v1.SetChatEnabledResponse - 33, // 112: hyapp.room.v1.RoomCommandService.SetRoomAdmin:output_type -> hyapp.room.v1.SetRoomAdminResponse - 35, // 113: hyapp.room.v1.RoomCommandService.TransferRoomHost:output_type -> hyapp.room.v1.TransferRoomHostResponse - 37, // 114: hyapp.room.v1.RoomCommandService.MuteUser:output_type -> hyapp.room.v1.MuteUserResponse - 39, // 115: hyapp.room.v1.RoomCommandService.KickUser:output_type -> hyapp.room.v1.KickUserResponse - 41, // 116: hyapp.room.v1.RoomCommandService.UnbanUser:output_type -> hyapp.room.v1.UnbanUserResponse - 43, // 117: hyapp.room.v1.RoomCommandService.SendGift:output_type -> hyapp.room.v1.SendGiftResponse - 45, // 118: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:output_type -> hyapp.room.v1.CheckSpeakPermissionResponse - 47, // 119: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:output_type -> hyapp.room.v1.VerifyRoomPresenceResponse - 52, // 120: hyapp.room.v1.RoomQueryService.ListRooms:output_type -> hyapp.room.v1.ListRoomsResponse - 52, // 121: hyapp.room.v1.RoomQueryService.ListRoomFeeds:output_type -> hyapp.room.v1.ListRoomsResponse - 54, // 122: hyapp.room.v1.RoomQueryService.GetMyRoom:output_type -> hyapp.room.v1.GetMyRoomResponse - 56, // 123: hyapp.room.v1.RoomQueryService.GetCurrentRoom:output_type -> hyapp.room.v1.GetCurrentRoomResponse - 58, // 124: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:output_type -> hyapp.room.v1.GetRoomSnapshotResponse - 99, // [99:125] is the sub-list for method output_type - 73, // [73:99] is the sub-list for method input_type - 73, // [73:73] is the sub-list for extension type_name - 73, // [73:73] is the sub-list for extension extendee - 0, // [0:73] is the sub-list for field type_name + 3, // 0: hyapp.room.v1.RoomSnapshot.mic_seats:type_name -> hyapp.room.v1.SeatState + 2, // 1: hyapp.room.v1.RoomSnapshot.online_users:type_name -> hyapp.room.v1.RoomUser + 4, // 2: hyapp.room.v1.RoomSnapshot.gift_rank:type_name -> hyapp.room.v1.RankItem + 63, // 3: hyapp.room.v1.RoomSnapshot.room_ext:type_name -> hyapp.room.v1.RoomSnapshot.RoomExtEntry + 0, // 4: hyapp.room.v1.CreateRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 5: hyapp.room.v1.CreateRoomResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 6: hyapp.room.v1.CreateRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 7: hyapp.room.v1.UpdateRoomProfileRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 8: hyapp.room.v1.UpdateRoomProfileResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 9: hyapp.room.v1.UpdateRoomProfileResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 10: hyapp.room.v1.JoinRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 11: hyapp.room.v1.JoinRoomResponse.result:type_name -> hyapp.room.v1.CommandResult + 2, // 12: hyapp.room.v1.JoinRoomResponse.user:type_name -> hyapp.room.v1.RoomUser + 5, // 13: hyapp.room.v1.JoinRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 14: hyapp.room.v1.RoomHeartbeatRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 15: hyapp.room.v1.RoomHeartbeatResponse.result:type_name -> hyapp.room.v1.CommandResult + 2, // 16: hyapp.room.v1.RoomHeartbeatResponse.user:type_name -> hyapp.room.v1.RoomUser + 5, // 17: hyapp.room.v1.RoomHeartbeatResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 18: hyapp.room.v1.LeaveRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 19: hyapp.room.v1.LeaveRoomResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 20: hyapp.room.v1.LeaveRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 21: hyapp.room.v1.CloseRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 22: hyapp.room.v1.CloseRoomResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 23: hyapp.room.v1.CloseRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 24: hyapp.room.v1.MicUpRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 25: hyapp.room.v1.MicUpResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 26: hyapp.room.v1.MicUpResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 27: hyapp.room.v1.MicDownRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 28: hyapp.room.v1.MicDownResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 29: hyapp.room.v1.MicDownResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 30: hyapp.room.v1.ChangeMicSeatRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 31: hyapp.room.v1.ChangeMicSeatResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 32: hyapp.room.v1.ChangeMicSeatResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 33: hyapp.room.v1.ConfirmMicPublishingRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 34: hyapp.room.v1.ConfirmMicPublishingResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 35: hyapp.room.v1.ConfirmMicPublishingResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 36: hyapp.room.v1.SetMicMuteRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 37: hyapp.room.v1.SetMicMuteResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 38: hyapp.room.v1.SetMicMuteResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 39: hyapp.room.v1.ApplyRTCEventRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 40: hyapp.room.v1.ApplyRTCEventResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 41: hyapp.room.v1.ApplyRTCEventResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 42: hyapp.room.v1.SetMicSeatLockRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 43: hyapp.room.v1.SetMicSeatLockResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 44: hyapp.room.v1.SetMicSeatLockResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 45: hyapp.room.v1.SetChatEnabledRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 46: hyapp.room.v1.SetChatEnabledResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 47: hyapp.room.v1.SetChatEnabledResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 48: hyapp.room.v1.SetRoomAdminRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 49: hyapp.room.v1.SetRoomAdminResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 50: hyapp.room.v1.SetRoomAdminResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 51: hyapp.room.v1.TransferRoomHostRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 52: hyapp.room.v1.TransferRoomHostResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 53: hyapp.room.v1.TransferRoomHostResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 54: hyapp.room.v1.MuteUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 55: hyapp.room.v1.MuteUserResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 56: hyapp.room.v1.MuteUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 57: hyapp.room.v1.KickUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 58: hyapp.room.v1.KickUserResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 59: hyapp.room.v1.KickUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 60: hyapp.room.v1.UnbanUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 61: hyapp.room.v1.UnbanUserResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 62: hyapp.room.v1.UnbanUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 63: hyapp.room.v1.SendGiftRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 64: hyapp.room.v1.SendGiftResponse.result:type_name -> hyapp.room.v1.CommandResult + 4, // 65: hyapp.room.v1.SendGiftResponse.gift_rank:type_name -> hyapp.room.v1.RankItem + 5, // 66: hyapp.room.v1.SendGiftResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 67: hyapp.room.v1.ListRoomsRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 0, // 68: hyapp.room.v1.ListRoomFeedsRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 52, // 69: hyapp.room.v1.ListRoomFeedsRequest.related_users:type_name -> hyapp.room.v1.RoomFeedRelatedUser + 53, // 70: hyapp.room.v1.ListRoomsResponse.rooms:type_name -> hyapp.room.v1.RoomListItem + 0, // 71: hyapp.room.v1.GetMyRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 53, // 72: hyapp.room.v1.GetMyRoomResponse.room:type_name -> hyapp.room.v1.RoomListItem + 0, // 73: hyapp.room.v1.GetCurrentRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 0, // 74: hyapp.room.v1.GetRoomSnapshotRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 5, // 75: hyapp.room.v1.GetRoomSnapshotResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 76: hyapp.room.v1.ListRoomOnlineUsersRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 2, // 77: hyapp.room.v1.ListRoomOnlineUsersResponse.users:type_name -> hyapp.room.v1.RoomUser + 6, // 78: hyapp.room.v1.RoomCommandService.CreateRoom:input_type -> hyapp.room.v1.CreateRoomRequest + 8, // 79: hyapp.room.v1.RoomCommandService.UpdateRoomProfile:input_type -> hyapp.room.v1.UpdateRoomProfileRequest + 10, // 80: hyapp.room.v1.RoomCommandService.JoinRoom:input_type -> hyapp.room.v1.JoinRoomRequest + 12, // 81: hyapp.room.v1.RoomCommandService.RoomHeartbeat:input_type -> hyapp.room.v1.RoomHeartbeatRequest + 14, // 82: hyapp.room.v1.RoomCommandService.LeaveRoom:input_type -> hyapp.room.v1.LeaveRoomRequest + 16, // 83: hyapp.room.v1.RoomCommandService.CloseRoom:input_type -> hyapp.room.v1.CloseRoomRequest + 18, // 84: hyapp.room.v1.RoomCommandService.MicUp:input_type -> hyapp.room.v1.MicUpRequest + 20, // 85: hyapp.room.v1.RoomCommandService.MicDown:input_type -> hyapp.room.v1.MicDownRequest + 22, // 86: hyapp.room.v1.RoomCommandService.ChangeMicSeat:input_type -> hyapp.room.v1.ChangeMicSeatRequest + 24, // 87: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:input_type -> hyapp.room.v1.ConfirmMicPublishingRequest + 26, // 88: hyapp.room.v1.RoomCommandService.SetMicMute:input_type -> hyapp.room.v1.SetMicMuteRequest + 28, // 89: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:input_type -> hyapp.room.v1.ApplyRTCEventRequest + 30, // 90: hyapp.room.v1.RoomCommandService.SetMicSeatLock:input_type -> hyapp.room.v1.SetMicSeatLockRequest + 32, // 91: hyapp.room.v1.RoomCommandService.SetChatEnabled:input_type -> hyapp.room.v1.SetChatEnabledRequest + 34, // 92: hyapp.room.v1.RoomCommandService.SetRoomAdmin:input_type -> hyapp.room.v1.SetRoomAdminRequest + 36, // 93: hyapp.room.v1.RoomCommandService.TransferRoomHost:input_type -> hyapp.room.v1.TransferRoomHostRequest + 38, // 94: hyapp.room.v1.RoomCommandService.MuteUser:input_type -> hyapp.room.v1.MuteUserRequest + 40, // 95: hyapp.room.v1.RoomCommandService.KickUser:input_type -> hyapp.room.v1.KickUserRequest + 42, // 96: hyapp.room.v1.RoomCommandService.UnbanUser:input_type -> hyapp.room.v1.UnbanUserRequest + 44, // 97: hyapp.room.v1.RoomCommandService.SendGift:input_type -> hyapp.room.v1.SendGiftRequest + 46, // 98: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:input_type -> hyapp.room.v1.CheckSpeakPermissionRequest + 48, // 99: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:input_type -> hyapp.room.v1.VerifyRoomPresenceRequest + 50, // 100: hyapp.room.v1.RoomQueryService.ListRooms:input_type -> hyapp.room.v1.ListRoomsRequest + 51, // 101: hyapp.room.v1.RoomQueryService.ListRoomFeeds:input_type -> hyapp.room.v1.ListRoomFeedsRequest + 55, // 102: hyapp.room.v1.RoomQueryService.GetMyRoom:input_type -> hyapp.room.v1.GetMyRoomRequest + 57, // 103: hyapp.room.v1.RoomQueryService.GetCurrentRoom:input_type -> hyapp.room.v1.GetCurrentRoomRequest + 59, // 104: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:input_type -> hyapp.room.v1.GetRoomSnapshotRequest + 61, // 105: hyapp.room.v1.RoomQueryService.ListRoomOnlineUsers:input_type -> hyapp.room.v1.ListRoomOnlineUsersRequest + 7, // 106: hyapp.room.v1.RoomCommandService.CreateRoom:output_type -> hyapp.room.v1.CreateRoomResponse + 9, // 107: hyapp.room.v1.RoomCommandService.UpdateRoomProfile:output_type -> hyapp.room.v1.UpdateRoomProfileResponse + 11, // 108: hyapp.room.v1.RoomCommandService.JoinRoom:output_type -> hyapp.room.v1.JoinRoomResponse + 13, // 109: hyapp.room.v1.RoomCommandService.RoomHeartbeat:output_type -> hyapp.room.v1.RoomHeartbeatResponse + 15, // 110: hyapp.room.v1.RoomCommandService.LeaveRoom:output_type -> hyapp.room.v1.LeaveRoomResponse + 17, // 111: hyapp.room.v1.RoomCommandService.CloseRoom:output_type -> hyapp.room.v1.CloseRoomResponse + 19, // 112: hyapp.room.v1.RoomCommandService.MicUp:output_type -> hyapp.room.v1.MicUpResponse + 21, // 113: hyapp.room.v1.RoomCommandService.MicDown:output_type -> hyapp.room.v1.MicDownResponse + 23, // 114: hyapp.room.v1.RoomCommandService.ChangeMicSeat:output_type -> hyapp.room.v1.ChangeMicSeatResponse + 25, // 115: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:output_type -> hyapp.room.v1.ConfirmMicPublishingResponse + 27, // 116: hyapp.room.v1.RoomCommandService.SetMicMute:output_type -> hyapp.room.v1.SetMicMuteResponse + 29, // 117: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:output_type -> hyapp.room.v1.ApplyRTCEventResponse + 31, // 118: hyapp.room.v1.RoomCommandService.SetMicSeatLock:output_type -> hyapp.room.v1.SetMicSeatLockResponse + 33, // 119: hyapp.room.v1.RoomCommandService.SetChatEnabled:output_type -> hyapp.room.v1.SetChatEnabledResponse + 35, // 120: hyapp.room.v1.RoomCommandService.SetRoomAdmin:output_type -> hyapp.room.v1.SetRoomAdminResponse + 37, // 121: hyapp.room.v1.RoomCommandService.TransferRoomHost:output_type -> hyapp.room.v1.TransferRoomHostResponse + 39, // 122: hyapp.room.v1.RoomCommandService.MuteUser:output_type -> hyapp.room.v1.MuteUserResponse + 41, // 123: hyapp.room.v1.RoomCommandService.KickUser:output_type -> hyapp.room.v1.KickUserResponse + 43, // 124: hyapp.room.v1.RoomCommandService.UnbanUser:output_type -> hyapp.room.v1.UnbanUserResponse + 45, // 125: hyapp.room.v1.RoomCommandService.SendGift:output_type -> hyapp.room.v1.SendGiftResponse + 47, // 126: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:output_type -> hyapp.room.v1.CheckSpeakPermissionResponse + 49, // 127: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:output_type -> hyapp.room.v1.VerifyRoomPresenceResponse + 54, // 128: hyapp.room.v1.RoomQueryService.ListRooms:output_type -> hyapp.room.v1.ListRoomsResponse + 54, // 129: hyapp.room.v1.RoomQueryService.ListRoomFeeds:output_type -> hyapp.room.v1.ListRoomsResponse + 56, // 130: hyapp.room.v1.RoomQueryService.GetMyRoom:output_type -> hyapp.room.v1.GetMyRoomResponse + 58, // 131: hyapp.room.v1.RoomQueryService.GetCurrentRoom:output_type -> hyapp.room.v1.GetCurrentRoomResponse + 60, // 132: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:output_type -> hyapp.room.v1.GetRoomSnapshotResponse + 62, // 133: hyapp.room.v1.RoomQueryService.ListRoomOnlineUsers:output_type -> hyapp.room.v1.ListRoomOnlineUsersResponse + 106, // [106:134] is the sub-list for method output_type + 78, // [78:106] is the sub-list for method input_type + 78, // [78:78] is the sub-list for extension type_name + 78, // [78:78] is the sub-list for extension extendee + 0, // [0:78] is the sub-list for field type_name } func init() { file_proto_room_v1_room_proto_init() } @@ -5067,7 +5458,7 @@ func file_proto_room_v1_room_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_room_v1_room_proto_rawDesc, NumEnums: 0, - NumMessages: 60, + NumMessages: 64, NumExtensions: 0, NumServices: 3, }, diff --git a/api/proto/room/v1/room.proto b/api/proto/room/v1/room.proto index f392c316..986c08b3 100644 --- a/api/proto/room/v1/room.proto +++ b/api/proto/room/v1/room.proto @@ -49,6 +49,8 @@ message SeatState { int64 mic_session_room_version = 7; // last_publish_event_time_ms 记录已接受的最新 RTC/客户端发流事件时间。 int64 last_publish_event_time_ms = 8; + // mic_muted 是服务端可见的麦克风静音状态,用于同步其他用户 UI。 + bool mic_muted = 9; } // RankItem 表达房间内本地礼物榜项目。 @@ -78,6 +80,7 @@ message RoomSnapshot { int64 version = 15; string app_code = 16; string room_short_id = 17; + int64 visible_region_id = 18; } // CreateRoomRequest 创建一个新的房间执行单元。 @@ -234,6 +237,21 @@ message ConfirmMicPublishingResponse { RoomSnapshot room = 3; } +// SetMicMuteRequest 修改麦位上的服务端可见静音态。 +message SetMicMuteRequest { + RequestMeta meta = 1; + // target_user_id 为空时默认修改 actor_user_id 自己的麦克风静音态。 + int64 target_user_id = 2; + bool muted = 3; +} + +// SetMicMuteResponse 返回静音态变更后的最新房间快照。 +message SetMicMuteResponse { + CommandResult result = 1; + int32 seat_no = 2; + RoomSnapshot room = 3; +} + // ApplyRTCEventRequest 把可信 RTC 服务端回调转换成 Room Cell 命令。 // event_type 当前只接受 audio_started、audio_stopped、room_exited。 message ApplyRTCEventRequest { @@ -343,9 +361,13 @@ message UnbanUserResponse { // SendGiftRequest 是首版房间内最重要的变现命令。 message SendGiftRequest { RequestMeta meta = 1; + // target_user_id 是兼容旧客户端的单目标字段;新客户端优先使用 target_user_ids。 int64 target_user_id = 2; string gift_id = 3; int32 gift_count = 4; + // target_type 当前只提交 user;后续扩展 all_mic、all_room、couple 时不再改 HTTP 契约。 + string target_type = 5; + repeated int64 target_user_ids = 6; } // SendGiftResponse 返回扣费成功并落到房间后的状态结果。 @@ -493,6 +515,23 @@ message GetRoomSnapshotResponse { int64 server_time_ms = 2; } +// ListRoomOnlineUsersRequest 分页查询房间在线用户,不让客户端拉完整 RoomSnapshot 做列表分页。 +message ListRoomOnlineUsersRequest { + RequestMeta meta = 1; + string room_id = 2; + int64 viewer_user_id = 3; + int32 page = 4; + int32 page_size = 5; +} + +message ListRoomOnlineUsersResponse { + repeated RoomUser users = 1; + int64 total = 2; + int32 page = 3; + int32 page_size = 4; + int64 server_time_ms = 5; +} + // RoomCommandService 承载所有会改变房间状态的命令。 service RoomCommandService { rpc CreateRoom(CreateRoomRequest) returns (CreateRoomResponse); @@ -505,6 +544,7 @@ service RoomCommandService { rpc MicDown(MicDownRequest) returns (MicDownResponse); rpc ChangeMicSeat(ChangeMicSeatRequest) returns (ChangeMicSeatResponse); rpc ConfirmMicPublishing(ConfirmMicPublishingRequest) returns (ConfirmMicPublishingResponse); + rpc SetMicMute(SetMicMuteRequest) returns (SetMicMuteResponse); rpc ApplyRTCEvent(ApplyRTCEventRequest) returns (ApplyRTCEventResponse); rpc SetMicSeatLock(SetMicSeatLockRequest) returns (SetMicSeatLockResponse); rpc SetChatEnabled(SetChatEnabledRequest) returns (SetChatEnabledResponse); @@ -529,4 +569,5 @@ service RoomQueryService { rpc GetMyRoom(GetMyRoomRequest) returns (GetMyRoomResponse); rpc GetCurrentRoom(GetCurrentRoomRequest) returns (GetCurrentRoomResponse); rpc GetRoomSnapshot(GetRoomSnapshotRequest) returns (GetRoomSnapshotResponse); + rpc ListRoomOnlineUsers(ListRoomOnlineUsersRequest) returns (ListRoomOnlineUsersResponse); } diff --git a/api/proto/room/v1/room_grpc.pb.go b/api/proto/room/v1/room_grpc.pb.go index ea1ea070..e019a996 100644 --- a/api/proto/room/v1/room_grpc.pb.go +++ b/api/proto/room/v1/room_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.27.3 +// - protoc v5.29.3 // source: proto/room/v1/room.proto package roomv1 @@ -29,6 +29,7 @@ const ( RoomCommandService_MicDown_FullMethodName = "/hyapp.room.v1.RoomCommandService/MicDown" RoomCommandService_ChangeMicSeat_FullMethodName = "/hyapp.room.v1.RoomCommandService/ChangeMicSeat" RoomCommandService_ConfirmMicPublishing_FullMethodName = "/hyapp.room.v1.RoomCommandService/ConfirmMicPublishing" + RoomCommandService_SetMicMute_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetMicMute" RoomCommandService_ApplyRTCEvent_FullMethodName = "/hyapp.room.v1.RoomCommandService/ApplyRTCEvent" RoomCommandService_SetMicSeatLock_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetMicSeatLock" RoomCommandService_SetChatEnabled_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetChatEnabled" @@ -56,6 +57,7 @@ type RoomCommandServiceClient interface { MicDown(ctx context.Context, in *MicDownRequest, opts ...grpc.CallOption) (*MicDownResponse, error) ChangeMicSeat(ctx context.Context, in *ChangeMicSeatRequest, opts ...grpc.CallOption) (*ChangeMicSeatResponse, error) ConfirmMicPublishing(ctx context.Context, in *ConfirmMicPublishingRequest, opts ...grpc.CallOption) (*ConfirmMicPublishingResponse, error) + SetMicMute(ctx context.Context, in *SetMicMuteRequest, opts ...grpc.CallOption) (*SetMicMuteResponse, error) ApplyRTCEvent(ctx context.Context, in *ApplyRTCEventRequest, opts ...grpc.CallOption) (*ApplyRTCEventResponse, error) SetMicSeatLock(ctx context.Context, in *SetMicSeatLockRequest, opts ...grpc.CallOption) (*SetMicSeatLockResponse, error) SetChatEnabled(ctx context.Context, in *SetChatEnabledRequest, opts ...grpc.CallOption) (*SetChatEnabledResponse, error) @@ -175,6 +177,16 @@ func (c *roomCommandServiceClient) ConfirmMicPublishing(ctx context.Context, in return out, nil } +func (c *roomCommandServiceClient) SetMicMute(ctx context.Context, in *SetMicMuteRequest, opts ...grpc.CallOption) (*SetMicMuteResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SetMicMuteResponse) + err := c.cc.Invoke(ctx, RoomCommandService_SetMicMute_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *roomCommandServiceClient) ApplyRTCEvent(ctx context.Context, in *ApplyRTCEventRequest, opts ...grpc.CallOption) (*ApplyRTCEventResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ApplyRTCEventResponse) @@ -281,6 +293,7 @@ type RoomCommandServiceServer interface { MicDown(context.Context, *MicDownRequest) (*MicDownResponse, error) ChangeMicSeat(context.Context, *ChangeMicSeatRequest) (*ChangeMicSeatResponse, error) ConfirmMicPublishing(context.Context, *ConfirmMicPublishingRequest) (*ConfirmMicPublishingResponse, error) + SetMicMute(context.Context, *SetMicMuteRequest) (*SetMicMuteResponse, error) ApplyRTCEvent(context.Context, *ApplyRTCEventRequest) (*ApplyRTCEventResponse, error) SetMicSeatLock(context.Context, *SetMicSeatLockRequest) (*SetMicSeatLockResponse, error) SetChatEnabled(context.Context, *SetChatEnabledRequest) (*SetChatEnabledResponse, error) @@ -330,6 +343,9 @@ func (UnimplementedRoomCommandServiceServer) ChangeMicSeat(context.Context, *Cha func (UnimplementedRoomCommandServiceServer) ConfirmMicPublishing(context.Context, *ConfirmMicPublishingRequest) (*ConfirmMicPublishingResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ConfirmMicPublishing not implemented") } +func (UnimplementedRoomCommandServiceServer) SetMicMute(context.Context, *SetMicMuteRequest) (*SetMicMuteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetMicMute not implemented") +} func (UnimplementedRoomCommandServiceServer) ApplyRTCEvent(context.Context, *ApplyRTCEventRequest) (*ApplyRTCEventResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ApplyRTCEvent not implemented") } @@ -558,6 +574,24 @@ func _RoomCommandService_ConfirmMicPublishing_Handler(srv interface{}, ctx conte return interceptor(ctx, in, info, handler) } +func _RoomCommandService_SetMicMute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetMicMuteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoomCommandServiceServer).SetMicMute(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoomCommandService_SetMicMute_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoomCommandServiceServer).SetMicMute(ctx, req.(*SetMicMuteRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _RoomCommandService_ApplyRTCEvent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ApplyRTCEventRequest) if err := dec(in); err != nil { @@ -767,6 +801,10 @@ var RoomCommandService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ConfirmMicPublishing", Handler: _RoomCommandService_ConfirmMicPublishing_Handler, }, + { + MethodName: "SetMicMute", + Handler: _RoomCommandService_SetMicMute_Handler, + }, { MethodName: "ApplyRTCEvent", Handler: _RoomCommandService_ApplyRTCEvent_Handler, @@ -953,11 +991,12 @@ var RoomGuardService_ServiceDesc = grpc.ServiceDesc{ } const ( - RoomQueryService_ListRooms_FullMethodName = "/hyapp.room.v1.RoomQueryService/ListRooms" - RoomQueryService_ListRoomFeeds_FullMethodName = "/hyapp.room.v1.RoomQueryService/ListRoomFeeds" - RoomQueryService_GetMyRoom_FullMethodName = "/hyapp.room.v1.RoomQueryService/GetMyRoom" - RoomQueryService_GetCurrentRoom_FullMethodName = "/hyapp.room.v1.RoomQueryService/GetCurrentRoom" - RoomQueryService_GetRoomSnapshot_FullMethodName = "/hyapp.room.v1.RoomQueryService/GetRoomSnapshot" + RoomQueryService_ListRooms_FullMethodName = "/hyapp.room.v1.RoomQueryService/ListRooms" + RoomQueryService_ListRoomFeeds_FullMethodName = "/hyapp.room.v1.RoomQueryService/ListRoomFeeds" + RoomQueryService_GetMyRoom_FullMethodName = "/hyapp.room.v1.RoomQueryService/GetMyRoom" + RoomQueryService_GetCurrentRoom_FullMethodName = "/hyapp.room.v1.RoomQueryService/GetCurrentRoom" + RoomQueryService_GetRoomSnapshot_FullMethodName = "/hyapp.room.v1.RoomQueryService/GetRoomSnapshot" + RoomQueryService_ListRoomOnlineUsers_FullMethodName = "/hyapp.room.v1.RoomQueryService/ListRoomOnlineUsers" ) // RoomQueryServiceClient is the client API for RoomQueryService service. @@ -971,6 +1010,7 @@ type RoomQueryServiceClient interface { GetMyRoom(ctx context.Context, in *GetMyRoomRequest, opts ...grpc.CallOption) (*GetMyRoomResponse, error) GetCurrentRoom(ctx context.Context, in *GetCurrentRoomRequest, opts ...grpc.CallOption) (*GetCurrentRoomResponse, error) GetRoomSnapshot(ctx context.Context, in *GetRoomSnapshotRequest, opts ...grpc.CallOption) (*GetRoomSnapshotResponse, error) + ListRoomOnlineUsers(ctx context.Context, in *ListRoomOnlineUsersRequest, opts ...grpc.CallOption) (*ListRoomOnlineUsersResponse, error) } type roomQueryServiceClient struct { @@ -1031,6 +1071,16 @@ func (c *roomQueryServiceClient) GetRoomSnapshot(ctx context.Context, in *GetRoo return out, nil } +func (c *roomQueryServiceClient) ListRoomOnlineUsers(ctx context.Context, in *ListRoomOnlineUsersRequest, opts ...grpc.CallOption) (*ListRoomOnlineUsersResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListRoomOnlineUsersResponse) + err := c.cc.Invoke(ctx, RoomQueryService_ListRoomOnlineUsers_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // RoomQueryServiceServer is the server API for RoomQueryService service. // All implementations must embed UnimplementedRoomQueryServiceServer // for forward compatibility. @@ -1042,6 +1092,7 @@ type RoomQueryServiceServer interface { GetMyRoom(context.Context, *GetMyRoomRequest) (*GetMyRoomResponse, error) GetCurrentRoom(context.Context, *GetCurrentRoomRequest) (*GetCurrentRoomResponse, error) GetRoomSnapshot(context.Context, *GetRoomSnapshotRequest) (*GetRoomSnapshotResponse, error) + ListRoomOnlineUsers(context.Context, *ListRoomOnlineUsersRequest) (*ListRoomOnlineUsersResponse, error) mustEmbedUnimplementedRoomQueryServiceServer() } @@ -1067,6 +1118,9 @@ func (UnimplementedRoomQueryServiceServer) GetCurrentRoom(context.Context, *GetC func (UnimplementedRoomQueryServiceServer) GetRoomSnapshot(context.Context, *GetRoomSnapshotRequest) (*GetRoomSnapshotResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetRoomSnapshot not implemented") } +func (UnimplementedRoomQueryServiceServer) ListRoomOnlineUsers(context.Context, *ListRoomOnlineUsersRequest) (*ListRoomOnlineUsersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListRoomOnlineUsers not implemented") +} func (UnimplementedRoomQueryServiceServer) mustEmbedUnimplementedRoomQueryServiceServer() {} func (UnimplementedRoomQueryServiceServer) testEmbeddedByValue() {} @@ -1178,6 +1232,24 @@ func _RoomQueryService_GetRoomSnapshot_Handler(srv interface{}, ctx context.Cont return interceptor(ctx, in, info, handler) } +func _RoomQueryService_ListRoomOnlineUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRoomOnlineUsersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoomQueryServiceServer).ListRoomOnlineUsers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoomQueryService_ListRoomOnlineUsers_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoomQueryServiceServer).ListRoomOnlineUsers(ctx, req.(*ListRoomOnlineUsersRequest)) + } + return interceptor(ctx, in, info, handler) +} + // RoomQueryService_ServiceDesc is the grpc.ServiceDesc for RoomQueryService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -1205,6 +1277,10 @@ var RoomQueryService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetRoomSnapshot", Handler: _RoomQueryService_GetRoomSnapshot_Handler, }, + { + MethodName: "ListRoomOnlineUsers", + Handler: _RoomQueryService_ListRoomOnlineUsers_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/room/v1/room.proto", diff --git a/api/proto/user/v1/auth.pb.go b/api/proto/user/v1/auth.pb.go index 88f2f926..49dd2016 100644 --- a/api/proto/user/v1/auth.pb.go +++ b/api/proto/user/v1/auth.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.35.1 -// protoc v5.27.3 +// protoc v5.29.3 // source: proto/user/v1/auth.proto package userv1 diff --git a/api/proto/user/v1/auth_grpc.pb.go b/api/proto/user/v1/auth_grpc.pb.go index 3a907a5f..31c77a58 100644 --- a/api/proto/user/v1/auth_grpc.pb.go +++ b/api/proto/user/v1/auth_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.27.3 +// - protoc v5.29.3 // source: proto/user/v1/auth.proto package userv1 diff --git a/api/proto/user/v1/host.pb.go b/api/proto/user/v1/host.pb.go index 30c524d9..01d186ac 100644 --- a/api/proto/user/v1/host.pb.go +++ b/api/proto/user/v1/host.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.35.1 -// protoc v5.27.3 +// protoc v5.29.3 // source: proto/user/v1/host.proto package userv1 diff --git a/api/proto/user/v1/host_grpc.pb.go b/api/proto/user/v1/host_grpc.pb.go index f6b6e93f..4f507c9d 100644 --- a/api/proto/user/v1/host_grpc.pb.go +++ b/api/proto/user/v1/host_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.27.3 +// - protoc v5.29.3 // source: proto/user/v1/host.proto package userv1 diff --git a/api/proto/user/v1/user.pb.go b/api/proto/user/v1/user.pb.go index b087763f..08e9140e 100644 --- a/api/proto/user/v1/user.pb.go +++ b/api/proto/user/v1/user.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.35.1 -// protoc v5.27.3 +// protoc v5.29.3 // source: proto/user/v1/user.proto package userv1 diff --git a/api/proto/user/v1/user_grpc.pb.go b/api/proto/user/v1/user_grpc.pb.go index ab3b1c53..52b3bc48 100644 --- a/api/proto/user/v1/user_grpc.pb.go +++ b/api/proto/user/v1/user_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.27.3 +// - protoc v5.29.3 // source: proto/user/v1/user.proto package userv1 diff --git a/api/proto/wallet/v1/wallet.pb.go b/api/proto/wallet/v1/wallet.pb.go index d94bf6f0..2982c023 100644 --- a/api/proto/wallet/v1/wallet.pb.go +++ b/api/proto/wallet/v1/wallet.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.35.1 -// protoc v5.27.3 +// protoc v5.29.3 // source: proto/wallet/v1/wallet.proto package walletv1 @@ -7361,6 +7361,202 @@ func (x *CreditTaskRewardResponse) GetGrantedAtMs() int64 { return 0 } +// ApplyGameCoinChangeRequest 是 game-service 唯一可以调用的钱包游戏改账入口。 +type ApplyGameCoinChangeRequest 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"` + CommandId string `protobuf:"bytes,3,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + UserId int64 `protobuf:"varint,4,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + PlatformCode string `protobuf:"bytes,5,opt,name=platform_code,json=platformCode,proto3" json:"platform_code,omitempty"` + GameId string `protobuf:"bytes,6,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` + ProviderOrderId string `protobuf:"bytes,7,opt,name=provider_order_id,json=providerOrderId,proto3" json:"provider_order_id,omitempty"` + ProviderRoundId string `protobuf:"bytes,8,opt,name=provider_round_id,json=providerRoundId,proto3" json:"provider_round_id,omitempty"` + OpType string `protobuf:"bytes,9,opt,name=op_type,json=opType,proto3" json:"op_type,omitempty"` + CoinAmount int64 `protobuf:"varint,10,opt,name=coin_amount,json=coinAmount,proto3" json:"coin_amount,omitempty"` + RoomId string `protobuf:"bytes,11,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + RequestHash string `protobuf:"bytes,12,opt,name=request_hash,json=requestHash,proto3" json:"request_hash,omitempty"` +} + +func (x *ApplyGameCoinChangeRequest) Reset() { + *x = ApplyGameCoinChangeRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[82] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ApplyGameCoinChangeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApplyGameCoinChangeRequest) ProtoMessage() {} + +func (x *ApplyGameCoinChangeRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_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 ApplyGameCoinChangeRequest.ProtoReflect.Descriptor instead. +func (*ApplyGameCoinChangeRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{82} +} + +func (x *ApplyGameCoinChangeRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *ApplyGameCoinChangeRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *ApplyGameCoinChangeRequest) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *ApplyGameCoinChangeRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *ApplyGameCoinChangeRequest) GetPlatformCode() string { + if x != nil { + return x.PlatformCode + } + return "" +} + +func (x *ApplyGameCoinChangeRequest) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *ApplyGameCoinChangeRequest) GetProviderOrderId() string { + if x != nil { + return x.ProviderOrderId + } + return "" +} + +func (x *ApplyGameCoinChangeRequest) GetProviderRoundId() string { + if x != nil { + return x.ProviderRoundId + } + return "" +} + +func (x *ApplyGameCoinChangeRequest) GetOpType() string { + if x != nil { + return x.OpType + } + return "" +} + +func (x *ApplyGameCoinChangeRequest) GetCoinAmount() int64 { + if x != nil { + return x.CoinAmount + } + return 0 +} + +func (x *ApplyGameCoinChangeRequest) GetRoomId() string { + if x != nil { + return x.RoomId + } + return "" +} + +func (x *ApplyGameCoinChangeRequest) GetRequestHash() string { + if x != nil { + return x.RequestHash + } + return "" +} + +// ApplyGameCoinChangeResponse 返回游戏改账流水和用户 COIN 账后余额。 +type ApplyGameCoinChangeResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + WalletTransactionId string `protobuf:"bytes,1,opt,name=wallet_transaction_id,json=walletTransactionId,proto3" json:"wallet_transaction_id,omitempty"` + BalanceAfter int64 `protobuf:"varint,2,opt,name=balance_after,json=balanceAfter,proto3" json:"balance_after,omitempty"` + IdempotentReplay bool `protobuf:"varint,3,opt,name=idempotent_replay,json=idempotentReplay,proto3" json:"idempotent_replay,omitempty"` +} + +func (x *ApplyGameCoinChangeResponse) Reset() { + *x = ApplyGameCoinChangeResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[83] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ApplyGameCoinChangeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApplyGameCoinChangeResponse) ProtoMessage() {} + +func (x *ApplyGameCoinChangeResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_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 ApplyGameCoinChangeResponse.ProtoReflect.Descriptor instead. +func (*ApplyGameCoinChangeResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{83} +} + +func (x *ApplyGameCoinChangeResponse) GetWalletTransactionId() string { + if x != nil { + return x.WalletTransactionId + } + return "" +} + +func (x *ApplyGameCoinChangeResponse) GetBalanceAfter() int64 { + if x != nil { + return x.BalanceAfter + } + return 0 +} + +func (x *ApplyGameCoinChangeResponse) GetIdempotentReplay() bool { + if x != nil { + return x.IdempotentReplay + } + return false +} + var File_proto_wallet_v1_wallet_proto protoreflect.FileDescriptor var file_proto_wallet_v1_wallet_proto_rawDesc = []byte{ @@ -8673,241 +8869,285 @@ var file_proto_wallet_v1_wallet_proto_rawDesc = []byte{ 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, 0x32, - 0xe9, 0x1c, 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, 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, - 0x67, 0x0a, 0x10, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x41, 0x73, - 0x73, 0x65, 0x74, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x64, 0x69, - 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 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, - 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, 0x85, 0x01, 0x0a, 0x1a, 0x41, 0x64, 0x6d, + 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, 0x32, 0xdb, 0x1d, 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, 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, 0x67, 0x0a, 0x10, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x64, 0x69, + 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, + 0x65, 0x64, 0x69, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 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, 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, 0x85, 0x01, 0x0a, 0x1a, + 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, 0x12, 0x32, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 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, 0x12, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 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, 0x1a, 0x33, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 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, 0x79, 0x0a, 0x16, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x6f, 0x69, 0x6e, - 0x46, 0x72, 0x6f, 0x6d, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x2e, 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, 0x43, 0x6f, 0x69, 0x6e, 0x46, 0x72, 0x6f, 0x6d, 0x53, 0x65, 0x6c, - 0x6c, 0x65, 0x72, 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, 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, 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, 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, + 0x65, 0x72, 0x53, 0x74, 0x6f, 0x63, 0x6b, 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, 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, + 0x2e, 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, 0x79, 0x0a, 0x16, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, + 0x6f, 0x69, 0x6e, 0x46, 0x72, 0x6f, 0x6d, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x12, 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, 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, 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, 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, 0x61, 0x0a, - 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 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, 0x1a, 0x2f, 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, 0x43, 0x6f, 0x69, 0x6e, 0x46, 0x72, 0x6f, 0x6d, + 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 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, 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, 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, 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, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, + 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, - 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, 0x41, - 0x70, 0x70, 0x6c, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x12, 0x27, + 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, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 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, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x57, - 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 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, + 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, 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, - 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, 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, 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, + 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, 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, 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, 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, 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, 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, + 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, 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, 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, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, + 0x6c, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, + 0x77, 0x61, 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, 0x41, 0x70, 0x70, + 0x6c, 0x79, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 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, 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, 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, + 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 ( @@ -8922,7 +9162,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, 82) +var file_proto_wallet_v1_wallet_proto_msgTypes = make([]protoimpl.MessageInfo, 84) var file_proto_wallet_v1_wallet_proto_goTypes = []any{ (*DebitGiftRequest)(nil), // 0: hyapp.wallet.v1.DebitGiftRequest (*DebitGiftResponse)(nil), // 1: hyapp.wallet.v1.DebitGiftResponse @@ -9006,6 +9246,8 @@ var file_proto_wallet_v1_wallet_proto_goTypes = []any{ (*PurchaseVipResponse)(nil), // 79: hyapp.wallet.v1.PurchaseVipResponse (*CreditTaskRewardRequest)(nil), // 80: hyapp.wallet.v1.CreditTaskRewardRequest (*CreditTaskRewardResponse)(nil), // 81: hyapp.wallet.v1.CreditTaskRewardResponse + (*ApplyGameCoinChangeRequest)(nil), // 82: hyapp.wallet.v1.ApplyGameCoinChangeRequest + (*ApplyGameCoinChangeResponse)(nil), // 83: hyapp.wallet.v1.ApplyGameCoinChangeResponse } var file_proto_wallet_v1_wallet_proto_depIdxs = []int32{ 2, // 0: hyapp.wallet.v1.GetBalancesResponse.balances:type_name -> hyapp.wallet.v1.AssetBalance @@ -9080,43 +9322,45 @@ var file_proto_wallet_v1_wallet_proto_depIdxs = []int32{ 76, // 69: hyapp.wallet.v1.WalletService.GetMyVip:input_type -> hyapp.wallet.v1.GetMyVipRequest 78, // 70: hyapp.wallet.v1.WalletService.PurchaseVip:input_type -> hyapp.wallet.v1.PurchaseVipRequest 80, // 71: hyapp.wallet.v1.WalletService.CreditTaskReward:input_type -> hyapp.wallet.v1.CreditTaskRewardRequest - 1, // 72: hyapp.wallet.v1.WalletService.DebitGift:output_type -> hyapp.wallet.v1.DebitGiftResponse - 4, // 73: hyapp.wallet.v1.WalletService.GetBalances:output_type -> hyapp.wallet.v1.GetBalancesResponse - 6, // 74: hyapp.wallet.v1.WalletService.AdminCreditAsset:output_type -> hyapp.wallet.v1.AdminCreditAssetResponse - 8, // 75: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:output_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockResponse - 10, // 76: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:output_type -> hyapp.wallet.v1.TransferCoinFromSellerResponse - 20, // 77: hyapp.wallet.v1.WalletService.ListResources:output_type -> hyapp.wallet.v1.ListResourcesResponse - 22, // 78: hyapp.wallet.v1.WalletService.GetResource:output_type -> hyapp.wallet.v1.GetResourceResponse - 26, // 79: hyapp.wallet.v1.WalletService.CreateResource:output_type -> hyapp.wallet.v1.ResourceResponse - 26, // 80: hyapp.wallet.v1.WalletService.UpdateResource:output_type -> hyapp.wallet.v1.ResourceResponse - 26, // 81: hyapp.wallet.v1.WalletService.SetResourceStatus:output_type -> hyapp.wallet.v1.ResourceResponse - 28, // 82: hyapp.wallet.v1.WalletService.ListResourceGroups:output_type -> hyapp.wallet.v1.ListResourceGroupsResponse - 30, // 83: hyapp.wallet.v1.WalletService.GetResourceGroup:output_type -> hyapp.wallet.v1.GetResourceGroupResponse - 34, // 84: hyapp.wallet.v1.WalletService.CreateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 34, // 85: hyapp.wallet.v1.WalletService.UpdateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 34, // 86: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 36, // 87: hyapp.wallet.v1.WalletService.ListGiftConfigs:output_type -> hyapp.wallet.v1.ListGiftConfigsResponse - 40, // 88: hyapp.wallet.v1.WalletService.CreateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse - 40, // 89: hyapp.wallet.v1.WalletService.UpdateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse - 40, // 90: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:output_type -> hyapp.wallet.v1.GiftConfigResponse - 43, // 91: hyapp.wallet.v1.WalletService.GrantResource:output_type -> hyapp.wallet.v1.ResourceGrantResponse - 43, // 92: hyapp.wallet.v1.WalletService.GrantResourceGroup:output_type -> hyapp.wallet.v1.ResourceGrantResponse - 45, // 93: hyapp.wallet.v1.WalletService.ListUserResources:output_type -> hyapp.wallet.v1.ListUserResourcesResponse - 47, // 94: hyapp.wallet.v1.WalletService.EquipUserResource:output_type -> hyapp.wallet.v1.EquipUserResourceResponse - 49, // 95: hyapp.wallet.v1.WalletService.ListResourceGrants:output_type -> hyapp.wallet.v1.ListResourceGrantsResponse - 52, // 96: hyapp.wallet.v1.WalletService.ListRechargeBills:output_type -> hyapp.wallet.v1.ListRechargeBillsResponse - 55, // 97: hyapp.wallet.v1.WalletService.GetWalletOverview:output_type -> hyapp.wallet.v1.GetWalletOverviewResponse - 58, // 98: hyapp.wallet.v1.WalletService.GetWalletValueSummary:output_type -> hyapp.wallet.v1.GetWalletValueSummaryResponse - 61, // 99: hyapp.wallet.v1.WalletService.ListRechargeProducts:output_type -> hyapp.wallet.v1.ListRechargeProductsResponse - 64, // 100: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:output_type -> hyapp.wallet.v1.GetDiamondExchangeConfigResponse - 67, // 101: hyapp.wallet.v1.WalletService.ListWalletTransactions:output_type -> hyapp.wallet.v1.ListWalletTransactionsResponse - 70, // 102: hyapp.wallet.v1.WalletService.ApplyWithdrawal:output_type -> hyapp.wallet.v1.ApplyWithdrawalResponse - 75, // 103: hyapp.wallet.v1.WalletService.ListVipPackages:output_type -> hyapp.wallet.v1.ListVipPackagesResponse - 77, // 104: hyapp.wallet.v1.WalletService.GetMyVip:output_type -> hyapp.wallet.v1.GetMyVipResponse - 79, // 105: hyapp.wallet.v1.WalletService.PurchaseVip:output_type -> hyapp.wallet.v1.PurchaseVipResponse - 81, // 106: hyapp.wallet.v1.WalletService.CreditTaskReward:output_type -> hyapp.wallet.v1.CreditTaskRewardResponse - 72, // [72:107] is the sub-list for method output_type - 37, // [37:72] is the sub-list for method input_type + 82, // 72: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:input_type -> hyapp.wallet.v1.ApplyGameCoinChangeRequest + 1, // 73: hyapp.wallet.v1.WalletService.DebitGift:output_type -> hyapp.wallet.v1.DebitGiftResponse + 4, // 74: hyapp.wallet.v1.WalletService.GetBalances:output_type -> hyapp.wallet.v1.GetBalancesResponse + 6, // 75: hyapp.wallet.v1.WalletService.AdminCreditAsset:output_type -> hyapp.wallet.v1.AdminCreditAssetResponse + 8, // 76: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:output_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockResponse + 10, // 77: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:output_type -> hyapp.wallet.v1.TransferCoinFromSellerResponse + 20, // 78: hyapp.wallet.v1.WalletService.ListResources:output_type -> hyapp.wallet.v1.ListResourcesResponse + 22, // 79: hyapp.wallet.v1.WalletService.GetResource:output_type -> hyapp.wallet.v1.GetResourceResponse + 26, // 80: hyapp.wallet.v1.WalletService.CreateResource:output_type -> hyapp.wallet.v1.ResourceResponse + 26, // 81: hyapp.wallet.v1.WalletService.UpdateResource:output_type -> hyapp.wallet.v1.ResourceResponse + 26, // 82: hyapp.wallet.v1.WalletService.SetResourceStatus:output_type -> hyapp.wallet.v1.ResourceResponse + 28, // 83: hyapp.wallet.v1.WalletService.ListResourceGroups:output_type -> hyapp.wallet.v1.ListResourceGroupsResponse + 30, // 84: hyapp.wallet.v1.WalletService.GetResourceGroup:output_type -> hyapp.wallet.v1.GetResourceGroupResponse + 34, // 85: hyapp.wallet.v1.WalletService.CreateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 34, // 86: hyapp.wallet.v1.WalletService.UpdateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 34, // 87: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 36, // 88: hyapp.wallet.v1.WalletService.ListGiftConfigs:output_type -> hyapp.wallet.v1.ListGiftConfigsResponse + 40, // 89: hyapp.wallet.v1.WalletService.CreateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse + 40, // 90: hyapp.wallet.v1.WalletService.UpdateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse + 40, // 91: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:output_type -> hyapp.wallet.v1.GiftConfigResponse + 43, // 92: hyapp.wallet.v1.WalletService.GrantResource:output_type -> hyapp.wallet.v1.ResourceGrantResponse + 43, // 93: hyapp.wallet.v1.WalletService.GrantResourceGroup:output_type -> hyapp.wallet.v1.ResourceGrantResponse + 45, // 94: hyapp.wallet.v1.WalletService.ListUserResources:output_type -> hyapp.wallet.v1.ListUserResourcesResponse + 47, // 95: hyapp.wallet.v1.WalletService.EquipUserResource:output_type -> hyapp.wallet.v1.EquipUserResourceResponse + 49, // 96: hyapp.wallet.v1.WalletService.ListResourceGrants:output_type -> hyapp.wallet.v1.ListResourceGrantsResponse + 52, // 97: hyapp.wallet.v1.WalletService.ListRechargeBills:output_type -> hyapp.wallet.v1.ListRechargeBillsResponse + 55, // 98: hyapp.wallet.v1.WalletService.GetWalletOverview:output_type -> hyapp.wallet.v1.GetWalletOverviewResponse + 58, // 99: hyapp.wallet.v1.WalletService.GetWalletValueSummary:output_type -> hyapp.wallet.v1.GetWalletValueSummaryResponse + 61, // 100: hyapp.wallet.v1.WalletService.ListRechargeProducts:output_type -> hyapp.wallet.v1.ListRechargeProductsResponse + 64, // 101: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:output_type -> hyapp.wallet.v1.GetDiamondExchangeConfigResponse + 67, // 102: hyapp.wallet.v1.WalletService.ListWalletTransactions:output_type -> hyapp.wallet.v1.ListWalletTransactionsResponse + 70, // 103: hyapp.wallet.v1.WalletService.ApplyWithdrawal:output_type -> hyapp.wallet.v1.ApplyWithdrawalResponse + 75, // 104: hyapp.wallet.v1.WalletService.ListVipPackages:output_type -> hyapp.wallet.v1.ListVipPackagesResponse + 77, // 105: hyapp.wallet.v1.WalletService.GetMyVip:output_type -> hyapp.wallet.v1.GetMyVipResponse + 79, // 106: hyapp.wallet.v1.WalletService.PurchaseVip:output_type -> hyapp.wallet.v1.PurchaseVipResponse + 81, // 107: hyapp.wallet.v1.WalletService.CreditTaskReward:output_type -> hyapp.wallet.v1.CreditTaskRewardResponse + 83, // 108: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:output_type -> hyapp.wallet.v1.ApplyGameCoinChangeResponse + 73, // [73:109] is the sub-list for method output_type + 37, // [37:73] is the sub-list for method input_type 37, // [37:37] is the sub-list for extension type_name 37, // [37:37] is the sub-list for extension extendee 0, // [0:37] is the sub-list for field type_name @@ -9135,7 +9379,7 @@ func file_proto_wallet_v1_wallet_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_wallet_v1_wallet_proto_rawDesc, NumEnums: 0, - NumMessages: 82, + NumMessages: 84, NumExtensions: 0, NumServices: 1, }, diff --git a/api/proto/wallet/v1/wallet.proto b/api/proto/wallet/v1/wallet.proto index d4de2e1a..ffeed403 100644 --- a/api/proto/wallet/v1/wallet.proto +++ b/api/proto/wallet/v1/wallet.proto @@ -805,6 +805,29 @@ message CreditTaskRewardResponse { int64 granted_at_ms = 4; } +// ApplyGameCoinChangeRequest 是 game-service 唯一可以调用的钱包游戏改账入口。 +message ApplyGameCoinChangeRequest { + string request_id = 1; + string app_code = 2; + string command_id = 3; + int64 user_id = 4; + string platform_code = 5; + string game_id = 6; + string provider_order_id = 7; + string provider_round_id = 8; + string op_type = 9; + int64 coin_amount = 10; + string room_id = 11; + string request_hash = 12; +} + +// ApplyGameCoinChangeResponse 返回游戏改账流水和用户 COIN 账后余额。 +message ApplyGameCoinChangeResponse { + string wallet_transaction_id = 1; + int64 balance_after = 2; + bool idempotent_replay = 3; +} + // WalletService 是内部账务 gRPC 边界,外部 HTTP 入口仍由 gateway-service 承载。 service WalletService { rpc DebitGift(DebitGiftRequest) returns (DebitGiftResponse); @@ -842,4 +865,5 @@ service WalletService { rpc GetMyVip(GetMyVipRequest) returns (GetMyVipResponse); rpc PurchaseVip(PurchaseVipRequest) returns (PurchaseVipResponse); rpc CreditTaskReward(CreditTaskRewardRequest) returns (CreditTaskRewardResponse); + rpc ApplyGameCoinChange(ApplyGameCoinChangeRequest) returns (ApplyGameCoinChangeResponse); } diff --git a/api/proto/wallet/v1/wallet_grpc.pb.go b/api/proto/wallet/v1/wallet_grpc.pb.go index 2e0df2a4..3502b5fc 100644 --- a/api/proto/wallet/v1/wallet_grpc.pb.go +++ b/api/proto/wallet/v1/wallet_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.27.3 +// - protoc v5.29.3 // source: proto/wallet/v1/wallet.proto package walletv1 @@ -54,6 +54,7 @@ const ( WalletService_GetMyVip_FullMethodName = "/hyapp.wallet.v1.WalletService/GetMyVip" WalletService_PurchaseVip_FullMethodName = "/hyapp.wallet.v1.WalletService/PurchaseVip" WalletService_CreditTaskReward_FullMethodName = "/hyapp.wallet.v1.WalletService/CreditTaskReward" + WalletService_ApplyGameCoinChange_FullMethodName = "/hyapp.wallet.v1.WalletService/ApplyGameCoinChange" ) // WalletServiceClient is the client API for WalletService service. @@ -97,6 +98,7 @@ type WalletServiceClient interface { GetMyVip(ctx context.Context, in *GetMyVipRequest, opts ...grpc.CallOption) (*GetMyVipResponse, error) PurchaseVip(ctx context.Context, in *PurchaseVipRequest, opts ...grpc.CallOption) (*PurchaseVipResponse, error) CreditTaskReward(ctx context.Context, in *CreditTaskRewardRequest, opts ...grpc.CallOption) (*CreditTaskRewardResponse, error) + ApplyGameCoinChange(ctx context.Context, in *ApplyGameCoinChangeRequest, opts ...grpc.CallOption) (*ApplyGameCoinChangeResponse, error) } type walletServiceClient struct { @@ -457,6 +459,16 @@ func (c *walletServiceClient) CreditTaskReward(ctx context.Context, in *CreditTa return out, nil } +func (c *walletServiceClient) ApplyGameCoinChange(ctx context.Context, in *ApplyGameCoinChangeRequest, opts ...grpc.CallOption) (*ApplyGameCoinChangeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ApplyGameCoinChangeResponse) + err := c.cc.Invoke(ctx, WalletService_ApplyGameCoinChange_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // WalletServiceServer is the server API for WalletService service. // All implementations must embed UnimplementedWalletServiceServer // for forward compatibility. @@ -498,6 +510,7 @@ type WalletServiceServer interface { GetMyVip(context.Context, *GetMyVipRequest) (*GetMyVipResponse, error) PurchaseVip(context.Context, *PurchaseVipRequest) (*PurchaseVipResponse, error) CreditTaskReward(context.Context, *CreditTaskRewardRequest) (*CreditTaskRewardResponse, error) + ApplyGameCoinChange(context.Context, *ApplyGameCoinChangeRequest) (*ApplyGameCoinChangeResponse, error) mustEmbedUnimplementedWalletServiceServer() } @@ -613,6 +626,9 @@ func (UnimplementedWalletServiceServer) PurchaseVip(context.Context, *PurchaseVi func (UnimplementedWalletServiceServer) CreditTaskReward(context.Context, *CreditTaskRewardRequest) (*CreditTaskRewardResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CreditTaskReward not implemented") } +func (UnimplementedWalletServiceServer) ApplyGameCoinChange(context.Context, *ApplyGameCoinChangeRequest) (*ApplyGameCoinChangeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ApplyGameCoinChange not implemented") +} func (UnimplementedWalletServiceServer) mustEmbedUnimplementedWalletServiceServer() {} func (UnimplementedWalletServiceServer) testEmbeddedByValue() {} @@ -1264,6 +1280,24 @@ func _WalletService_CreditTaskReward_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _WalletService_ApplyGameCoinChange_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplyGameCoinChangeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).ApplyGameCoinChange(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_ApplyGameCoinChange_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).ApplyGameCoinChange(ctx, req.(*ApplyGameCoinChangeRequest)) + } + return interceptor(ctx, in, info, handler) +} + // WalletService_ServiceDesc is the grpc.ServiceDesc for WalletService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -1411,6 +1445,10 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{ MethodName: "CreditTaskReward", Handler: _WalletService_CreditTaskReward_Handler, }, + { + MethodName: "ApplyGameCoinChange", + Handler: _WalletService_ApplyGameCoinChange_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/wallet/v1/wallet.proto", diff --git a/deploy/mysql/initdb/999_local_grants.sql b/deploy/mysql/initdb/999_local_grants.sql index 3103b279..ff7c6833 100644 --- a/deploy/mysql/initdb/999_local_grants.sql +++ b/deploy/mysql/initdb/999_local_grants.sql @@ -8,5 +8,6 @@ GRANT ALL PRIVILEGES ON hyapp_wallet.* TO 'hyapp'@'%'; GRANT ALL PRIVILEGES ON hyapp_activity.* TO 'hyapp'@'%'; GRANT ALL PRIVILEGES ON hyapp_admin.* TO 'hyapp'@'%'; GRANT ALL PRIVILEGES ON hyapp_cron.* TO 'hyapp'@'%'; +GRANT ALL PRIVILEGES ON hyapp_game.* TO 'hyapp'@'%'; FLUSH PRIVILEGES; diff --git a/docker-compose.yml b/docker-compose.yml index e35baf09..f66ac9fb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,8 @@ services: condition: service_healthy user-service: condition: service_healthy + game-service: + condition: service_healthy redis: condition: service_healthy healthcheck: @@ -141,6 +143,30 @@ services: retries: 20 start_period: 10s + game-service: + build: + context: . + dockerfile: services/game-service/Dockerfile + args: *go-build-args + container_name: game-service + environment: + TZ: UTC + ports: + - "13008:13008" + depends_on: + mysql: + condition: service_healthy + wallet-service: + condition: service_healthy + user-service: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "/app/grpc-health-probe -addr=127.0.0.1:13008 -service=game-service"] + interval: 5s + timeout: 3s + retries: 20 + start_period: 10s + mysql: image: mysql:8.4 container_name: hyapp-mysql @@ -166,6 +192,7 @@ services: - ./deploy/mysql/initdb/006_admin_database.sql:/docker-entrypoint-initdb.d/006_admin_database.sql:ro - ./deploy/mysql/initdb/007_resource_group_wallet_asset_items.sql:/docker-entrypoint-initdb.d/007_resource_group_wallet_asset_items.sql:ro - ./services/cron-service/deploy/mysql/initdb/001_cron_service.sql:/docker-entrypoint-initdb.d/008_cron_service.sql:ro + - ./services/game-service/deploy/mysql/initdb/001_game_service.sql:/docker-entrypoint-initdb.d/009_game_service.sql:ro - ./deploy/mysql/initdb/999_local_grants.sql:/docker-entrypoint-initdb.d/999_local_grants.sql:ro healthcheck: test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -proot --silent"] diff --git a/docs/接口清单.md b/docs/接口清单.md index a160bb58..78007cc5 100644 --- a/docs/接口清单.md +++ b/docs/接口清单.md @@ -21,6 +21,9 @@ | GET | `/api/v1/im/usersig` | tencent-im | `issueTencentIMUserSig` | 获取腾讯云 IM UserSig | | POST | `/api/v1/rtc/token` | tencent-rtc | `issueTencentRTCToken` | 获取腾讯 RTC 进房 UserSig | | POST | `/api/v1/tencent-rtc/callback` | tencent-rtc | `handleTencentRTCCallback` | 腾讯 RTC 服务端事件回调 | +| GET | `/api/v1/games` | games | `listGames` | 获取语音房合并游戏列表 | +| POST | `/api/v1/games/{game_id}/launch` | games | `launchGame` | 创建 H5 游戏启动会话 | +| POST | `/api/v1/game-callbacks/{platform_code}/{operation}` | games | `handleGameCallback` | 第三方游戏平台公网回调 | | POST | `/api/v1/files/upload` | files | `uploadFile` | 上传 App 通用文件 | | POST | `/api/v1/files/avatar/upload` | files | `uploadAvatar` | 上传用户头像 | | POST | `/api/v1/devices/push-token` | devices | `bindPushToken` | 绑定设备推送 token | @@ -28,6 +31,7 @@ | POST | `/api/v1/tencent-im/callback` | tencent-im | `handleTencentIMCallback` | 腾讯云 IM 服务端回调 | | GET | `/api/v1/users/by-display-user-id/{display_user_id}` | users | `resolveDisplayUserID` | 通过当前展示短号解析用户 | | GET | `/api/v1/users/profiles:batch` | users | `batchUserProfiles` | 批量查询会话展示资料 | +| GET | `/api/v1/users/room-display-profiles:batch` | users | `batchRoomDisplayProfiles` | 批量查询房间和公屏展示资料 | | GET | `/api/v1/users/me/identity` | users | `getMyIdentity` | 查询当前用户展示短号状态 | | GET | `/api/v1/users/me/host-identity` | users | `getMyHostIdentity` | 查询当前用户 Host/Agency/BD 身份 | | GET | `/api/v1/users/me/overview` | users | `getMyOverview` | 我的页首屏聚合摘要 | @@ -52,15 +56,20 @@ | GET | `/api/v1/rooms/feeds` | rooms | `listRoomFeeds` | Mine 页 Visited/Friend/Following 房间流 | | GET | `/api/v1/rooms/current` | rooms | `getCurrentRoom` | 查询当前可恢复房间 | | GET | `/api/v1/rooms/snapshot` | rooms | `getRoomSnapshot` | 查询房间完整快照 | +| GET | `/api/v1/rooms/{room_id}/detail` | rooms | `getRoomDetail` | 查询房间详情页首屏聚合包 | +| GET | `/api/v1/rooms/{room_id}/online-users` | rooms | `listRoomOnlineUsers` | 分页查询房间在线用户 | +| GET | `/api/v1/rooms/{room_id}/gift-panel` | rooms | `getRoomGiftPanel` | 查询房间礼物面板初始化数据 | | POST | `/api/v1/rooms/create` | rooms | `createRoom` | 创建房间 | | POST | `/api/v1/rooms/profile/update` | rooms | `updateRoomProfile` | 修改房间资料和座位数 | | POST | `/api/v1/rooms/join` | rooms | `joinRoom` | 加入房间并返回首屏数据和 RTC token | | POST | `/api/v1/rooms/heartbeat` | rooms | `roomHeartbeat` | 刷新房间业务 presence | | POST | `/api/v1/rooms/leave` | rooms | `leaveRoom` | 离开房间 | +| POST | `/api/v1/rooms/close` | rooms | `closeRoom` | 设计稿电源按钮:离开房间但不关闭房间 | | POST | `/api/v1/rooms/mic/up` | rooms | `micUp` | 上麦 | | POST | `/api/v1/rooms/mic/down` | rooms | `micDown` | 下麦 | | POST | `/api/v1/rooms/mic/change` | rooms | `changeMicSeat` | 调整麦位 | | POST | `/api/v1/rooms/mic/publishing/confirm` | rooms | `confirmMicPublishing` | 确认 RTC 音频发布成功 | +| POST | `/api/v1/rooms/mic/mute` | rooms | `setMicMute` | 设置服务端可见麦克风静音状态 | | POST | `/api/v1/rooms/mic/lock` | rooms | `setMicSeatLock` | 锁定或解锁麦位 | | POST | `/api/v1/rooms/chat/enabled` | rooms | `setChatEnabled` | 开启或关闭房间公屏 | | POST | `/api/v1/rooms/admin/set` | rooms | `setRoomAdmin` | 添加或移除房间管理员 | @@ -133,6 +142,13 @@ | PUT | `/api/v1/admin/gifts/{gift_id}` | admin | `updateGift` | updateGift | | POST | `/api/v1/admin/gifts/{gift_id}/disable` | admin | `disableGift` | disableGift | | POST | `/api/v1/admin/gifts/{gift_id}/enable` | admin | `enableGift` | enableGift | +| GET | `/api/v1/admin/game/platforms` | admin | `listGamePlatforms` | 查询游戏平台配置 | +| POST | `/api/v1/admin/game/platforms` | admin | `upsertGamePlatform` | 创建或更新游戏平台 | +| PATCH | `/api/v1/admin/game/platforms/{platform_code}` | admin | `updateGamePlatform` | 更新游戏平台 | +| GET | `/api/v1/admin/game/games` | admin | `listGameCatalog` | 查询游戏目录 | +| POST | `/api/v1/admin/game/games` | admin | `upsertGameCatalog` | 创建或更新游戏目录 | +| PATCH | `/api/v1/admin/game/games/{game_id}` | admin | `updateGameCatalog` | 更新游戏目录 | +| PATCH | `/api/v1/admin/game/games/{game_id}/status` | admin | `setGameStatus` | 设置游戏状态 | | GET | `/api/v1/admin/hosts` | admin | `listHosts` | listHosts | | GET | `/api/v1/admin/payment/recharge-bills` | admin | `listRechargeBills` | listRechargeBills | | GET | `/api/v1/admin/regions` | admin | `listRegions` | listRegions | @@ -286,8 +302,19 @@ | POST | `/grpc.health.v1.Health/Check` | health | `roomHealthCheck` | 标准 gRPC health check | | POST | `/grpc.health.v1.Health/Watch` | health | `roomHealthWatch` | 标准 gRPC health watch | | POST | `/hyapp.wallet.v1.WalletService/DebitGift` | wallet | `walletDebitGift` | 送礼扣费 | +| POST | `/hyapp.wallet.v1.WalletService/ApplyGameCoinChange` | wallet | `walletApplyGameCoinChange` | 游戏专用金币改账 | | POST | `/grpc.health.v1.Health/Check` | health | `walletHealthCheck` | 标准 gRPC health check | | POST | `/grpc.health.v1.Health/Watch` | health | `walletHealthWatch` | 标准 gRPC health watch | +| POST | `/hyapp.game.v1.GameAppService/ListGames` | games | `gameListGames` | App 合并游戏列表 | +| POST | `/hyapp.game.v1.GameAppService/LaunchGame` | games | `gameLaunchGame` | 创建 H5 启动会话 | +| POST | `/hyapp.game.v1.GameCallbackService/HandleCallback` | games | `gameHandleCallback` | 处理平台原始回调 | +| POST | `/hyapp.game.v1.GameAdminService/ListPlatforms` | games-admin | `gameAdminListPlatforms` | 查询游戏平台配置 | +| POST | `/hyapp.game.v1.GameAdminService/UpsertPlatform` | games-admin | `gameAdminUpsertPlatform` | 创建或更新游戏平台 | +| POST | `/hyapp.game.v1.GameAdminService/ListCatalog` | games-admin | `gameAdminListCatalog` | 查询游戏目录 | +| POST | `/hyapp.game.v1.GameAdminService/UpsertCatalog` | games-admin | `gameAdminUpsertCatalog` | 创建或更新游戏目录 | +| POST | `/hyapp.game.v1.GameAdminService/SetGameStatus` | games-admin | `gameAdminSetGameStatus` | 设置游戏状态 | +| POST | `/grpc.health.v1.Health/Check` | health | `gameHealthCheck` | 标准 gRPC health check | +| POST | `/grpc.health.v1.Health/Watch` | health | `gameHealthWatch` | 标准 gRPC health watch | | POST | `/hyapp.activity.v1.ActivityService/PingActivity` | activity | `activityPingActivity` | 活动服务连通性探测 | | POST | `/hyapp.activity.v1.ActivityService/GetActivityStatus` | activity | `activityGetActivityStatus` | 查询活动状态 | | POST | `/hyapp.activity.v1.MessageInboxService/ListMessageTabs` | messages | `activityListMessageTabs` | 查询 App 消息 tab 摘要 | diff --git a/docs/游戏接入架构.md b/docs/游戏接入架构.md new file mode 100644 index 00000000..fee89c12 --- /dev/null +++ b/docs/游戏接入架构.md @@ -0,0 +1,574 @@ +# 游戏接入架构 + +本文定义语音房 H5 弹窗游戏的服务边界、平台接入、金币改账、App 游戏列表、后台管理和补单对账方案。游戏可以来自百顺、灵仙、即构、小游等多个平台,但 App 侧看到的是一个合并后的游戏列表。 + +## 目标 + +- 在语音房内打开 H5 弹窗游戏,支持多个第三方游戏平台并行接入。 +- App 获取统一游戏列表,不关心游戏来自哪个平台,但返回字段必须包含 `platform_code` 便于排查和埋点。 +- 第三方平台回调可以获取用户信息、查询余额、变更用户金币和补单。 +- 后台增加一级菜单 `游戏管理`,二级菜单 `游戏列表`,列表按平台筛选和维护。 +- 所有金币变化仍由 `wallet-service` 落账,游戏平台、gateway、admin 和 room-service 都不能直接改金币余额。 + +## 非目标 + +- 不把游戏状态放进 Room Cell。房间只负责展示入口、房间上下文和必要的 IM 通知。 +- 不让第三方平台直接访问 wallet-service 或 user-service。 +- 不用 Redis 保存金币、订单或补单事实。Redis 只可用于短期 launch token、限流和幂等热缓存。 +- 不把不同平台的协议字段直接扩散到 App、room-service 或 wallet-service。 + +## 服务边界 + +新增 `game-service`,端口建议使用 `13008`,MySQL 数据库建议使用 `hyapp_game`。 + +| 服务 | 职责 | +|---|---| +| `gateway-service` | App HTTP 入口;游戏列表、启动游戏、第三方平台公网回调入口;鉴权、协议转换、原始请求体透传。 | +| `game-service` | 游戏平台配置、游戏目录、展示规则、H5 启动会话、平台回调验签、平台协议适配、游戏订单、补单和对账任务 owner。 | +| `wallet-service` | 用户 `COIN` 的唯一 owner;处理游戏扣款、派奖、退款、补单入账和幂等流水。 | +| `user-service` | 用户基础资料 owner;向 game-service 提供平台回调需要的用户资料投影。 | +| `room-service` | 语音房状态 owner;只提供 `room_id` 上下文和可选的在房校验,不处理游戏订单或金币。 | +| `server/admin` | 后台 RBAC、菜单、审计和后台 HTTP API;通过 game-service gRPC 管理平台和游戏配置。 | +| `cron-service` | 只触发 game-service 的补单/对账批处理,不直接扫游戏表或修改钱包。 | + +```mermaid +flowchart LR + App["App voice room"] -->|"GET /api/v1/games"| Gateway["gateway-service"] + App -->|"POST /api/v1/games/{game_id}/launch"| Gateway + Provider["game platform callback"] -->|"provider HTTP callback"| Gateway + Admin["admin frontend"] -->|"admin HTTP"| AdminServer["server/admin"] + + Gateway -->|"gRPC"| Game["game-service"] + AdminServer -->|"gRPC"| Game + Game -->|"gRPC ApplyGameCoinChange"| Wallet["wallet-service"] + Game -->|"gRPC user profile"| User["user-service"] + Game -. optional .->|"validate room context"| Room["room-service"] + + Game --> GameDB[("hyapp_game")] + Wallet --> WalletDB[("hyapp_wallet")] +``` + +## 平台适配模型 + +每个平台实现一个 adapter,把第三方协议转换成内部统一命令。 + +```go +type PlatformAdapter interface { + VerifyCallback(rawBody []byte, headers map[string]string, query map[string]string, secret PlatformSecret) error + ParseOperation(path string, rawBody []byte) (ProviderOperation, error) + BuildResponse(result ProviderResult) ([]byte, string, error) + BuildLaunchURL(session LaunchSession, user GameUser, game GameConfig) (string, error) +} +``` + +内部统一操作只保留业务语义: + +| 内部操作 | 说明 | +|---|---| +| `get_user_info` | 平台获取用户 ID、昵称、头像、余额等启动或运行信息。 | +| `get_balance` | 平台查询用户当前金币余额。 | +| `change_coin` | 平台发起扣款、派奖、退款、冲正等金币变化。 | +| `repair_order` | 平台或后台补单,重放遗漏订单或修复平台异常订单。 | +| `query_order` | 平台查询内部订单处理结果。 | + +平台差异只能停留在 adapter 层。进入 game-service 领域层以后,所有金额单位、订单 ID、用户 ID、游戏 ID、状态机和错误码都必须统一。 + +## 数据模型 + +### game_platforms + +游戏平台配置表。 + +```sql +game_platforms( + app_code VARCHAR(32) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + platform_name VARCHAR(128) NOT NULL, + status VARCHAR(32) NOT NULL, + api_base_url VARCHAR(512) NOT NULL DEFAULT '', + callback_secret_ciphertext VARCHAR(1024) NOT NULL DEFAULT '', + callback_ip_whitelist JSON NULL, + adapter_config JSON NULL, + sort_order INT NOT NULL DEFAULT 0, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, platform_code) +); +``` + +`status` 建议取值: + +| 状态 | 语义 | +|---|---| +| `active` | 可展示、可启动、可接收回调。 | +| `maintenance` | App 可展示维护态,不允许启动新会话;已发生订单仍可接收回调。 | +| `disabled` | 不展示、不允许启动;回调只允许查询历史订单或按配置拒绝。 | + +### game_catalog + +第三方游戏目录表,一条记录对应一个平台下的一个游戏。 + +```sql +game_catalog( + app_code VARCHAR(32) NOT NULL, + game_id VARCHAR(96) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + provider_game_id VARCHAR(128) NOT NULL, + game_name VARCHAR(128) NOT NULL, + category VARCHAR(64) NOT NULL DEFAULT '', + icon_url VARCHAR(512) NOT NULL DEFAULT '', + cover_url VARCHAR(512) NOT NULL DEFAULT '', + launch_mode VARCHAR(32) NOT NULL DEFAULT 'h5_popup', + orientation VARCHAR(32) NOT NULL DEFAULT 'portrait', + min_coin BIGINT NOT NULL DEFAULT 0, + status VARCHAR(32) NOT NULL, + sort_order INT NOT NULL DEFAULT 0, + tags JSON NULL, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, game_id), + UNIQUE KEY uk_game_provider(app_code, platform_code, provider_game_id), + KEY idx_game_platform_status(app_code, platform_code, status, sort_order) +); +``` + +`game_id` 是内部稳定 ID,App、订单和补单都使用它;`provider_game_id` 只在 adapter 和平台请求中使用。 + +### game_display_rules + +控制游戏在 App 内的展示范围。 + +```sql +game_display_rules( + app_code VARCHAR(32) NOT NULL, + rule_id VARCHAR(96) NOT NULL, + game_id VARCHAR(96) NOT NULL, + scene VARCHAR(64) NOT NULL, + region_id BIGINT NOT NULL DEFAULT 0, + language VARCHAR(32) NOT NULL DEFAULT '', + platform VARCHAR(16) NOT NULL DEFAULT '', + visible TINYINT(1) NOT NULL DEFAULT 1, + enabled TINYINT(1) NOT NULL DEFAULT 1, + sort_order INT NOT NULL DEFAULT 0, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, rule_id), + KEY idx_game_display(app_code, scene, region_id, visible, sort_order) +); +``` + +`scene` 当前先支持 `voice_room`,后续可以扩展首页、活动页或个人中心入口。`region_id=0` 表示全区域默认规则,具体区域规则优先级高于默认规则。 + +### game_launch_sessions + +启动会话表,绑定用户、房间、游戏和平台短期令牌。 + +```sql +game_launch_sessions( + app_code VARCHAR(32) NOT NULL, + session_id VARCHAR(96) NOT NULL, + user_id BIGINT NOT NULL, + display_user_id VARCHAR(32) NOT NULL, + room_id VARCHAR(96) NOT NULL DEFAULT '', + scene VARCHAR(64) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + game_id VARCHAR(96) NOT NULL, + provider_game_id VARCHAR(128) NOT NULL, + launch_token_hash VARCHAR(128) NOT NULL, + status VARCHAR(32) NOT NULL, + expires_at_ms BIGINT NOT NULL, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, session_id), + KEY idx_game_launch_user(app_code, user_id, created_at_ms), + KEY idx_game_launch_token(app_code, launch_token_hash) +); +``` + +H5 URL 里只放短期 `session_token` 或 provider 需要的签名参数,不直接暴露钱包能力。会话过期后不允许继续用它拉用户信息,但历史订单仍按 provider order id 幂等处理。 + +### game_orders + +游戏金币变化订单表。 + +```sql +game_orders( + app_code VARCHAR(32) NOT NULL, + order_id VARCHAR(96) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + provider_order_id VARCHAR(160) NOT NULL, + provider_round_id VARCHAR(160) NOT NULL DEFAULT '', + game_id VARCHAR(96) NOT NULL, + provider_game_id VARCHAR(128) NOT NULL, + user_id BIGINT NOT NULL, + room_id VARCHAR(96) NOT NULL DEFAULT '', + op_type VARCHAR(32) NOT NULL, + coin_amount BIGINT NOT NULL, + status VARCHAR(32) NOT NULL, + wallet_transaction_id VARCHAR(96) NOT NULL DEFAULT '', + wallet_balance_after BIGINT NOT NULL DEFAULT 0, + request_hash VARCHAR(128) NOT NULL, + raw_payload_ref VARCHAR(256) NOT NULL DEFAULT '', + failure_code VARCHAR(64) NOT NULL DEFAULT '', + failure_message VARCHAR(256) NOT NULL DEFAULT '', + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, order_id), + UNIQUE KEY uk_game_provider_order(app_code, platform_code, provider_order_id), + KEY idx_game_order_user_time(app_code, user_id, created_at_ms), + KEY idx_game_order_round(app_code, platform_code, provider_round_id) +); +``` + +`op_type` 建议取值: + +| 类型 | 钱包语义 | +|---|---| +| `debit` | 用户下注或购买局内道具,扣 `COIN`。 | +| `credit` | 用户中奖或平台派奖,加 `COIN`。 | +| `refund` | 扣款后失败退款,加 `COIN`,必须关联原订单。 | +| `reverse` | 平台冲正,按平台规则转成扣款或退款,不允许直接传负数。 | + +订单状态机: + +```mermaid +stateDiagram-v2 + [*] --> received + received --> wallet_applying + wallet_applying --> succeeded + wallet_applying --> failed + wallet_applying --> conflict + succeeded --> refunded + succeeded --> reversed + failed --> repair_pending + repair_pending --> wallet_applying +``` + +### game_callback_logs + +回调日志表用于审计、排错和补偿,不作为金币事实。 + +```sql +game_callback_logs( + app_code VARCHAR(32) NOT NULL, + callback_id VARCHAR(96) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + operation VARCHAR(64) NOT NULL, + request_id VARCHAR(96) NOT NULL DEFAULT '', + provider_request_id VARCHAR(160) NOT NULL DEFAULT '', + signature_valid TINYINT(1) NOT NULL DEFAULT 0, + request_hash VARCHAR(128) NOT NULL, + response_code VARCHAR(64) NOT NULL DEFAULT '', + response_body_hash VARCHAR(128) NOT NULL DEFAULT '', + status VARCHAR(32) NOT NULL, + created_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, callback_id), + KEY idx_game_callback_platform_time(app_code, platform_code, created_at_ms) +); +``` + +原始请求体如果较大,存对象存储或压缩表;主表只保留 hash 和引用,避免日志表膨胀影响查询。 + +### game_repair_orders + +补单任务表,来源可以是平台回调、后台手动、对账任务。 + +```sql +game_repair_orders( + app_code VARCHAR(32) NOT NULL, + repair_id VARCHAR(96) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + provider_order_id VARCHAR(160) NOT NULL, + reason VARCHAR(128) NOT NULL, + status VARCHAR(32) NOT NULL, + operator_admin_id BIGINT NOT NULL DEFAULT 0, + attempts INT NOT NULL DEFAULT 0, + next_run_at_ms BIGINT NOT NULL DEFAULT 0, + last_error_code VARCHAR(64) NOT NULL DEFAULT '', + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, repair_id), + UNIQUE KEY uk_game_repair_provider_order(app_code, platform_code, provider_order_id) +); +``` + +补单只能重放到 `game_orders` 和 `wallet-service` 幂等命令,不能直接 `UPDATE wallet_accounts`。 + +## 钱包改账 + +钱包底层统一表达为“用户金币增加或减少 + 业务类型”,最终都落到 `wallet_transactions` 和 `wallet_entries`。RPC 层不要暴露裸的通用 `ChangeCoin(user_id, amount, type)` 给所有业务直接调用,而是按业务场景封装入口;游戏场景固定由 game-service 调 wallet-service 的 `ApplyGameCoinChange`,wallet-service 内部再转换成统一账本分录。 + +game-service 调 wallet-service 新增内部 RPC: + +```proto +message ApplyGameCoinChangeRequest { + RequestMeta meta = 1; + string command_id = 2; + int64 user_id = 3; + string platform_code = 4; + string game_id = 5; + string provider_order_id = 6; + string provider_round_id = 7; + string op_type = 8; + int64 coin_amount = 9; + string room_id = 10; + string request_hash = 11; +} + +message ApplyGameCoinChangeResponse { + string wallet_transaction_id = 1; + int64 balance_after = 2; + bool idempotent_replay = 3; +} +``` + +`command_id` 固定为: + +```text +game:{platform_code}:{provider_order_id} +``` + +钱包侧要求: + +- `coin_amount` 必须是正整数,方向由 `op_type` 决定。 +- `op_type` 只表达业务方向,最终统计口径落在 `wallet_transactions.biz_type` 和 `wallet_entries.available_delta`。 +- `app_code + command_id` 唯一幂等,同一命令重复请求返回同一结果。 +- 同一 `command_id` 携带不同 `request_hash` 必须返回冲突,不允许二次改账。 +- `debit` 余额不足时不写成功交易、不写分录、不写 outbox。 +- `credit/refund/reverse` 必须写 `wallet_transactions`、`wallet_entries` 和 `wallet_outbox`。 +- 游戏账务 `biz_type` 至少包括 `game_debit`、`game_credit`、`game_refund`、`game_reverse`。 + +统一账本落账示例: + +| 游戏动作 | `op_type` | `wallet_transactions.biz_type` | `wallet_entries.asset_type` | `wallet_entries.available_delta` | +|---|---|---|---|---:| +| 下注扣金币 | `debit` | `game_debit` | `COIN` | `-coin_amount` | +| 中奖派奖 | `credit` | `game_credit` | `COIN` | `+coin_amount` | +| 失败退款 | `refund` | `game_refund` | `COIN` | `+coin_amount` | +| 平台冲正 | `reverse` | `game_reverse` | `COIN` | 按冲正规则转换 | + +用户金币收支统计只查 `wallet_entries`: + +```sql +SELECT biz_type, SUM(available_delta) AS coin_delta +FROM wallet_entries +WHERE app_code = ? + AND user_id = ? + AND asset_type = 'COIN' + AND created_at_ms >= ? + AND created_at_ms < ? +GROUP BY biz_type; +``` + +## App 接口 + +### 获取合并游戏列表 + +```http +GET /api/v1/games?scene=voice_room&room_id={room_id} +``` + +返回: + +```json +{ + "games": [ + { + "game_id": "bs_rocket_001", + "platform_code": "baishun", + "name_key": "game.bs_rocket_001.name", + "name": "Rocket", + "icon_url": "https://cdn.example.com/game/rocket.png", + "cover_url": "https://cdn.example.com/game/rocket-cover.png", + "category": "casual", + "launch_mode": "h5_popup", + "orientation": "portrait", + "min_coin": 100, + "enabled": true, + "maintenance": false, + "sort_order": 10 + } + ], + "server_time_ms": 1778490000000 +} +``` + +列表聚合规则: + +- 只返回 `game_platforms.status in active/maintenance` 且 `game_catalog.status in active/maintenance` 的游戏。 +- `maintenance=true` 时可以展示但不能启动。 +- 根据用户区域、语言、平台和 `scene` 过滤 `game_display_rules`。 +- 返回是多个平台合并排序,不按平台分组;后台才按平台筛选。 + +### 启动游戏 + +```http +POST /api/v1/games/{game_id}/launch +``` + +请求: + +```json +{ + "scene": "voice_room", + "room_id": "room_123" +} +``` + +返回: + +```json +{ + "session_id": "game_sess_1778490000_abcd", + "launch_url": "https://provider.example.com/h5?...", + "expires_at_ms": 1778490900000, + "orientation": "portrait" +} +``` + +启动时校验: + +- 用户必须已登录且 onboarding completed。 +- 游戏和平台不能是 `disabled`。 +- `maintenance` 状态拒绝启动。 +- 如果 `scene=voice_room` 且产品要求必须在房间内,game-service 通过 room-service 做只读校验;校验失败不创建启动会话。 + +## 第三方平台回调 + +公网入口统一放 gateway-service: + +```http +POST /api/v1/game-callbacks/{platform_code}/{operation} +``` + +gateway 行为: + +- 保留原始 body、query、header,不提前解析成统一 JSON。 +- 生成或透传 `request_id`。 +- 按 `platform_code` 转发给 game-service。 +- 回调响应不套 App 业务 envelope,因为第三方通常要求固定响应格式。 + +game-service 行为: + +1. 读取平台配置和 adapter。 +2. 验签、IP 白名单、时间戳/nonce 防重放。 +3. 写 `game_callback_logs`。 +4. adapter 解析为内部 `ProviderOperation`。 +5. 需要用户资料时读 user-service;需要余额或改账时读/写 wallet-service。 +6. 写 `game_orders` 或 `game_repair_orders`。 +7. adapter 构造平台要求的响应。 + +## 补单和对账 + +补单来源: + +- 平台主动补单回调。 +- 后台管理员按 `platform_code + provider_order_id` 手动补单。 +- cron-service 触发 game-service 扫描 `game_repair_orders`。 +- 后续平台支持订单查询时,由 game-service 拉取平台订单对账。 + +补单约束: + +- 补单必须复用原始 `provider_order_id` 生成同一个钱包 `command_id`。 +- 钱包幂等命中时视为补单成功,但必须记录 `idempotent_replay=true`。 +- 原订单请求 hash 不一致时进入 `conflict`,需要人工处理。 +- 后台手动补单必须写 admin 审计,包含操作人、原因和平台订单号。 + +## 后台管理 + +新增一级菜单: + +```text +游戏管理 +└── 游戏列表 +``` + +游戏列表默认按平台 tab 或筛选展示: + +| 字段 | 说明 | +|---|---| +| 平台 | 百顺、灵仙、即构、小游等 `platform_code/platform_name`。 | +| 游戏 ID | 内部 `game_id` 和平台 `provider_game_id`。 | +| 名称/图标 | App 展示资源。 | +| 类目 | casual、slot、board 等运营类目。 | +| 状态 | active、maintenance、disabled。 | +| 展示范围 | scene、区域、语言、端。 | +| 最低金币 | App 展示和启动前提示用。 | +| 排序 | 合并列表排序。 | +| 回调状态 | 最近回调成功率、最近错误码。 | + +后台接口由 `server/admin` 提供: + +```http +GET /api/v1/admin/game/platforms +POST /api/v1/admin/game/platforms +PATCH /api/v1/admin/game/platforms/{platform_code} +GET /api/v1/admin/game/games?platform_code=baishun +POST /api/v1/admin/game/games +PATCH /api/v1/admin/game/games/{game_id} +PATCH /api/v1/admin/game/games/{game_id}/status +GET /api/v1/admin/game/orders +GET /api/v1/admin/game/callback-logs +POST /api/v1/admin/game/repair-orders +``` + +`server/admin` 只做权限、审计和后台协议;平台配置和游戏目录事实仍写入 game-service。 + +当前第一期已落地平台配置和游戏目录维护接口,订单查询、回调日志查询和手动补单入口随补单状态机一起实现。 + +## 并发与可靠性 + +- game-service 可以多实例无状态部署;订单事实在 MySQL,短期 session 可同时写 MySQL 和 Redis。 +- 同一用户多游戏并发改金币时,由 wallet-service 对 `wallet_accounts(app_code,user_id,COIN)` 行锁和命令幂等保证余额正确。 +- 平台重复回调必须返回幂等结果,不允许重复扣款或重复派奖。 +- 平台回调乱序时,game-service 只按 provider order id 和 round id 记录事实;是否允许先派奖后扣款由平台 adapter 配置决定。 +- game_callback_logs 不能作为成功事实;成功事实是 `game_orders.status=succeeded` 加 wallet 成功交易。 +- game-service outbox 只发布游戏订单完成、补单完成等事件;钱包余额变化仍以 wallet outbox 为准。 + +## 安全要求 + +- 每个平台必须独立 secret,支持轮换;后台只能看到 masked secret。 +- 回调验签使用原始 body,不使用重新序列化后的 JSON。 +- 支持 IP 白名单,但 IP 白名单失败不能替代签名校验。 +- 请求时间戳和 nonce 防重放;nonce 可短期放 Redis,最终仍以 provider order id 幂等兜底。 +- H5 启动 token 短有效期,绑定 `app_code/user_id/game_id/platform_code/room_id`。 +- 所有 provider 回调、补单、后台状态变更必须写审计日志。 +- 金币金额使用整数,不允许 float,不允许负数金额表达方向。 + +## 监控指标 + +| 指标 | 说明 | +|---|---| +| `game_callback_total{platform,operation,status}` | 平台回调量和状态。 | +| `game_callback_verify_failed_total{platform}` | 验签失败和白名单失败。 | +| `game_order_total{platform,op_type,status}` | 游戏订单状态。 | +| `game_wallet_apply_duration_ms{platform,op_type}` | 调钱包耗时。 | +| `game_wallet_conflict_total{platform}` | 幂等冲突数量。 | +| `game_repair_pending_total{platform}` | 待补单数量。 | +| `game_launch_total{platform,game_id,status}` | 游戏启动量和失败原因。 | + +## 落地顺序 + +1. 新增 `game-service` 空服务、`hyapp_game` schema、配置和 docker-compose 端口 `13008`。 +2. 定义 `api/proto/game/v1/game.proto`,包含 App 列表、启动、后台管理和回调处理 RPC。 +3. wallet-service 增加 `ApplyGameCoinChange` RPC 和 `game_*` 账务类型测试。 +4. game-service 实现平台配置、游戏目录、展示规则和 App 合并列表。 +5. game-service 实现 H5 launch session,gateway 暴露 `/api/v1/games` 和 `/api/v1/games/{game_id}/launch`。 +6. 实现第一个平台 adapter,先打通 `get_user_info/get_balance/change_coin`。 +7. 增加 `game_orders`、`game_callback_logs`、补单幂等和冲突处理。 +8. server/admin 增加 `游戏管理 / 游戏列表`,接入 game-service 管理接口。 +9. 增加补单后台入口和 cron-service 触发的 game-service 补偿批处理。 +10. 接入第二个平台前,固化 adapter 测试基线,避免平台差异污染核心领域层。 + +## 验收清单 + +- App 能在语音房拿到多个平台合并后的游戏列表。 +- 禁用平台、维护平台、禁用游戏、区域不匹配游戏不会被错误启动。 +- 启动游戏返回短期 H5 URL,过期 session 不能再换取用户信息。 +- 平台扣金币成功后 wallet 余额减少,重复回调不重复扣。 +- 平台派奖成功后 wallet 余额增加,重复回调不重复加。 +- 同一 provider order id 不同 payload 返回冲突并进入人工处理。 +- 余额不足时 game_orders 失败,wallet 不写成功交易。 +- 后台可以按平台维护游戏、上下架、排序和查看回调日志。 +- 后台补单只能通过 game-service 和 wallet-service 幂等命令完成,不能直接改余额。 +- cron-service 只触发补单批处理,不直接拥有游戏或钱包状态。 diff --git a/docs/语音房功能清单.md b/docs/语音房功能清单.md index c6e514f7..0ada834b 100644 --- a/docs/语音房功能清单.md +++ b/docs/语音房功能清单.md @@ -40,7 +40,10 @@ | Feature | Status | Current Evidence | Remaining Work | | --- | --- | --- | --- | | `/api/v1` envelope | `DONE` | gateway 业务响应使用 `{code,message,request_id,data}` | healthcheck 不套 envelope,保持现状 | -| 房间命令 HTTP 路由 | `DONE` | create/join/leave/mic/mute/kick/sendGift 路由存在 | OpenAPI 需持续同步最新字段 | +| 房间命令 HTTP 路由 | `DONE` | create/join/leave/close-as-leave/mic/mute/kick/sendGift 路由存在 | OpenAPI 需持续同步最新字段 | +| 房间详情页聚合接口 | `DONE` | `/rooms/{room_id}/detail` 返回 room/viewer/seats/rank_top/online_count/permissions/im/rtc | 头像框、座驾、VIP、等级等展示 read model 仍需接入 | +| 房间在线用户分页 | `DONE` | `/rooms/{room_id}/online-users` 读取 `room_user_presence` 分页投影 | 大页码仍是 offset 分页,超大房间可升级 cursor | +| 房间礼物面板 | `DONE` | `/rooms/{room_id}/gift-panel` 聚合 COIN 余额、收礼人、礼物 tabs、礼物配置和数量预设 | all_mic/all_room/couple 账务拆单策略未开放 | | 房间命令 `command_id` | `DONE` | 创建房间由 gateway 生成内部 `command_id`;其他写命令支持 body `command_id`,未传时 gateway 生成一次性值 | 客户端只在非创建类重试动作复用 command_id | | `request_id` 追踪 | `DONE` | request_id 透传 RequestMeta,不作为幂等键 | 日志和链路追踪系统可继续完善 | | IM UserSig 签发 | `DONE` | `/api/v1/im/usersig` 使用 access token `user_id` 签发 | 真实腾讯云应用联调和告警 | @@ -61,7 +64,7 @@ | 离房 | `DONE` | `LeaveRoom` 幂等,移除 presence,释放麦位,写 outbox | 客户端收到离房/被踢后退出 IM/RTC 需联调 | | stale presence 清理 | `DONE` | `RunPresenceStaleWorker` 和 `SweepStalePresence` 存在 | 多实例下只扫描本节点已装载 Cell,需压测确认 | | 房间恢复 | `DONE` | snapshot + command log + Redis lease 接管恢复测试存在 | 大房间 command log 回放性能和快照策略压测 | -| 房间关闭 | `TODO` | 当前无 CloseRoom 命令 | 需要状态变更、IM/RTC 退出通知、恢复语义 | +| 房间关闭 | `PARTIAL` | room-service 有 CloseRoom 命令;App 设计稿右上角电源按钮当前走 `/rooms/close` 退出自己,不关闭房间 | 真正关闭房间入口只应给房主/后台,需单独确认产品入口 | | 房间列表/发现 | `TODO` | 当前无 list/search/recommend API | 需要房间索引、热度排序、状态过滤 | | 房间基础配置更新 | `TODO` | 当前无修改标题、公告、封面、模式等命令 | 需要定义哪些字段属于 Room Cell,哪些属于房间资料表 | @@ -127,7 +130,7 @@ | --- | --- | --- | --- | | wallet-service 账务表 | `DONE` | `wallet_accounts`、`wallet_transactions`、`wallet_entries`、`wallet_outbox`、`wallet_gift_prices` | 充值、退款、管理员调账未做 | | 礼物扣费幂等 | `DONE` | `DebitGift` 使用 `wallet_transactions.command_id` 幂等,测试覆盖 | 冲突请求告警 | -| SendGift 房间命令 | `DONE` | room-service 先扣费,再更新 heat、gift_rank、outbox | 礼物目标、礼物表现和客户端动画联调 | +| SendGift 房间命令 | `DONE` | room-service 先扣费,再更新 heat、gift_rank、outbox;HTTP 支持 `target_type + target_user_ids`,兼容旧 `target_user_id` | 当前只开放 `target_type=user`,all_mic/all_room/couple 账务拆单和表现策略未做 | | 房间热度 | `DONE` | `RoomSnapshot.heat` 和 `RoomHeatChanged` 事件 | 热度衰减/排行榜周期未做 | | 本地礼物榜 | `DONE` | `LocalRank` 和 `gift_rank` 快照 | 全局榜、跨房榜、榜单重置未做 | | 礼物配置 | `PARTIAL` | `wallet_gift_prices` 提供服务端价格和 `COIN`/`DIAMOND` 收费资产,`gift_configs` 提供礼物类型、有效期和特效;SendGift 请求只传 `gift_id/gift_count`;礼物配置已绑定 `resource_type=gift` 资源 | 客户端礼物表现联调、资源版本缓存刷新未做 | diff --git a/docs/语音房客户端接口流程.md b/docs/语音房客户端接口流程.md index 438f9045..e56ae846 100644 --- a/docs/语音房客户端接口流程.md +++ b/docs/语音房客户端接口流程.md @@ -63,6 +63,9 @@ sequenceDiagram C->>G: GET /api/v1/rooms/snapshot G-->>C: optional active-room resync + C->>G: GET /api/v1/rooms/{room_id}/detail + G-->>C: render-ready room detail package + loop while room page is active C->>G: POST /api/v1/rooms/heartbeat end @@ -80,6 +83,9 @@ sequenceDiagram | 6 | Tencent IM SDK `joinGroup(im_group_id)` | yes | 必须在 JoinRoom 成功后执行,IM 回调守卫会校验 room-service presence | | 7 | Tencent RTC SDK `enterRoom` | yes for voice room audio | 使用 JoinRoom 响应里的 `rtc.token` 进入真实音频频道旁听 | | 8 | `POST /api/v1/rooms/heartbeat` | yes | 刷新业务 presence,避免 stale timeout | +| 9 | `GET /api/v1/rooms/{room_id}/detail` | optional after join/restore | 详情页聚合包;用于 JoinRoom 后补偿刷新、回前台、设计稿首屏重绘 | +| 10 | `GET /api/v1/rooms/{room_id}/gift-panel` | lazy on gift button | 打开礼物面板时再拉,不放进房间首屏 | +| 11 | `GET /api/v1/rooms/{room_id}/online-users` | lazy on online count | 点击右上角在线人数时分页拉取 | 如果 JoinRoom 响应里的 `rtc.available=false`,客户端可以先展示房间页并重试 `POST /api/v1/rtc/token`;RTC 失败不能回滚已经成功的业务 presence。 @@ -207,6 +213,127 @@ Rules: - JoinRoom 成功后才能调用腾讯 IM SDK `joinGroup`;列表里的 `im_group_id` 只用于准备,不代表入群授权。 - JoinRoom 会尝试内嵌签发 RTC token;签发失败时返回 `rtc.available=false`,客户端可以继续展示房间并单独重试 `/api/v1/rtc/token`。 +### Room Detail + +```http +GET /api/v1/rooms/{room_id}/detail +Authorization: Bearer +``` + +Response `data`: + +```json +{ + "room": {}, + "viewer": {}, + "seats": [], + "rank_top": [], + "online_count": 30, + "permissions": {}, + "profiles": [], + "im": {}, + "rtc": {} +} +``` + +Rules: + +- `detail` 只读 Room Cell/snapshot,不刷新 heartbeat,不隐式进房。 +- viewer 必须仍在 room-service presence 内;未进房、被踢或被 ban 返回 `PERMISSION_DENIED`。 +- `permissions.can_close_room` 表示设计稿右上角电源按钮可用,但当前语义是“退出房间”,不是关闭房间生命周期。 +- `profiles` 只返回首屏需要的 owner、host、viewer、麦位用户和榜单 top 用户。完整在线用户资料走在线用户分页或 `users/room-display-profiles:batch`。 + +### Online Users + +```http +GET /api/v1/rooms/{room_id}/online-users?page=1&page_size=50 +Authorization: Bearer +``` + +Response `data`: + +```json +{ + "items": [ + { + "user_id": "10002", + "role": "audience", + "last_seen_at_ms": 1778000000000, + "profile": {} + } + ], + "total": 30, + "page": 1, + "page_size": 50, + "server_time_ms": 1778000000000 +} +``` + +Rules: + +- 该接口读 `room_user_presence` 分页投影,不拉完整 `RoomSnapshot` 做列表分页。 +- `page_size` 默认 50,服务端最大 100。 +- 用户展示资料失败不影响在线列表主数据,客户端可用 `GET /api/v1/users/room-display-profiles:batch` 补偿。 + +### Gift Panel + +```http +GET /api/v1/rooms/{room_id}/gift-panel +Authorization: Bearer +``` + +Response `data`: + +```json +{ + "coin_balance": 1123123, + "recipients": [], + "tabs": [], + "gifts": [], + "quantity_presets": [1, 9, 99, 999] +} +``` + +Rules: + +- 打开礼物面板时再请求,不放进 `rooms/detail`,避免房间首屏每次都查钱包和礼物大列表。 +- `gifts` 来自 wallet-service 礼物配置,按房间 `visible_region_id` 过滤。 +- `recipients` 首版包含麦上用户;超过一个麦上用户时附带 `target_type=all_mic` 占位。 +- `coin_balance` 只查 `COIN` 余额;余额不足时送礼接口仍以 wallet-service 扣费结果为准。 + +### Room Display Profiles + +```http +GET /api/v1/users/room-display-profiles:batch?user_ids=10001,10002 +Authorization: Bearer +``` + +Response `data`: + +```json +{ + "profiles": [ + { + "user_id": "10002", + "username": "Jack", + "avatar": "https://cdn.example/avatar.png", + "display_user_id": "123456", + "vip": {}, + "level": {}, + "badges": [], + "avatar_frame": {}, + "vehicle": {}, + "charm": 10 + } + ] +} +``` + +Rules: + +- 该接口专门服务房间麦位、公屏和礼物动效展示;不要复用“我的页”或用户主页重接口。 +- 当前已接入用户基础资料,`vip/level/badges/avatar_frame/vehicle/charm` 保持稳定字段,后续接入对应 read model 后填充。 + ### IM UserSig ```http @@ -509,12 +636,14 @@ Rules: | Interface | Body | Result | | --- | --- | --- | | `POST /api/v1/rooms/mic/lock` | `room_id, command_id, seat_no, locked` | 锁/解锁麦位 | +| `POST /api/v1/rooms/mic/mute` | `room_id, command_id, target_user_id, muted` | 修改麦克风静音状态;该状态会同步给房间其他用户 | | `POST /api/v1/rooms/chat/enabled` | `room_id, command_id, enabled` | 开/关公屏,影响发言守卫 | | `POST /api/v1/rooms/admin/set` | `room_id, command_id, target_user_id, is_admin` | 设置或取消房管 | | `POST /api/v1/rooms/host/transfer` | `room_id, command_id, target_user_id` | 转移主持人 | | `POST /api/v1/rooms/user/mute` | `room_id, command_id, target_user_id, muted, reason` | 禁言或解禁 | | `POST /api/v1/rooms/user/kick` | `room_id, command_id, target_user_id, reason` | 踢人并 ban,释放 presence 和麦位 | | `POST /api/v1/rooms/user/unban` | `room_id, command_id, target_user_id, reason` | 解除房间 ban | +| `POST /api/v1/rooms/close` | `room_id, command_id` | 设计稿电源按钮:当前用户退出房间,不关闭房间 | Management event handling: @@ -536,7 +665,8 @@ Request: { "room_id": "lalu_xxx", "command_id": "cmd_send_gift___", - "target_user_id": 10002, + "target_type": "user", + "target_user_ids": [10002], "gift_id": "rose", "gift_count": 1 } @@ -545,6 +675,8 @@ Request: Rules: - `SendGift` 必须先由 wallet-service 扣费成功,再落房间表现。 +- 旧客户端的 `target_user_id` 仍兼容;新客户端统一传 `target_type + target_user_ids`。 +- 当前已支持 `target_type=user`;`all_mic/all_room/couple` 先保留协议字段,等账务拆单和房间表现策略补齐后开放。 - 房间表现、热度、本地榜通过 room snapshot 和房间系统消息同步。 - 公屏文本消息不走 gateway HTTP;客户端使用腾讯云 IM 群消息。 @@ -625,6 +757,7 @@ Client restore rules: 当前客户端拿完整房间状态的来源: - `JoinRoom` 响应里的 room snapshot。 +- `GET /api/v1/rooms/{room_id}/detail` 的首屏聚合包。 - 房间命令响应里的 room snapshot。 - 腾讯云 IM 房间系统消息。 - `rooms/current` 的恢复摘要。 diff --git a/scripts/apply-local-mysql-initdb.sh b/scripts/apply-local-mysql-initdb.sh index 86be7e21..bc36b59f 100755 --- a/scripts/apply-local-mysql-initdb.sh +++ b/scripts/apply-local-mysql-initdb.sh @@ -19,6 +19,7 @@ SQL_FILES=( "deploy/mysql/initdb/006_admin_database.sql" "deploy/mysql/initdb/007_resource_group_wallet_asset_items.sql" "services/cron-service/deploy/mysql/initdb/001_cron_service.sql" + "services/game-service/deploy/mysql/initdb/001_game_service.sql" "deploy/mysql/initdb/999_local_grants.sql" ) diff --git a/server/admin/cmd/server/main.go b/server/admin/cmd/server/main.go index b6f38c4a..887a3446 100644 --- a/server/admin/cmd/server/main.go +++ b/server/admin/cmd/server/main.go @@ -17,6 +17,7 @@ import ( "hyapp-admin-server/internal/cache" "hyapp-admin-server/internal/config" "hyapp-admin-server/internal/integration/activityclient" + "hyapp-admin-server/internal/integration/gameclient" "hyapp-admin-server/internal/integration/userclient" "hyapp-admin-server/internal/integration/walletclient" jobrunner "hyapp-admin-server/internal/job" @@ -30,6 +31,7 @@ import ( countryregionmodule "hyapp-admin-server/internal/modules/countryregion" dailytaskmodule "hyapp-admin-server/internal/modules/dailytask" dashboardmodule "hyapp-admin-server/internal/modules/dashboard" + gamemanagementmodule "hyapp-admin-server/internal/modules/gamemanagement" healthmodule "hyapp-admin-server/internal/modules/health" hostorgmodule "hyapp-admin-server/internal/modules/hostorg" jobmodule "hyapp-admin-server/internal/modules/job" @@ -167,6 +169,12 @@ func main() { } defer activityConn.Close() + gameConn, err := grpc.Dial(cfg.GameService.Addr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + fatalRuntime("connect_game_service_failed", err) + } + defer gameConn.Close() + auth := service.NewAuthService(cfg.JWTSecret, cfg.AccessTokenTTL) var runner *jobrunner.Runner var jobStatus healthmodule.JobStatusProvider @@ -202,6 +210,7 @@ func main() { CountryRegion: countryregionmodule.New(userclient.NewGRPC(userConn), userDB, cfg, auditHandler), DailyTask: dailytaskmodule.New(activityclient.NewGRPC(activityConn), auditHandler), Dashboard: dashboardmodule.New(store), + Game: gamemanagementmodule.New(gameclient.NewGRPC(gameConn), auditHandler), Health: healthmodule.New(store, redisClient, jobStatus), HostOrg: hostorgmodule.New(userclient.NewGRPC(userConn), walletclient.NewGRPC(walletConn), userDB, walletDB, sqlDB, auditHandler), Job: jobmodule.New(store, cfg, auditHandler), diff --git a/server/admin/configs/config.yaml b/server/admin/configs/config.yaml index 6cf3bfc6..8a6e539e 100644 --- a/server/admin/configs/config.yaml +++ b/server/admin/configs/config.yaml @@ -43,6 +43,9 @@ wallet_service: activity_service: addr: "127.0.0.1:13006" request_timeout: "3s" +game_service: + addr: "127.0.0.1:13008" + request_timeout: "3s" tencent-cos: enabled: true secret-id: "IKIDMchhZEfrsiNo472DAtTpzzmLjttkOnyu" diff --git a/server/admin/internal/config/config.go b/server/admin/internal/config/config.go index e1b74168..ba5ef7bf 100644 --- a/server/admin/internal/config/config.go +++ b/server/admin/internal/config/config.go @@ -37,6 +37,7 @@ type Config struct { Jobs JobsConfig `yaml:"jobs"` WalletService WalletServiceConfig `yaml:"wallet_service"` ActivityService ActivityServiceConfig `yaml:"activity_service"` + GameService GameServiceConfig `yaml:"game_service"` } type MigrationConfig struct { @@ -60,6 +61,11 @@ type ActivityServiceConfig struct { RequestTimeout time.Duration `yaml:"request_timeout"` } +type GameServiceConfig struct { + Addr string `yaml:"addr"` + RequestTimeout time.Duration `yaml:"request_timeout"` +} + type TencentCOSConfig struct { Enabled bool `yaml:"enabled"` SecretID string `yaml:"secret-id"` @@ -141,6 +147,10 @@ func Default() Config { Addr: "127.0.0.1:13006", RequestTimeout: 3 * time.Second, }, + GameService: GameServiceConfig{ + Addr: "127.0.0.1:13008", + RequestTimeout: 3 * time.Second, + }, TencentCOS: TencentCOSConfig{ Enabled: false, ObjectPrefix: "admin", @@ -223,6 +233,25 @@ func (cfg *Config) Normalize() { if cfg.Jobs.ArtifactDir == "" { cfg.Jobs.ArtifactDir = "storage/exports" } + cfg.UserService.Addr = strings.TrimSpace(cfg.UserService.Addr) + if cfg.UserService.RequestTimeout <= 0 { + cfg.UserService.RequestTimeout = 3 * time.Second + } + cfg.WalletService.Addr = strings.TrimSpace(cfg.WalletService.Addr) + if cfg.WalletService.RequestTimeout <= 0 { + cfg.WalletService.RequestTimeout = 3 * time.Second + } + cfg.ActivityService.Addr = strings.TrimSpace(cfg.ActivityService.Addr) + if cfg.ActivityService.RequestTimeout <= 0 { + cfg.ActivityService.RequestTimeout = 3 * time.Second + } + cfg.GameService.Addr = strings.TrimSpace(cfg.GameService.Addr) + if cfg.GameService.Addr == "" { + cfg.GameService.Addr = "127.0.0.1:13008" + } + if cfg.GameService.RequestTimeout <= 0 { + cfg.GameService.RequestTimeout = 3 * time.Second + } cfg.TencentCOS.SecretID = strings.TrimSpace(cfg.TencentCOS.SecretID) cfg.TencentCOS.SecretKey = strings.TrimSpace(cfg.TencentCOS.SecretKey) cfg.TencentCOS.BucketName = strings.TrimSpace(cfg.TencentCOS.BucketName) @@ -309,6 +338,12 @@ func (cfg Config) Validate() error { if cfg.ActivityService.RequestTimeout <= 0 { return errors.New("activity_service.request_timeout must be greater than 0") } + if strings.TrimSpace(cfg.GameService.Addr) == "" { + return errors.New("game_service.addr is required") + } + if cfg.GameService.RequestTimeout <= 0 { + return errors.New("game_service.request_timeout must be greater than 0") + } if cfg.TencentCOS.Enabled { if cfg.TencentCOS.SecretID == "" || cfg.TencentCOS.SecretKey == "" || cfg.TencentCOS.BucketName == "" || cfg.TencentCOS.Region == "" || cfg.TencentCOS.AccessURL == "" { return errors.New("tencent-cos config is incomplete") diff --git a/server/admin/internal/integration/gameclient/client.go b/server/admin/internal/integration/gameclient/client.go new file mode 100644 index 00000000..b51d7d58 --- /dev/null +++ b/server/admin/internal/integration/gameclient/client.go @@ -0,0 +1,46 @@ +package gameclient + +import ( + "context" + + gamev1 "hyapp.local/api/proto/game/v1" + + "google.golang.org/grpc" +) + +// Client 是 admin-server 管理游戏平台和目录配置的最小 gRPC 依赖。 +type Client interface { + ListPlatforms(ctx context.Context, req *gamev1.ListPlatformsRequest) (*gamev1.ListPlatformsResponse, error) + UpsertPlatform(ctx context.Context, req *gamev1.UpsertPlatformRequest) (*gamev1.PlatformResponse, error) + ListCatalog(ctx context.Context, req *gamev1.ListCatalogRequest) (*gamev1.ListCatalogResponse, error) + UpsertCatalog(ctx context.Context, req *gamev1.UpsertCatalogRequest) (*gamev1.CatalogResponse, error) + SetGameStatus(ctx context.Context, req *gamev1.SetGameStatusRequest) (*gamev1.CatalogResponse, error) +} + +type GRPCClient struct { + client gamev1.GameAdminServiceClient +} + +func NewGRPC(conn grpc.ClientConnInterface) *GRPCClient { + return &GRPCClient{client: gamev1.NewGameAdminServiceClient(conn)} +} + +func (c *GRPCClient) ListPlatforms(ctx context.Context, req *gamev1.ListPlatformsRequest) (*gamev1.ListPlatformsResponse, error) { + return c.client.ListPlatforms(ctx, req) +} + +func (c *GRPCClient) UpsertPlatform(ctx context.Context, req *gamev1.UpsertPlatformRequest) (*gamev1.PlatformResponse, error) { + return c.client.UpsertPlatform(ctx, req) +} + +func (c *GRPCClient) ListCatalog(ctx context.Context, req *gamev1.ListCatalogRequest) (*gamev1.ListCatalogResponse, error) { + return c.client.ListCatalog(ctx, req) +} + +func (c *GRPCClient) UpsertCatalog(ctx context.Context, req *gamev1.UpsertCatalogRequest) (*gamev1.CatalogResponse, error) { + return c.client.UpsertCatalog(ctx, req) +} + +func (c *GRPCClient) SetGameStatus(ctx context.Context, req *gamev1.SetGameStatusRequest) (*gamev1.CatalogResponse, error) { + return c.client.SetGameStatus(ctx, req) +} diff --git a/server/admin/internal/modules/gamemanagement/dto.go b/server/admin/internal/modules/gamemanagement/dto.go new file mode 100644 index 00000000..458441d6 --- /dev/null +++ b/server/admin/internal/modules/gamemanagement/dto.go @@ -0,0 +1,73 @@ +package gamemanagement + +import gamev1 "hyapp.local/api/proto/game/v1" + +type platformDTO struct { + AppCode string `json:"appCode"` + PlatformCode string `json:"platformCode"` + PlatformName string `json:"platformName"` + Status string `json:"status"` + APIBaseURL string `json:"apiBaseUrl"` + SortOrder int32 `json:"sortOrder"` + CreatedAtMS int64 `json:"createdAtMs"` + UpdatedAtMS int64 `json:"updatedAtMs"` +} + +type catalogDTO struct { + AppCode string `json:"appCode"` + GameID string `json:"gameId"` + PlatformCode string `json:"platformCode"` + ProviderGameID string `json:"providerGameId"` + GameName string `json:"gameName"` + Category string `json:"category"` + IconURL string `json:"iconUrl"` + CoverURL string `json:"coverUrl"` + LaunchMode string `json:"launchMode"` + Orientation string `json:"orientation"` + MinCoin int64 `json:"minCoin"` + Status string `json:"status"` + SortOrder int32 `json:"sortOrder"` + Tags []string `json:"tags"` + CreatedAtMS int64 `json:"createdAtMs"` + UpdatedAtMS int64 `json:"updatedAtMs"` +} + +func platformFromProto(item *gamev1.GamePlatform) platformDTO { + if item == nil { + return platformDTO{} + } + return platformDTO{ + AppCode: item.GetAppCode(), + PlatformCode: item.GetPlatformCode(), + PlatformName: item.GetPlatformName(), + Status: item.GetStatus(), + APIBaseURL: item.GetApiBaseUrl(), + SortOrder: item.GetSortOrder(), + CreatedAtMS: item.GetCreatedAtMs(), + UpdatedAtMS: item.GetUpdatedAtMs(), + } +} + +func catalogFromProto(item *gamev1.GameCatalogItem) catalogDTO { + if item == nil { + return catalogDTO{} + } + return catalogDTO{ + AppCode: item.GetAppCode(), + GameID: item.GetGameId(), + PlatformCode: item.GetPlatformCode(), + ProviderGameID: item.GetProviderGameId(), + GameName: item.GetGameName(), + Category: item.GetCategory(), + IconURL: item.GetIconUrl(), + CoverURL: item.GetCoverUrl(), + LaunchMode: item.GetLaunchMode(), + Orientation: item.GetOrientation(), + MinCoin: item.GetMinCoin(), + Status: item.GetStatus(), + SortOrder: item.GetSortOrder(), + Tags: item.GetTags(), + CreatedAtMS: item.GetCreatedAtMs(), + UpdatedAtMS: item.GetUpdatedAtMs(), + } +} diff --git a/server/admin/internal/modules/gamemanagement/handler.go b/server/admin/internal/modules/gamemanagement/handler.go new file mode 100644 index 00000000..c2884b1d --- /dev/null +++ b/server/admin/internal/modules/gamemanagement/handler.go @@ -0,0 +1,171 @@ +package gamemanagement + +import ( + "strconv" + "strings" + + "hyapp-admin-server/internal/integration/gameclient" + "hyapp-admin-server/internal/modules/shared" + "hyapp-admin-server/internal/response" + gamev1 "hyapp.local/api/proto/game/v1" + + "github.com/gin-gonic/gin" +) + +type Handler struct { + game gameclient.Client + audit shared.OperationLogger +} + +func New(game gameclient.Client, audit shared.OperationLogger) *Handler { + return &Handler{game: game, audit: audit} +} + +// ListPlatforms 返回后台游戏平台筛选项和平台配置列表。 +func (h *Handler) ListPlatforms(c *gin.Context) { + resp, err := h.game.ListPlatforms(c.Request.Context(), &gamev1.ListPlatformsRequest{ + Meta: requestMeta(c), + Status: strings.TrimSpace(c.Query("status")), + }) + if err != nil { + response.ServerError(c, "获取游戏平台失败") + return + } + items := make([]platformDTO, 0, len(resp.GetPlatforms())) + for _, item := range resp.GetPlatforms() { + items = append(items, platformFromProto(item)) + } + response.OK(c, gin.H{"items": items, "serverTimeMs": resp.GetServerTimeMs()}) +} + +func (h *Handler) CreatePlatform(c *gin.Context) { + h.upsertPlatform(c, "") +} + +func (h *Handler) UpdatePlatform(c *gin.Context) { + h.upsertPlatform(c, strings.TrimSpace(c.Param("platform_code"))) +} + +// upsertPlatform 创建或更新第三方游戏平台配置;平台密钥后续走单独的密钥托管接口。 +func (h *Handler) upsertPlatform(c *gin.Context, platformCode string) { + var req platformRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "游戏平台参数不正确") + return + } + resp, err := h.game.UpsertPlatform(c.Request.Context(), &gamev1.UpsertPlatformRequest{ + Meta: requestMeta(c), + Platform: req.toProto(platformCode), + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + item := platformFromProto(resp.GetPlatform()) + h.auditLog(c, "upsert-game-platform", "game_platforms", item.PlatformCode, item.PlatformName) + response.OK(c, item) +} + +// ListCatalog 返回游戏目录;当前 game-service 使用 cursor 扩展点,后台先按 pageSize 截断。 +func (h *Handler) ListCatalog(c *gin.Context) { + pageSize := int32(parsePositiveInt(firstQuery(c, "pageSize", "page_size"), 50)) + resp, err := h.game.ListCatalog(c.Request.Context(), &gamev1.ListCatalogRequest{ + Meta: requestMeta(c), + PlatformCode: strings.TrimSpace(firstQuery(c, "platformCode", "platform_code")), + Status: strings.TrimSpace(c.Query("status")), + PageSize: pageSize, + Cursor: strings.TrimSpace(c.Query("cursor")), + }) + if err != nil { + response.ServerError(c, "获取游戏列表失败") + return + } + items := make([]catalogDTO, 0, len(resp.GetGames())) + for _, item := range resp.GetGames() { + items = append(items, catalogFromProto(item)) + } + response.OK(c, gin.H{ + "items": items, + "nextCursor": resp.GetNextCursor(), + "pageSize": pageSize, + "serverTimeMs": resp.GetServerTimeMs(), + }) +} + +func (h *Handler) CreateCatalog(c *gin.Context) { + h.upsertCatalog(c, "") +} + +func (h *Handler) UpdateCatalog(c *gin.Context) { + h.upsertCatalog(c, strings.TrimSpace(c.Param("game_id"))) +} + +// upsertCatalog 创建或更新内部 game_id 到 provider_game_id 的映射。 +func (h *Handler) upsertCatalog(c *gin.Context, gameID string) { + var req catalogRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "游戏参数不正确") + return + } + resp, err := h.game.UpsertCatalog(c.Request.Context(), &gamev1.UpsertCatalogRequest{ + Meta: requestMeta(c), + Game: req.toProto(gameID), + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + item := catalogFromProto(resp.GetGame()) + h.auditLog(c, "upsert-game-catalog", "game_catalog", item.GameID, item.GameName) + response.OK(c, item) +} + +// SetGameStatus 只处理 active/maintenance/disabled 状态,不删除历史订单关联的游戏目录。 +func (h *Handler) SetGameStatus(c *gin.Context) { + gameID := strings.TrimSpace(c.Param("game_id")) + if gameID == "" { + response.BadRequest(c, "游戏 ID 参数不正确") + return + } + var req statusRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "状态参数不正确") + return + } + resp, err := h.game.SetGameStatus(c.Request.Context(), &gamev1.SetGameStatusRequest{ + Meta: requestMeta(c), + GameId: gameID, + Status: strings.TrimSpace(req.Status), + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + item := catalogFromProto(resp.GetGame()) + h.auditLog(c, "set-game-status", "game_catalog", item.GameID, item.Status) + response.OK(c, item) +} + +func (h *Handler) auditLog(c *gin.Context, action string, resource string, resourceID string, detail string) { + shared.OperationLogWithResourceID(c, h.audit, action, resource, resourceID, "success", detail) +} + +func parsePositiveInt(raw string, fallback int) int { + value, err := strconv.Atoi(strings.TrimSpace(raw)) + if err != nil || value <= 0 { + return fallback + } + if value > 200 { + return 200 + } + return value +} + +func firstQuery(c *gin.Context, keys ...string) string { + for _, key := range keys { + if value := c.Query(key); value != "" { + return value + } + } + return "" +} diff --git a/server/admin/internal/modules/gamemanagement/request.go b/server/admin/internal/modules/gamemanagement/request.go new file mode 100644 index 00000000..6446c975 --- /dev/null +++ b/server/admin/internal/modules/gamemanagement/request.go @@ -0,0 +1,101 @@ +package gamemanagement + +import ( + "strings" + "time" + + "hyapp-admin-server/internal/appctx" + "hyapp-admin-server/internal/middleware" + gamev1 "hyapp.local/api/proto/game/v1" + + "github.com/gin-gonic/gin" +) + +type platformRequest struct { + PlatformCode string `json:"platformCode"` + PlatformName string `json:"platformName"` + Status string `json:"status"` + APIBaseURL string `json:"apiBaseUrl"` + SortOrder int32 `json:"sortOrder"` +} + +type catalogRequest struct { + GameID string `json:"gameId"` + PlatformCode string `json:"platformCode"` + ProviderGameID string `json:"providerGameId"` + GameName string `json:"gameName"` + Category string `json:"category"` + IconURL string `json:"iconUrl"` + CoverURL string `json:"coverUrl"` + LaunchMode string `json:"launchMode"` + Orientation string `json:"orientation"` + MinCoin int64 `json:"minCoin"` + Status string `json:"status"` + SortOrder int32 `json:"sortOrder"` + Tags []string `json:"tags"` +} + +type statusRequest struct { + Status string `json:"status"` +} + +func (r platformRequest) toProto(platformCode string) *gamev1.GamePlatform { + if platformCode == "" { + platformCode = r.PlatformCode + } + return &gamev1.GamePlatform{ + PlatformCode: strings.TrimSpace(platformCode), + PlatformName: strings.TrimSpace(r.PlatformName), + Status: strings.TrimSpace(r.Status), + ApiBaseUrl: strings.TrimSpace(r.APIBaseURL), + SortOrder: r.SortOrder, + } +} + +func (r catalogRequest) toProto(gameID string) *gamev1.GameCatalogItem { + if gameID == "" { + gameID = r.GameID + } + return &gamev1.GameCatalogItem{ + GameId: strings.TrimSpace(gameID), + PlatformCode: strings.TrimSpace(r.PlatformCode), + ProviderGameId: strings.TrimSpace(r.ProviderGameID), + GameName: strings.TrimSpace(r.GameName), + Category: strings.TrimSpace(r.Category), + IconUrl: strings.TrimSpace(r.IconURL), + CoverUrl: strings.TrimSpace(r.CoverURL), + LaunchMode: strings.TrimSpace(r.LaunchMode), + Orientation: strings.TrimSpace(r.Orientation), + MinCoin: r.MinCoin, + Status: strings.TrimSpace(r.Status), + SortOrder: r.SortOrder, + Tags: compactTags(r.Tags), + } +} + +func compactTags(tags []string) []string { + out := make([]string, 0, len(tags)) + seen := make(map[string]struct{}, len(tags)) + for _, tag := range tags { + value := strings.TrimSpace(tag) + if value == "" { + continue + } + if _, ok := seen[value]; ok { + continue + } + seen[value] = struct{}{} + out = append(out, value) + } + return out +} + +func requestMeta(c *gin.Context) *gamev1.RequestMeta { + return &gamev1.RequestMeta{ + RequestId: middleware.CurrentRequestID(c), + Caller: "admin-server", + ActorUserId: int64(middleware.CurrentUserID(c)), + SentAtMs: time.Now().UnixMilli(), + AppCode: appctx.FromContext(c.Request.Context()), + } +} diff --git a/server/admin/internal/modules/gamemanagement/routes.go b/server/admin/internal/modules/gamemanagement/routes.go new file mode 100644 index 00000000..661f55e1 --- /dev/null +++ b/server/admin/internal/modules/gamemanagement/routes.go @@ -0,0 +1,21 @@ +package gamemanagement + +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/game/platforms", middleware.RequirePermission("game:view"), h.ListPlatforms) + protected.POST("/admin/game/platforms", middleware.RequirePermission("game:update"), h.CreatePlatform) + protected.PATCH("/admin/game/platforms/:platform_code", middleware.RequirePermission("game:update"), h.UpdatePlatform) + protected.GET("/admin/game/games", middleware.RequirePermission("game:view"), h.ListCatalog) + protected.POST("/admin/game/games", middleware.RequirePermission("game:create"), h.CreateCatalog) + protected.PATCH("/admin/game/games/:game_id", middleware.RequirePermission("game:update"), h.UpdateCatalog) + protected.PATCH("/admin/game/games/:game_id/status", middleware.RequirePermission("game:status"), h.SetGameStatus) +} diff --git a/server/admin/internal/repository/seed.go b/server/admin/internal/repository/seed.go index e2489757..efd9cc71 100644 --- a/server/admin/internal/repository/seed.go +++ b/server/admin/internal/repository/seed.go @@ -59,6 +59,10 @@ var defaultPermissions = []model.Permission{ {Name: "币商更新", Code: "coin-seller:update", Kind: "button"}, {Name: "币商进货", Code: "coin-seller:stock-credit", Kind: "button"}, {Name: "支付账单查看", Code: "payment-bill:view", Kind: "menu"}, + {Name: "游戏查看", Code: "game:view", Kind: "menu"}, + {Name: "游戏创建", Code: "game:create", Kind: "button"}, + {Name: "游戏更新", Code: "game:update", Kind: "button"}, + {Name: "游戏状态", Code: "game:status", Kind: "button"}, {Name: "每日任务查看", Code: "daily-task:view", Kind: "menu"}, {Name: "每日任务创建", Code: "daily-task:create", Kind: "button"}, {Name: "每日任务更新", Code: "daily-task:update", Kind: "button"}, @@ -110,6 +114,7 @@ func (s *Store) seedMenus() error { roomsID := uint(0) paymentID := uint(0) activityID := uint(0) + gameID := uint(0) menus := []model.Menu{ {Title: "总览", Code: "overview", Path: "/overview", Icon: "dashboard", PermissionCode: "overview:view", Sort: 10, Visible: true}, {Title: "后台设置", Code: "system", Path: "", Icon: "settings", PermissionCode: "", Sort: 30, Visible: true}, @@ -121,7 +126,8 @@ func (s *Store) seedMenus() error { {Title: "资源管理", Code: "resources", Path: "", Icon: "inventory", PermissionCode: "", Sort: 67, Visible: true}, {Title: "支付管理", Code: "payment", Path: "", Icon: "wallet", PermissionCode: "", Sort: 68, Visible: true}, {Title: "活动管理", Code: "activities", Path: "", Icon: "campaign", PermissionCode: "", Sort: 69, Visible: true}, - {Title: "地区管理", Code: "geo", Path: "", Icon: "map", PermissionCode: "", Sort: 70, Visible: true}, + {Title: "游戏管理", Code: "games", Path: "", Icon: "sports_esports", PermissionCode: "", Sort: 70, Visible: true}, + {Title: "地区管理", Code: "geo", Path: "", Icon: "map", PermissionCode: "", Sort: 71, Visible: true}, {Title: "团队管理", Code: "host-org", Path: "", Icon: "network", PermissionCode: "", Sort: 80, Visible: true}, {Title: "任务中心", Code: "jobs", Path: "/jobs", Icon: "task", PermissionCode: "job:view", Sort: 90, Visible: true}, } @@ -157,6 +163,9 @@ func (s *Store) seedMenus() error { if syncedMenu.Code == "activities" { activityID = syncedMenu.ID } + if syncedMenu.Code == "games" { + gameID = syncedMenu.ID + } } children := []model.Menu{ @@ -177,6 +186,7 @@ func (s *Store) seedMenus() error { {ParentID: &resourceID, Title: "资源赠送", Code: "resource-grant-list", Path: "/resource-grants", Icon: "send", PermissionCode: "resource-grant:view", Sort: 70, Visible: true}, {ParentID: &paymentID, Title: "账单列表", Code: "payment-bill-list", Path: "/payment/bills", Icon: "receipt", PermissionCode: "payment-bill:view", Sort: 68, Visible: true}, {ParentID: &activityID, Title: "每日任务", Code: "daily-task-list", Path: "/activities/daily-tasks", Icon: "task", PermissionCode: "daily-task:view", Sort: 69, Visible: true}, + {ParentID: &gameID, Title: "游戏列表", Code: "game-list", Path: "/games", Icon: "sports_esports", PermissionCode: "game:view", Sort: 70, Visible: true}, {ParentID: &geoID, Title: "国家管理", Code: "host-org-countries", Path: "/host/countries", Icon: "public", PermissionCode: "country:view", Sort: 70, Visible: true}, {ParentID: &geoID, Title: "区域管理", Code: "host-org-regions", Path: "/host/regions", Icon: "map", PermissionCode: "region:view", Sort: 71, Visible: true}, {ParentID: &hostOrgID, Title: "Agency 管理", Code: "host-org-agencies", Path: "/host/agencies", Icon: "apartment", PermissionCode: "agency:view", Sort: 80, Visible: true}, @@ -422,6 +432,7 @@ func defaultRolePermissionCodes(code string) []string { "bd:view", "bd:create", "bd:update", "coin-seller:view", "coin-seller:create", "coin-seller:update", "coin-seller:stock-credit", "payment-bill:view", + "game:view", "game:create", "game:update", "game:status", "daily-task:view", "daily-task:create", "daily-task:update", "daily-task:status", "log:view", "notification:view", "notification:read", @@ -429,7 +440,7 @@ func defaultRolePermissionCodes(code string) []string { "upload:create", } case "auditor": - return []string{"overview:view", "log:view", "user:view", "app-user:view", "room:view", "room-config:view", "app-config:view", "resource:view", "resource-group:view", "resource-grant:view", "gift:view", "payment-bill:view", "daily-task:view", "role:view", "permission:view", "job:view"} + return []string{"overview:view", "log:view", "user:view", "app-user:view", "room:view", "room-config:view", "app-config:view", "resource:view", "resource-group:view", "resource-grant:view", "gift:view", "payment-bill:view", "game:view", "daily-task:view", "role:view", "permission:view", "job:view"} case "readonly": return []string{ "overview:view", @@ -449,6 +460,7 @@ func defaultRolePermissionCodes(code string) []string { "bd:view", "coin-seller:view", "payment-bill:view", + "game:view", "daily-task:view", "role:view", "permission:view", @@ -478,10 +490,11 @@ func defaultRolePermissionMigrationCodes(code string) []string { "region:view", "region:create", "region:update", "region:status", "coin-seller:view", "coin-seller:create", "coin-seller:update", "coin-seller:stock-credit", "payment-bill:view", + "game:view", "game:create", "game:update", "game:status", "daily-task:view", "daily-task:create", "daily-task:update", "daily-task:status", } case "auditor", "readonly": - return []string{"room:view", "room-config:view", "app-config:view", "resource:view", "resource-group:view", "resource-grant:view", "gift:view", "coin-seller:view", "payment-bill:view", "daily-task:view"} + return []string{"room:view", "room-config:view", "app-config:view", "resource:view", "resource-group:view", "resource-grant:view", "gift:view", "coin-seller:view", "payment-bill:view", "game:view", "daily-task:view"} default: return nil } diff --git a/server/admin/internal/router/router.go b/server/admin/internal/router/router.go index 11363872..be193d4b 100644 --- a/server/admin/internal/router/router.go +++ b/server/admin/internal/router/router.go @@ -12,6 +12,7 @@ import ( "hyapp-admin-server/internal/modules/countryregion" "hyapp-admin-server/internal/modules/dailytask" "hyapp-admin-server/internal/modules/dashboard" + gamemanagement "hyapp-admin-server/internal/modules/gamemanagement" "hyapp-admin-server/internal/modules/health" "hyapp-admin-server/internal/modules/hostorg" "hyapp-admin-server/internal/modules/job" @@ -39,6 +40,7 @@ type Handlers struct { CountryRegion *countryregion.Handler DailyTask *dailytask.Handler Dashboard *dashboard.Handler + Game *gamemanagement.Handler Health *health.Handler HostOrg *hostorg.Handler Job *job.Handler @@ -73,6 +75,7 @@ func New(cfg config.Config, auth *service.AuthService, h Handlers) *gin.Engine { appconfig.RegisterRoutes(protected, h.AppConfig) countryregion.RegisterRoutes(protected, h.CountryRegion) dailytask.RegisterRoutes(protected, h.DailyTask) + gamemanagement.RegisterRoutes(protected, h.Game) roomadmin.RegisterRoutes(protected, h.RoomAdmin) dashboard.RegisterRoutes(protected, h.Dashboard) hostorg.RegisterRoutes(protected, h.HostOrg) diff --git a/services/activity-service/configs/config.docker.yaml b/services/activity-service/configs/config.docker.yaml index 3f68ae60..81460f86 100644 --- a/services/activity-service/configs/config.docker.yaml +++ b/services/activity-service/configs/config.docker.yaml @@ -13,17 +13,17 @@ user_service_addr: "user-service:13005" wallet_service_addr: "wallet-service:13004" mysql_auto_migrate: false tencent_im: - # Docker 本地默认关闭播报 REST,避免未配置密钥时启动后访问外部腾讯云。 - enabled: false - sdk_app_id: 0 - secret_key: "" - admin_identifier: "administrator" + # Docker 本地联调全局/区域播报群;必须与 gateway/room-service 使用同一组腾讯 IM 应用配置。 + enabled: true + sdk_app_id: 20036101 + secret_key: "dcdf53135f27e7cf8541c4ae9798fb0cb2f0e4d6c5c20022dbaea053f35cbb8c" + admin_identifier: "900100100" admin_user_sig_ttl: "24h" - endpoint: "console.tim.qq.com" + endpoint: "adminapisgp.im.qcloud.com" group_type: "ChatRoom" request_timeout: "5s" broadcast: - enabled: false + enabled: true super_gift_min_value: 100000 worker_poll_interval: "1s" worker_batch_size: 100 diff --git a/services/activity-service/configs/config.yaml b/services/activity-service/configs/config.yaml index f6bda29a..f7ae2003 100644 --- a/services/activity-service/configs/config.yaml +++ b/services/activity-service/configs/config.yaml @@ -13,17 +13,17 @@ user_service_addr: "127.0.0.1:13005" wallet_service_addr: "127.0.0.1:13004" mysql_auto_migrate: false tencent_im: - # activity-service 只负责全局/区域播报群;本地默认关闭,配置完整后才发送腾讯云 IM REST。 - enabled: false - sdk_app_id: 0 - secret_key: "" - admin_identifier: "administrator" + # activity-service 只负责全局/区域播报群;必须与 gateway/room-service 使用同一组腾讯 IM 应用配置。 + enabled: true + sdk_app_id: 20036101 + secret_key: "dcdf53135f27e7cf8541c4ae9798fb0cb2f0e4d6c5c20022dbaea053f35cbb8c" + admin_identifier: "900100100" admin_user_sig_ttl: "24h" - endpoint: "console.tim.qq.com" + endpoint: "adminapisgp.im.qcloud.com" group_type: "ChatRoom" request_timeout: "5s" broadcast: - enabled: false + enabled: true super_gift_min_value: 100000 worker_poll_interval: "1s" worker_batch_size: 100 diff --git a/services/game-service/Dockerfile b/services/game-service/Dockerfile new file mode 100644 index 00000000..3807e342 --- /dev/null +++ b/services/game-service/Dockerfile @@ -0,0 +1,24 @@ +FROM golang:1.26.3-alpine AS builder +WORKDIR /src + +ARG GOPROXY=https://goproxy.cn|https://proxy.golang.org|direct +ARG GOSUMDB=sum.golang.org +ENV GOPROXY=${GOPROXY} GOSUMDB=${GOSUMDB} + +COPY go.mod go.sum ./ +COPY api/go.mod api/go.sum ./api/ +RUN GOWORK=off go mod download +COPY . . +RUN GOWORK=off CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/server ./services/game-service/cmd/server +RUN GOWORK=off CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/grpc-health-probe ./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/grpc-health-probe /app/grpc-health-probe +COPY services/game-service/configs/config.docker.yaml /app/config.yaml +USER appuser +EXPOSE 13008 +ENTRYPOINT ["/app/server", "-config", "/app/config.yaml"] diff --git a/services/game-service/cmd/server/main.go b/services/game-service/cmd/server/main.go new file mode 100644 index 00000000..67b9e6f0 --- /dev/null +++ b/services/game-service/cmd/server/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "flag" + "log" + "os" + "os/signal" + "syscall" + + "hyapp/pkg/logx" + "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") + flag.Parse() + + cfg, err := config.Load(*configPath) + if err != nil { + log.Fatal(err) + } + if err := logx.Init(cfg.Log.WithRuntimeFields(cfg.ServiceName, cfg.Environment, cfg.NodeID)); err != nil { + log.Fatal(err) + } + + application, err := app.New(cfg) + if err != nil { + fatalRuntime("app_new_failed", err) + } + + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + + errCh := make(chan error, 1) + go func() { + errCh <- application.Run() + }() + + select { + case <-ctx.Done(): + application.Close() + case err := <-errCh: + if err != nil { + fatalRuntime("service_run_failed", err) + } + } +} + +func fatalRuntime(message string, err error) { + logx.Error(context.Background(), message, err) + os.Exit(1) +} diff --git a/services/game-service/configs/config.docker.yaml b/services/game-service/configs/config.docker.yaml new file mode 100644 index 00000000..a154f10e --- /dev/null +++ b/services/game-service/configs/config.docker.yaml @@ -0,0 +1,14 @@ +service_name: game-service +node_id: game-docker +environment: local +grpc_addr: ":13008" +mysql_dsn: "hyapp:hyapp@tcp(mysql:3306)/hyapp_game?parseTime=true&charset=utf8mb4&loc=UTC&multiStatements=true" +wallet_service_addr: "wallet-service:13004" +user_service_addr: "user-service:13005" +launch_session_ttl: "15m" +log: + level: info + format: json + include_request_body: false + include_response_body: false + max_payload_bytes: 2048 diff --git a/services/game-service/configs/config.tencent.example.yaml b/services/game-service/configs/config.tencent.example.yaml new file mode 100644 index 00000000..c6de0a84 --- /dev/null +++ b/services/game-service/configs/config.tencent.example.yaml @@ -0,0 +1,14 @@ +service_name: game-service +node_id: game-prod-1 +environment: prod +grpc_addr: ":13008" +mysql_dsn: "${GAME_MYSQL_DSN}" +wallet_service_addr: "wallet-service:13004" +user_service_addr: "user-service:13005" +launch_session_ttl: "15m" +log: + level: info + format: json + include_request_body: false + include_response_body: false + max_payload_bytes: 4096 diff --git a/services/game-service/configs/config.yaml b/services/game-service/configs/config.yaml new file mode 100644 index 00000000..67f4d457 --- /dev/null +++ b/services/game-service/configs/config.yaml @@ -0,0 +1,14 @@ +service_name: game-service +node_id: game-local +environment: local +grpc_addr: ":13008" +mysql_dsn: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_game?parseTime=true&charset=utf8mb4&loc=UTC&multiStatements=true" +wallet_service_addr: "127.0.0.1:13004" +user_service_addr: "127.0.0.1:13005" +launch_session_ttl: "15m" +log: + level: info + format: json + include_request_body: false + include_response_body: false + max_payload_bytes: 2048 diff --git a/services/game-service/deploy/mysql/initdb/001_game_service.sql b/services/game-service/deploy/mysql/initdb/001_game_service.sql new file mode 100644 index 00000000..8c398ed2 --- /dev/null +++ b/services/game-service/deploy/mysql/initdb/001_game_service.sql @@ -0,0 +1,161 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +CREATE DATABASE IF NOT EXISTS hyapp_game DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE hyapp_game; + +CREATE TABLE IF NOT EXISTS game_platforms ( + app_code VARCHAR(32) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + platform_name VARCHAR(128) NOT NULL, + status VARCHAR(32) NOT NULL, + api_base_url VARCHAR(512) NOT NULL DEFAULT '', + callback_secret_ciphertext VARCHAR(1024) NOT NULL DEFAULT '', + callback_ip_whitelist JSON NULL, + adapter_config JSON NULL, + sort_order INT NOT NULL DEFAULT 0, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, platform_code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS game_catalog ( + app_code VARCHAR(32) NOT NULL, + game_id VARCHAR(96) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + provider_game_id VARCHAR(128) NOT NULL, + game_name VARCHAR(128) NOT NULL, + category VARCHAR(64) NOT NULL DEFAULT '', + icon_url VARCHAR(512) NOT NULL DEFAULT '', + cover_url VARCHAR(512) NOT NULL DEFAULT '', + launch_mode VARCHAR(32) NOT NULL DEFAULT 'h5_popup', + orientation VARCHAR(32) NOT NULL DEFAULT 'portrait', + min_coin BIGINT NOT NULL DEFAULT 0, + status VARCHAR(32) NOT NULL, + sort_order INT NOT NULL DEFAULT 0, + tags JSON NULL, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, game_id), + UNIQUE KEY uk_game_provider(app_code, platform_code, provider_game_id), + KEY idx_game_platform_status(app_code, platform_code, status, sort_order) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS game_display_rules ( + app_code VARCHAR(32) NOT NULL, + rule_id VARCHAR(96) NOT NULL, + game_id VARCHAR(96) NOT NULL, + scene VARCHAR(64) NOT NULL, + region_id BIGINT NOT NULL DEFAULT 0, + language VARCHAR(32) NOT NULL DEFAULT '', + platform VARCHAR(16) NOT NULL DEFAULT '', + visible TINYINT(1) NOT NULL DEFAULT 1, + enabled TINYINT(1) NOT NULL DEFAULT 1, + sort_order INT NOT NULL DEFAULT 0, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, rule_id), + KEY idx_game_display(app_code, scene, region_id, visible, sort_order) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS game_launch_sessions ( + app_code VARCHAR(32) NOT NULL, + session_id VARCHAR(96) NOT NULL, + user_id BIGINT NOT NULL, + display_user_id VARCHAR(32) NOT NULL, + room_id VARCHAR(96) NOT NULL DEFAULT '', + scene VARCHAR(64) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + game_id VARCHAR(96) NOT NULL, + provider_game_id VARCHAR(128) NOT NULL, + launch_token_hash VARCHAR(128) NOT NULL, + status VARCHAR(32) NOT NULL, + expires_at_ms BIGINT NOT NULL, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, session_id), + KEY idx_game_launch_user(app_code, user_id, created_at_ms), + KEY idx_game_launch_token(app_code, launch_token_hash) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS game_orders ( + app_code VARCHAR(32) NOT NULL, + order_id VARCHAR(96) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + provider_order_id VARCHAR(160) NOT NULL, + provider_round_id VARCHAR(160) NOT NULL DEFAULT '', + game_id VARCHAR(96) NOT NULL, + provider_game_id VARCHAR(128) NOT NULL, + user_id BIGINT NOT NULL, + room_id VARCHAR(96) NOT NULL DEFAULT '', + op_type VARCHAR(32) NOT NULL, + coin_amount BIGINT NOT NULL, + status VARCHAR(32) NOT NULL, + wallet_transaction_id VARCHAR(96) NOT NULL DEFAULT '', + wallet_balance_after BIGINT NOT NULL DEFAULT 0, + request_hash VARCHAR(128) NOT NULL, + raw_payload_ref VARCHAR(256) NOT NULL DEFAULT '', + failure_code VARCHAR(64) NOT NULL DEFAULT '', + failure_message VARCHAR(256) NOT NULL DEFAULT '', + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, order_id), + UNIQUE KEY uk_game_provider_order(app_code, platform_code, provider_order_id), + KEY idx_game_order_user_time(app_code, user_id, created_at_ms), + KEY idx_game_order_round(app_code, platform_code, provider_round_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS game_callback_logs ( + app_code VARCHAR(32) NOT NULL, + callback_id VARCHAR(96) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + operation VARCHAR(64) NOT NULL, + request_id VARCHAR(96) NOT NULL DEFAULT '', + provider_request_id VARCHAR(160) NOT NULL DEFAULT '', + signature_valid TINYINT(1) NOT NULL DEFAULT 0, + request_hash VARCHAR(128) NOT NULL, + response_code VARCHAR(64) NOT NULL DEFAULT '', + response_body_hash VARCHAR(128) NOT NULL DEFAULT '', + status VARCHAR(32) NOT NULL, + created_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, callback_id), + KEY idx_game_callback_platform_time(app_code, platform_code, created_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS game_repair_orders ( + app_code VARCHAR(32) NOT NULL, + repair_id VARCHAR(96) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + provider_order_id VARCHAR(160) NOT NULL, + reason VARCHAR(128) NOT NULL, + status VARCHAR(32) NOT NULL, + operator_admin_id BIGINT NOT NULL DEFAULT 0, + attempts INT NOT NULL DEFAULT 0, + next_run_at_ms BIGINT NOT NULL DEFAULT 0, + last_error_code VARCHAR(64) NOT NULL DEFAULT '', + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, repair_id), + UNIQUE KEY uk_game_repair_provider_order(app_code, platform_code, provider_order_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +INSERT INTO game_platforms ( + app_code, platform_code, platform_name, status, api_base_url, sort_order, created_at_ms, updated_at_ms +) VALUES ( + 'lalu', 'demo', 'Demo Games', 'active', 'https://game.example.local/h5', 10, 0, 0 +) ON DUPLICATE KEY UPDATE platform_name = VALUES(platform_name); + +INSERT INTO game_catalog ( + app_code, game_id, platform_code, provider_game_id, game_name, category, icon_url, cover_url, + launch_mode, orientation, min_coin, status, sort_order, tags, created_at_ms, updated_at_ms +) VALUES ( + 'lalu', 'demo_rocket_001', 'demo', 'rocket_001', 'Rocket', 'casual', + 'https://cdn.example.com/game/rocket.png', 'https://cdn.example.com/game/rocket-cover.png', + 'h5_popup', 'portrait', 100, 'active', 10, JSON_ARRAY('demo','voice_room'), 0, 0 +) ON DUPLICATE KEY UPDATE game_name = VALUES(game_name); + +INSERT INTO game_display_rules ( + app_code, rule_id, game_id, scene, region_id, language, platform, visible, enabled, sort_order, created_at_ms, updated_at_ms +) VALUES ( + 'lalu', 'demo_rocket_voice_default', 'demo_rocket_001', 'voice_room', 0, '', '', 1, 1, 10, 0, 0 +) ON DUPLICATE KEY UPDATE visible = VALUES(visible), enabled = VALUES(enabled); diff --git a/services/game-service/internal/app/app.go b/services/game-service/internal/app/app.go new file mode 100644 index 00000000..34a34e0e --- /dev/null +++ b/services/game-service/internal/app/app.go @@ -0,0 +1,97 @@ +package app + +import ( + "context" + "errors" + "net" + "sync" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + healthgrpc "google.golang.org/grpc/health/grpc_health_v1" + gamev1 "hyapp.local/api/proto/game/v1" + "hyapp/pkg/grpchealth" + "hyapp/pkg/logx" + "hyapp/services/game-service/internal/client" + "hyapp/services/game-service/internal/config" + gameservice "hyapp/services/game-service/internal/service/game" + mysqlstorage "hyapp/services/game-service/internal/storage/mysql" + grpcserver "hyapp/services/game-service/internal/transport/grpc" +) + +// App 装配 game-service gRPC 入口和持久化依赖。 +type App struct { + server *grpc.Server + listener net.Listener + health *grpchealth.ServingChecker + repo *mysqlstorage.Repository + walletConn *grpc.ClientConn + userConn *grpc.ClientConn + closeOnce sync.Once +} + +func New(cfg config.Config) (*App, error) { + startupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + repo, err := mysqlstorage.Open(startupCtx, cfg.MySQLDSN) + if err != nil { + return nil, err + } + walletConn, err := grpc.Dial(cfg.WalletServiceAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + _ = repo.Close() + return nil, err + } + userConn, err := grpc.Dial(cfg.UserServiceAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + _ = walletConn.Close() + _ = repo.Close() + return nil, err + } + listener, err := net.Listen("tcp", cfg.GRPCAddr) + if err != nil { + _ = userConn.Close() + _ = walletConn.Close() + _ = repo.Close() + return nil, err + } + + server := grpc.NewServer(grpc.UnaryInterceptor(logx.UnaryServerInterceptor("game-service"))) + svc := gameservice.New(gameservice.Config{LaunchSessionTTL: cfg.LaunchSessionTTL}, repo, client.NewWalletClient(walletConn), client.NewUserClient(userConn)) + transport := grpcserver.NewServer(svc) + gamev1.RegisterGameAppServiceServer(server, transport) + gamev1.RegisterGameCallbackServiceServer(server, transport) + gamev1.RegisterGameAdminServiceServer(server, transport) + health := grpchealth.NewServingChecker("game-service") + healthgrpc.RegisterHealthServer(server, grpchealth.NewServer(health)) + + return &App{server: server, listener: listener, health: health, repo: repo, walletConn: walletConn, userConn: userConn}, nil +} + +func (a *App) Run() error { + a.health.MarkServing() + err := a.server.Serve(a.listener) + a.health.MarkStopped() + if errors.Is(err, grpc.ErrServerStopped) { + return nil + } + return err +} + +func (a *App) Close() { + a.closeOnce.Do(func() { + a.health.MarkDraining() + a.server.GracefulStop() + if a.walletConn != nil { + _ = a.walletConn.Close() + } + if a.userConn != nil { + _ = a.userConn.Close() + } + if a.repo != nil { + _ = a.repo.Close() + } + }) +} diff --git a/services/game-service/internal/client/user_client.go b/services/game-service/internal/client/user_client.go new file mode 100644 index 00000000..a27e06a1 --- /dev/null +++ b/services/game-service/internal/client/user_client.go @@ -0,0 +1,21 @@ +package client + +import ( + "context" + + userv1 "hyapp.local/api/proto/user/v1" + + "google.golang.org/grpc" +) + +type UserClient struct { + client userv1.UserServiceClient +} + +func NewUserClient(conn *grpc.ClientConn) *UserClient { + return &UserClient{client: userv1.NewUserServiceClient(conn)} +} + +func (c *UserClient) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) { + return c.client.GetUser(ctx, req) +} diff --git a/services/game-service/internal/client/wallet_client.go b/services/game-service/internal/client/wallet_client.go new file mode 100644 index 00000000..2fcde252 --- /dev/null +++ b/services/game-service/internal/client/wallet_client.go @@ -0,0 +1,24 @@ +package client + +import ( + "context" + + "google.golang.org/grpc" + walletv1 "hyapp.local/api/proto/wallet/v1" +) + +type WalletClient struct { + client walletv1.WalletServiceClient +} + +func NewWalletClient(conn *grpc.ClientConn) *WalletClient { + return &WalletClient{client: walletv1.NewWalletServiceClient(conn)} +} + +func (c *WalletClient) ApplyGameCoinChange(ctx context.Context, req *walletv1.ApplyGameCoinChangeRequest) (*walletv1.ApplyGameCoinChangeResponse, error) { + return c.client.ApplyGameCoinChange(ctx, req) +} + +func (c *WalletClient) GetBalances(ctx context.Context, req *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error) { + return c.client.GetBalances(ctx, req) +} diff --git a/services/game-service/internal/config/config.go b/services/game-service/internal/config/config.go new file mode 100644 index 00000000..60958a6c --- /dev/null +++ b/services/game-service/internal/config/config.go @@ -0,0 +1,92 @@ +package config + +import ( + "fmt" + "strings" + "time" + + "hyapp/pkg/configx" + "hyapp/pkg/logx" +) + +// Config 描述 game-service 启动所需的最小依赖。 +type Config struct { + ServiceName string `yaml:"service_name"` + NodeID string `yaml:"node_id"` + Environment string `yaml:"environment"` + GRPCAddr string `yaml:"grpc_addr"` + MySQLDSN string `yaml:"mysql_dsn"` + WalletServiceAddr string `yaml:"wallet_service_addr"` + UserServiceAddr string `yaml:"user_service_addr"` + LaunchSessionTTL time.Duration `yaml:"launch_session_ttl"` + Log logx.Config `yaml:"log"` +} + +// Default 返回本地开发默认配置。 +func Default() Config { + return Config{ + ServiceName: "game-service", + NodeID: "game-local", + Environment: "local", + GRPCAddr: ":13008", + MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_game?parseTime=true&charset=utf8mb4&loc=UTC&multiStatements=true", + WalletServiceAddr: "127.0.0.1:13004", + UserServiceAddr: "127.0.0.1:13005", + LaunchSessionTTL: 15 * time.Minute, + Log: logx.Config{ + Level: "info", + Format: "json", + MaxPayloadBytes: 2048, + }, + } +} + +// Load 从 YAML 文件读取配置,并填充默认值。 +func Load(path string) (Config, error) { + cfg := Default() + if path != "" { + if err := configx.LoadYAML(path, &cfg); err != nil { + return Config{}, err + } + } + if err := cfg.Normalize(); err != nil { + return Config{}, err + } + return cfg, nil +} + +// Normalize 在启动时收敛配置,避免运行期才暴露关键依赖缺失。 +func (cfg *Config) Normalize() error { + cfg.ServiceName = strings.TrimSpace(cfg.ServiceName) + if cfg.ServiceName == "" { + cfg.ServiceName = "game-service" + } + cfg.NodeID = strings.TrimSpace(cfg.NodeID) + if cfg.NodeID == "" { + cfg.NodeID = cfg.ServiceName + } + cfg.Environment = strings.TrimSpace(cfg.Environment) + if cfg.Environment == "" { + cfg.Environment = "local" + } + cfg.GRPCAddr = strings.TrimSpace(cfg.GRPCAddr) + if cfg.GRPCAddr == "" { + cfg.GRPCAddr = ":13008" + } + cfg.MySQLDSN = strings.TrimSpace(cfg.MySQLDSN) + if cfg.MySQLDSN == "" { + return fmt.Errorf("mysql_dsn is required") + } + cfg.WalletServiceAddr = strings.TrimSpace(cfg.WalletServiceAddr) + if cfg.WalletServiceAddr == "" { + return fmt.Errorf("wallet_service_addr is required") + } + cfg.UserServiceAddr = strings.TrimSpace(cfg.UserServiceAddr) + if cfg.UserServiceAddr == "" { + return fmt.Errorf("user_service_addr is required") + } + if cfg.LaunchSessionTTL <= 0 { + cfg.LaunchSessionTTL = 15 * time.Minute + } + return nil +} diff --git a/services/game-service/internal/domain/game/game.go b/services/game-service/internal/domain/game/game.go new file mode 100644 index 00000000..216bdae5 --- /dev/null +++ b/services/game-service/internal/domain/game/game.go @@ -0,0 +1,131 @@ +package game + +const ( + StatusActive = "active" + StatusMaintenance = "maintenance" + StatusDisabled = "disabled" + + SceneVoiceRoom = "voice_room" + + LaunchModeH5Popup = "h5_popup" + SessionActive = "active" + + OrderStatusWalletApplying = "wallet_applying" + OrderStatusSucceeded = "succeeded" + OrderStatusFailed = "failed" + OrderStatusConflict = "conflict" +) + +// Platform 是第三方游戏平台配置事实。 +type Platform struct { + AppCode string + PlatformCode string + PlatformName string + Status string + APIBaseURL string + SortOrder int32 + CreatedAtMS int64 + UpdatedAtMS int64 +} + +// CatalogItem 是内部稳定 game_id 到第三方 provider_game_id 的映射。 +type CatalogItem struct { + AppCode string + GameID string + PlatformCode string + ProviderGameID string + GameName string + Category string + IconURL string + CoverURL string + LaunchMode string + Orientation string + MinCoin int64 + Status string + SortOrder int32 + Tags []string + CreatedAtMS int64 + UpdatedAtMS int64 +} + +// AppGame 是 App 语音房内看到的跨平台合并游戏卡片。 +type AppGame struct { + GameID string + PlatformCode string + NameKey string + Name string + IconURL string + CoverURL string + Category string + LaunchMode string + Orientation string + MinCoin int64 + Enabled bool + Maintenance bool + SortOrder int32 +} + +// LaunchableGame 汇总启动时需要的平台和目录配置。 +type LaunchableGame struct { + CatalogItem + PlatformName string + PlatformStatus string + APIBaseURL string +} + +// LaunchSession 是 H5 启动会话持久事实,token 只保存 hash。 +type LaunchSession struct { + AppCode string + SessionID string + UserID int64 + DisplayUserID string + RoomID string + Scene string + PlatformCode string + GameID string + ProviderGameID string + LaunchTokenHash string + Status string + ExpiresAtMS int64 + CreatedAtMS int64 + UpdatedAtMS int64 +} + +// GameOrder 是 provider_order_id 对应的钱包改账事实。 +type GameOrder struct { + AppCode string + OrderID string + PlatformCode string + ProviderOrderID string + ProviderRoundID string + GameID string + ProviderGameID string + UserID int64 + RoomID string + OpType string + CoinAmount int64 + Status string + WalletTransactionID string + WalletBalanceAfter int64 + RequestHash string + FailureCode string + FailureMessage string + CreatedAtMS int64 + UpdatedAtMS int64 +} + +// CallbackLog 保存平台回调审计摘要,原始 body 后续可迁移到对象存储。 +type CallbackLog struct { + AppCode string + CallbackID string + PlatformCode string + Operation string + RequestID string + ProviderRequestID string + SignatureValid bool + RequestHash string + ResponseCode string + ResponseBodyHash string + Status string + CreatedAtMS int64 +} diff --git a/services/game-service/internal/service/game/service.go b/services/game-service/internal/service/game/service.go new file mode 100644 index 00000000..8351b03b --- /dev/null +++ b/services/game-service/internal/service/game/service.go @@ -0,0 +1,478 @@ +package game + +import ( + "context" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "net/url" + "strconv" + "strings" + "time" + + gamev1 "hyapp.local/api/proto/game/v1" + userv1 "hyapp.local/api/proto/user/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + gamedomain "hyapp/services/game-service/internal/domain/game" +) + +const defaultScene = gamedomain.SceneVoiceRoom + +// Repository 隔离 game-service 持久化事实,订单和 session 不能只放内存。 +type Repository interface { + Ping(ctx context.Context) error + ListGames(ctx context.Context, query ListGamesQuery) ([]gamedomain.AppGame, error) + GetLaunchableGame(ctx context.Context, appCode string, gameID string) (gamedomain.LaunchableGame, error) + CreateLaunchSession(ctx context.Context, session gamedomain.LaunchSession) error + InsertCallbackLog(ctx context.Context, log gamedomain.CallbackLog) error + CreateGameOrder(ctx context.Context, order gamedomain.GameOrder) (gamedomain.GameOrder, bool, error) + MarkOrderSucceeded(ctx context.Context, appCode string, orderID string, walletTransactionID string, balanceAfter int64, nowMs int64) error + MarkOrderFailed(ctx context.Context, appCode string, orderID string, status string, code string, message string, nowMs int64) error + ListPlatforms(ctx context.Context, appCode string, status string) ([]gamedomain.Platform, error) + UpsertPlatform(ctx context.Context, platform gamedomain.Platform) (gamedomain.Platform, error) + ListCatalog(ctx context.Context, query ListCatalogQuery) ([]gamedomain.CatalogItem, string, error) + UpsertCatalog(ctx context.Context, item gamedomain.CatalogItem) (gamedomain.CatalogItem, error) + SetGameStatus(ctx context.Context, appCode string, gameID string, status string, nowMs int64) (gamedomain.CatalogItem, error) +} + +// WalletClient 是 game-service 调 wallet-service 的最小改账依赖。 +type WalletClient interface { + ApplyGameCoinChange(ctx context.Context, req *walletv1.ApplyGameCoinChangeRequest) (*walletv1.ApplyGameCoinChangeResponse, error) + GetBalances(ctx context.Context, req *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error) +} + +// UserClient 只暴露平台回调需要的用户展示资料查询能力。 +type UserClient interface { + GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) +} + +type Config struct { + LaunchSessionTTL time.Duration +} + +type Service struct { + config Config + repository Repository + wallet WalletClient + user UserClient + now func() time.Time +} + +type ListGamesQuery struct { + AppCode string + UserID int64 + Scene string + RoomID string + RegionID int64 + Language string + ClientPlatform string +} + +type ListCatalogQuery struct { + AppCode string + PlatformCode string + Status string + PageSize int32 + Cursor string +} + +type LaunchCommand struct { + AppCode string + RequestID string + UserID int64 + DisplayUserID string + GameID string + Scene string + RoomID string +} + +type LaunchResult struct { + SessionID string + LaunchURL string + ExpiresAtMS int64 + Orientation string + ServerTimeMS int64 +} + +// New 创建 game-service 用例层。 +func New(config Config, repository Repository, wallet WalletClient, user UserClient) *Service { + if config.LaunchSessionTTL <= 0 { + config.LaunchSessionTTL = 15 * time.Minute + } + return &Service{ + config: config, + repository: repository, + wallet: wallet, + user: user, + now: time.Now, + } +} + +// Repository 只给 transport/admin 薄封装复用;业务主链路仍通过 Service 方法表达。 +func (s *Service) Repository() Repository { + if s == nil { + return nil + } + return s.repository +} + +// ListGames 返回 App 语音房里的跨平台合并游戏列表。 +func (s *Service) ListGames(ctx context.Context, query ListGamesQuery) ([]gamedomain.AppGame, 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, "game repository is not configured") + } + query.AppCode = appcode.Normalize(query.AppCode) + query.Scene = normalizeScene(query.Scene) + query.Language = strings.ToLower(strings.TrimSpace(query.Language)) + query.ClientPlatform = strings.ToLower(strings.TrimSpace(query.ClientPlatform)) + ctx = appcode.WithContext(ctx, query.AppCode) + + games, err := s.repository.ListGames(ctx, query) + return games, s.now().UnixMilli(), err +} + +// LaunchGame 创建短期 H5 启动会话,并把原始钱包能力限制在服务端。 +func (s *Service) LaunchGame(ctx context.Context, command LaunchCommand) (LaunchResult, error) { + if command.UserID <= 0 || strings.TrimSpace(command.GameID) == "" { + return LaunchResult{}, xerr.New(xerr.InvalidArgument, "launch command is incomplete") + } + if s.repository == nil { + return LaunchResult{}, xerr.New(xerr.Unavailable, "game repository is not configured") + } + command.AppCode = appcode.Normalize(command.AppCode) + command.Scene = normalizeScene(command.Scene) + command.DisplayUserID = strings.TrimSpace(command.DisplayUserID) + if command.DisplayUserID == "" { + command.DisplayUserID = strconv.FormatInt(command.UserID, 10) + } + ctx = appcode.WithContext(ctx, command.AppCode) + + game, err := s.repository.GetLaunchableGame(ctx, command.AppCode, strings.TrimSpace(command.GameID)) + if err != nil { + return LaunchResult{}, err + } + if game.PlatformStatus != gamedomain.StatusActive || game.Status != gamedomain.StatusActive { + return LaunchResult{}, xerr.New(xerr.Conflict, "game is not launchable") + } + + now := s.now() + sessionID := "game_sess_" + strconv.FormatInt(now.UnixMilli(), 10) + "_" + randomHex(6) + token := "gt_" + randomHex(24) + expiresAt := now.Add(s.config.LaunchSessionTTL).UnixMilli() + session := gamedomain.LaunchSession{ + AppCode: command.AppCode, + SessionID: sessionID, + UserID: command.UserID, + DisplayUserID: command.DisplayUserID, + RoomID: strings.TrimSpace(command.RoomID), + Scene: command.Scene, + PlatformCode: game.PlatformCode, + GameID: game.GameID, + ProviderGameID: game.ProviderGameID, + LaunchTokenHash: stableHash(token), + Status: gamedomain.SessionActive, + ExpiresAtMS: expiresAt, + CreatedAtMS: now.UnixMilli(), + UpdatedAtMS: now.UnixMilli(), + } + if err := s.repository.CreateLaunchSession(ctx, session); err != nil { + return LaunchResult{}, err + } + + launchURL, err := buildLaunchURL(game, session, token) + if err != nil { + return LaunchResult{}, err + } + return LaunchResult{ + SessionID: sessionID, + LaunchURL: launchURL, + ExpiresAtMS: expiresAt, + Orientation: game.Orientation, + ServerTimeMS: now.UnixMilli(), + }, nil +} + +// HandleCallback 保留 provider 原始请求摘要,并对 demo adapter 支持余额查询和金币改账。 +func (s *Service) HandleCallback(ctx context.Context, req *gamev1.CallbackRequest) ([]byte, string, error) { + if req == nil || strings.TrimSpace(req.GetPlatformCode()) == "" || strings.TrimSpace(req.GetOperation()) == "" { + return nil, "", xerr.New(xerr.InvalidArgument, "callback request is incomplete") + } + if s.repository == nil { + return nil, "", xerr.New(xerr.Unavailable, "game repository is not configured") + } + app := appcode.Normalize(req.GetMeta().GetAppCode()) + ctx = appcode.WithContext(ctx, app) + operation := strings.ToLower(strings.TrimSpace(req.GetOperation())) + requestHash := stableHashBytes(req.GetRawBody()) + callbackID := "gcb_" + stableHash(fmt.Sprintf("%s|%s|%s|%d", app, req.GetPlatformCode(), requestHash, s.now().UnixNano())) + + responseBody, contentType, statusCode, err := s.handleDemoOperation(ctx, app, req, operation, requestHash) + responseHash := stableHashBytes(responseBody) + logStatus := "succeeded" + if err != nil { + logStatus = "failed" + responseBody, contentType = jsonResponse(map[string]any{"code": string(xerr.CodeOf(err)), "message": xerr.MessageOf(err)}) + statusCode = string(xerr.CodeOf(err)) + responseHash = stableHashBytes(responseBody) + } + _ = s.repository.InsertCallbackLog(ctx, gamedomain.CallbackLog{ + AppCode: app, + CallbackID: callbackID, + PlatformCode: strings.TrimSpace(req.GetPlatformCode()), + Operation: operation, + RequestID: req.GetMeta().GetRequestId(), + SignatureValid: false, + RequestHash: requestHash, + ResponseCode: statusCode, + ResponseBodyHash: responseHash, + Status: logStatus, + CreatedAtMS: s.now().UnixMilli(), + }) + return responseBody, contentType, err +} + +func (s *Service) handleDemoOperation(ctx context.Context, app string, req *gamev1.CallbackRequest, operation string, requestHash string) ([]byte, string, string, error) { + switch operation { + case "get_user_info": + result, err := s.getCallbackUserInfo(ctx, app, req) + if err != nil { + return nil, "", "", err + } + raw, contentType := jsonResponse(result) + return raw, contentType, "OK", nil + case "get_balance": + if s.wallet == nil { + return nil, "", "", xerr.New(xerr.Unavailable, "wallet client is not configured") + } + var body struct { + UserID int64 `json:"user_id"` + } + if err := json.Unmarshal(req.GetRawBody(), &body); err != nil || body.UserID <= 0 { + return nil, "", "", xerr.New(xerr.InvalidArgument, "invalid get_balance payload") + } + resp, err := s.wallet.GetBalances(ctx, &walletv1.GetBalancesRequest{ + RequestId: req.GetMeta().GetRequestId(), + AppCode: app, + UserId: body.UserID, + AssetTypes: []string{"COIN"}, + }) + if err != nil { + return nil, "", "", err + } + var balance int64 + for _, item := range resp.GetBalances() { + if item.GetAssetType() == "COIN" { + balance = item.GetAvailableAmount() + } + } + raw, contentType := jsonResponse(map[string]any{"code": 0, "balance": balance}) + return raw, contentType, "OK", nil + case "change_coin": + result, err := s.applyCallbackCoinChange(ctx, app, req, requestHash) + if err != nil { + return nil, "", "", err + } + raw, contentType := jsonResponse(result) + return raw, contentType, "OK", nil + default: + raw, contentType := jsonResponse(map[string]any{"code": 0, "message": "ok"}) + return raw, contentType, "OK", nil + } +} + +func (s *Service) getCallbackUserInfo(ctx context.Context, app string, req *gamev1.CallbackRequest) (map[string]any, error) { + if s.user == nil || s.wallet == nil { + return nil, xerr.New(xerr.Unavailable, "user or wallet client is not configured") + } + var body struct { + UserID int64 `json:"user_id"` + } + if err := json.Unmarshal(req.GetRawBody(), &body); err != nil || body.UserID <= 0 { + return nil, xerr.New(xerr.InvalidArgument, "invalid get_user_info payload") + } + userResp, err := s.user.GetUser(ctx, &userv1.GetUserRequest{ + Meta: &userv1.RequestMeta{ + RequestId: req.GetMeta().GetRequestId(), + Caller: "game-service", + SentAtMs: s.now().UnixMilli(), + AppCode: app, + }, + UserId: body.UserID, + }) + if err != nil { + return nil, err + } + balanceResp, err := s.wallet.GetBalances(ctx, &walletv1.GetBalancesRequest{ + RequestId: req.GetMeta().GetRequestId(), + AppCode: app, + UserId: body.UserID, + AssetTypes: []string{"COIN"}, + }) + if err != nil { + return nil, err + } + var balance int64 + for _, item := range balanceResp.GetBalances() { + if item.GetAssetType() == "COIN" { + balance = item.GetAvailableAmount() + break + } + } + user := userResp.GetUser() + return map[string]any{ + "code": 0, + "user_id": body.UserID, + "display_user_id": user.GetDisplayUserId(), + "nickname": user.GetUsername(), + "avatar": user.GetAvatar(), + "country": user.GetCountry(), + "region_id": user.GetRegionId(), + "balance": balance, + }, nil +} + +func (s *Service) applyCallbackCoinChange(ctx context.Context, app string, req *gamev1.CallbackRequest, requestHash string) (map[string]any, error) { + if s.wallet == nil { + return nil, xerr.New(xerr.Unavailable, "wallet client is not configured") + } + var body struct { + UserID int64 `json:"user_id"` + GameID string `json:"game_id"` + ProviderGameID string `json:"provider_game_id"` + ProviderOrderID string `json:"provider_order_id"` + ProviderRoundID string `json:"provider_round_id"` + OpType string `json:"op_type"` + CoinAmount int64 `json:"coin_amount"` + RoomID string `json:"room_id"` + } + if err := json.Unmarshal(req.GetRawBody(), &body); err != nil { + return nil, xerr.New(xerr.InvalidArgument, "invalid change_coin payload") + } + body.GameID = strings.TrimSpace(body.GameID) + body.ProviderOrderID = strings.TrimSpace(body.ProviderOrderID) + body.OpType = strings.TrimSpace(body.OpType) + if body.UserID <= 0 || body.GameID == "" || body.ProviderOrderID == "" || body.OpType == "" || body.CoinAmount <= 0 { + return nil, xerr.New(xerr.InvalidArgument, "change_coin payload is incomplete") + } + if body.ProviderGameID == "" { + game, err := s.repository.GetLaunchableGame(ctx, app, body.GameID) + if err != nil { + return nil, err + } + body.ProviderGameID = game.ProviderGameID + } + order := gamedomain.GameOrder{ + AppCode: app, + OrderID: "gord_" + stableHash(app+"|"+req.GetPlatformCode()+"|"+body.ProviderOrderID), + PlatformCode: strings.TrimSpace(req.GetPlatformCode()), + ProviderOrderID: body.ProviderOrderID, + ProviderRoundID: body.ProviderRoundID, + GameID: body.GameID, + ProviderGameID: body.ProviderGameID, + UserID: body.UserID, + RoomID: strings.TrimSpace(body.RoomID), + OpType: body.OpType, + CoinAmount: body.CoinAmount, + Status: gamedomain.OrderStatusWalletApplying, + RequestHash: requestHash, + } + order, exists, err := s.repository.CreateGameOrder(ctx, order) + if err != nil { + return nil, err + } + if exists && order.Status == gamedomain.OrderStatusSucceeded { + return map[string]any{"code": 0, "order_id": order.OrderID, "wallet_transaction_id": order.WalletTransactionID, "balance_after": order.WalletBalanceAfter, "idempotent_replay": true}, nil + } + + walletResp, err := s.wallet.ApplyGameCoinChange(ctx, &walletv1.ApplyGameCoinChangeRequest{ + RequestId: req.GetMeta().GetRequestId(), + AppCode: app, + CommandId: "game:" + strings.TrimSpace(req.GetPlatformCode()) + ":" + body.ProviderOrderID, + UserId: body.UserID, + PlatformCode: strings.TrimSpace(req.GetPlatformCode()), + GameId: body.GameID, + ProviderOrderId: body.ProviderOrderID, + ProviderRoundId: body.ProviderRoundID, + OpType: body.OpType, + CoinAmount: body.CoinAmount, + RoomId: body.RoomID, + RequestHash: requestHash, + }) + nowMs := s.now().UnixMilli() + if err != nil { + status := gamedomain.OrderStatusFailed + if xerr.IsCode(err, xerr.IdempotencyConflict) { + status = gamedomain.OrderStatusConflict + } + _ = s.repository.MarkOrderFailed(ctx, app, order.OrderID, status, string(xerr.ReasonFromGRPC(err)), err.Error(), nowMs) + return nil, err + } + if err := s.repository.MarkOrderSucceeded(ctx, app, order.OrderID, walletResp.GetWalletTransactionId(), walletResp.GetBalanceAfter(), nowMs); err != nil { + return nil, err + } + return map[string]any{"code": 0, "order_id": order.OrderID, "wallet_transaction_id": walletResp.GetWalletTransactionId(), "balance_after": walletResp.GetBalanceAfter(), "idempotent_replay": walletResp.GetIdempotentReplay()}, nil +} + +func normalizeScene(scene string) string { + scene = strings.ToLower(strings.TrimSpace(scene)) + if scene == "" { + return defaultScene + } + return scene +} + +func buildLaunchURL(game gamedomain.LaunchableGame, session gamedomain.LaunchSession, token string) (string, error) { + base := strings.TrimSpace(game.APIBaseURL) + if base == "" { + base = "https://game.example.local/h5" + } + parsed, err := url.Parse(base) + if err != nil { + return "", xerr.New(xerr.InvalidArgument, "platform launch url is invalid") + } + query := parsed.Query() + query.Set("session_id", session.SessionID) + query.Set("session_token", token) + query.Set("game_id", session.GameID) + query.Set("provider_game_id", session.ProviderGameID) + query.Set("platform_code", session.PlatformCode) + query.Set("user_id", strconv.FormatInt(session.UserID, 10)) + query.Set("display_user_id", session.DisplayUserID) + query.Set("scene", session.Scene) + query.Set("room_id", session.RoomID) + parsed.RawQuery = query.Encode() + return parsed.String(), nil +} + +func jsonResponse(value any) ([]byte, string) { + body, err := json.Marshal(value) + if err != nil { + return []byte(`{"code":"INTERNAL_ERROR","message":"internal error"}`), "application/json" + } + return body, "application/json" +} + +func randomHex(size int) string { + bytes := make([]byte, size) + if _, err := rand.Read(bytes); err != nil { + return strconv.FormatInt(time.Now().UnixNano(), 16) + } + return hex.EncodeToString(bytes) +} + +func stableHash(value string) string { + sum := sha256.Sum256([]byte(value)) + return hex.EncodeToString(sum[:]) +} + +func stableHashBytes(value []byte) string { + sum := sha256.Sum256(value) + return hex.EncodeToString(sum[:]) +} diff --git a/services/game-service/internal/service/game/service_test.go b/services/game-service/internal/service/game/service_test.go new file mode 100644 index 00000000..325a88e8 --- /dev/null +++ b/services/game-service/internal/service/game/service_test.go @@ -0,0 +1,205 @@ +package game + +import ( + "context" + "strings" + "testing" + "time" + + gamev1 "hyapp.local/api/proto/game/v1" + userv1 "hyapp.local/api/proto/user/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/xerr" + gamedomain "hyapp/services/game-service/internal/domain/game" +) + +func TestLaunchGameCreatesSessionAndRejectsMaintenance(t *testing.T) { + repo := &fakeRepository{ + launchable: gamedomain.LaunchableGame{ + CatalogItem: gamedomain.CatalogItem{ + AppCode: "lalu", + GameID: "demo_rocket_001", + PlatformCode: "demo", + ProviderGameID: "rocket_001", + GameName: "Rocket", + Status: gamedomain.StatusActive, + Orientation: "portrait", + }, + PlatformStatus: gamedomain.StatusActive, + APIBaseURL: "https://provider.example/h5", + }, + } + svc := New(Config{LaunchSessionTTL: 15 * time.Minute}, repo, &fakeWallet{}, &fakeUser{}) + svc.now = func() time.Time { return time.UnixMilli(1700000000000) } + + result, err := svc.LaunchGame(context.Background(), LaunchCommand{ + AppCode: "lalu", + UserID: 42, + DisplayUserID: "420001", + GameID: "demo_rocket_001", + Scene: gamedomain.SceneVoiceRoom, + RoomID: "room_1", + }) + if err != nil { + t.Fatalf("LaunchGame failed: %v", err) + } + if result.SessionID == "" || result.ExpiresAtMS != 1700000900000 || !strings.Contains(result.LaunchURL, "session_token=") { + t.Fatalf("launch result mismatch: %+v", result) + } + if repo.session.GameID != "demo_rocket_001" || repo.session.UserID != 42 || repo.session.LaunchTokenHash == "" { + t.Fatalf("launch session was not persisted: %+v", repo.session) + } + + repo.launchable.PlatformStatus = gamedomain.StatusMaintenance + _, err = svc.LaunchGame(context.Background(), LaunchCommand{AppCode: "lalu", UserID: 42, GameID: "demo_rocket_001"}) + if !xerr.IsCode(err, xerr.Conflict) { + t.Fatalf("maintenance game should reject launch, got %v", err) + } +} + +func TestHandleCallbackChangeCoinUsesWalletAndOrderIdempotency(t *testing.T) { + repo := &fakeRepository{ + launchable: gamedomain.LaunchableGame{CatalogItem: gamedomain.CatalogItem{GameID: "demo_rocket_001", ProviderGameID: "rocket_001"}}, + } + wallet := &fakeWallet{balanceAfter: 880} + svc := New(Config{}, repo, wallet, &fakeUser{}) + svc.now = func() time.Time { return time.UnixMilli(1700000000000) } + + req := &gamev1.CallbackRequest{ + Meta: &gamev1.RequestMeta{RequestId: "req-callback", AppCode: "lalu"}, + PlatformCode: "demo", + Operation: "change_coin", + RawBody: []byte(`{"user_id":42,"game_id":"demo_rocket_001","provider_order_id":"porder_1","op_type":"debit","coin_amount":120,"room_id":"room_1"}`), + } + raw, contentType, err := svc.HandleCallback(context.Background(), req) + if err != nil { + t.Fatalf("HandleCallback failed: %v", err) + } + if contentType != "application/json" || !strings.Contains(string(raw), `"balance_after":880`) { + t.Fatalf("callback response mismatch: contentType=%s raw=%s", contentType, raw) + } + if wallet.lastApply.GetCommandId() != "game:demo:porder_1" || wallet.lastApply.GetCoinAmount() != 120 { + t.Fatalf("wallet command mismatch: %+v", wallet.lastApply) + } + if repo.order.Status != gamedomain.OrderStatusSucceeded || repo.order.WalletTransactionID != "wtx-game-1" { + t.Fatalf("order state mismatch: %+v", repo.order) + } + + raw, _, err = svc.HandleCallback(context.Background(), req) + if err != nil { + t.Fatalf("idempotent callback failed: %v", err) + } + if !strings.Contains(string(raw), `"idempotent_replay":true`) { + t.Fatalf("idempotent response mismatch: %s", raw) + } +} + +func TestHandleCallbackGetUserInfoUsesUserAndWallet(t *testing.T) { + wallet := &fakeWallet{balanceAfter: 1680} + user := &fakeUser{} + svc := New(Config{}, &fakeRepository{}, wallet, user) + svc.now = func() time.Time { return time.UnixMilli(1700000000000) } + + raw, contentType, err := svc.HandleCallback(context.Background(), &gamev1.CallbackRequest{ + Meta: &gamev1.RequestMeta{RequestId: "req-user-info", AppCode: "lalu"}, + PlatformCode: "demo", + Operation: "get_user_info", + RawBody: []byte(`{"user_id":42}`), + }) + if err != nil { + t.Fatalf("HandleCallback failed: %v", err) + } + if contentType != "application/json" || !strings.Contains(string(raw), `"display_user_id":"420001"`) || !strings.Contains(string(raw), `"balance":1680`) { + t.Fatalf("user info response mismatch: %s", raw) + } + if user.lastGet.GetUserId() != 42 { + t.Fatalf("user request mismatch: %+v", user.lastGet) + } +} + +type fakeRepository struct { + launchable gamedomain.LaunchableGame + session gamedomain.LaunchSession + order gamedomain.GameOrder +} + +func (f *fakeRepository) Ping(context.Context) error { return nil } +func (f *fakeRepository) ListGames(context.Context, ListGamesQuery) ([]gamedomain.AppGame, error) { + return []gamedomain.AppGame{{GameID: "demo_rocket_001"}}, nil +} +func (f *fakeRepository) GetLaunchableGame(context.Context, string, string) (gamedomain.LaunchableGame, error) { + return f.launchable, nil +} +func (f *fakeRepository) CreateLaunchSession(_ context.Context, session gamedomain.LaunchSession) error { + f.session = session + return nil +} +func (f *fakeRepository) InsertCallbackLog(context.Context, gamedomain.CallbackLog) error { return nil } +func (f *fakeRepository) CreateGameOrder(_ context.Context, order gamedomain.GameOrder) (gamedomain.GameOrder, bool, error) { + if f.order.OrderID != "" { + return f.order, true, nil + } + f.order = order + return order, false, nil +} +func (f *fakeRepository) MarkOrderSucceeded(_ context.Context, _ string, orderID string, walletTransactionID string, balanceAfter int64, nowMs int64) error { + f.order.OrderID = orderID + f.order.Status = gamedomain.OrderStatusSucceeded + f.order.WalletTransactionID = walletTransactionID + f.order.WalletBalanceAfter = balanceAfter + f.order.UpdatedAtMS = nowMs + return nil +} +func (f *fakeRepository) MarkOrderFailed(_ context.Context, _ string, orderID string, status string, code string, message string, nowMs int64) error { + f.order.OrderID = orderID + f.order.Status = status + f.order.FailureCode = code + f.order.FailureMessage = message + f.order.UpdatedAtMS = nowMs + return nil +} +func (f *fakeRepository) ListPlatforms(context.Context, string, string) ([]gamedomain.Platform, error) { + return nil, nil +} +func (f *fakeRepository) UpsertPlatform(context.Context, gamedomain.Platform) (gamedomain.Platform, error) { + return gamedomain.Platform{}, nil +} +func (f *fakeRepository) ListCatalog(context.Context, ListCatalogQuery) ([]gamedomain.CatalogItem, string, error) { + return nil, "", nil +} +func (f *fakeRepository) UpsertCatalog(context.Context, gamedomain.CatalogItem) (gamedomain.CatalogItem, error) { + return gamedomain.CatalogItem{}, nil +} +func (f *fakeRepository) SetGameStatus(context.Context, string, string, string, int64) (gamedomain.CatalogItem, error) { + return gamedomain.CatalogItem{}, nil +} + +type fakeWallet struct { + lastApply *walletv1.ApplyGameCoinChangeRequest + balanceAfter int64 +} + +func (f *fakeWallet) ApplyGameCoinChange(_ context.Context, req *walletv1.ApplyGameCoinChangeRequest) (*walletv1.ApplyGameCoinChangeResponse, error) { + f.lastApply = req + return &walletv1.ApplyGameCoinChangeResponse{WalletTransactionId: "wtx-game-1", BalanceAfter: f.balanceAfter}, nil +} + +func (f *fakeWallet) GetBalances(context.Context, *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error) { + return &walletv1.GetBalancesResponse{Balances: []*walletv1.AssetBalance{{AssetType: "COIN", AvailableAmount: f.balanceAfter}}}, nil +} + +type fakeUser struct { + lastGet *userv1.GetUserRequest +} + +func (f *fakeUser) GetUser(_ context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) { + f.lastGet = req + return &userv1.GetUserResponse{User: &userv1.User{ + UserId: req.GetUserId(), + DisplayUserId: "420001", + Username: "tester", + Avatar: "https://cdn.example/avatar.png", + Country: "AE", + RegionId: 100, + }}, nil +} diff --git a/services/game-service/internal/storage/mysql/repository.go b/services/game-service/internal/storage/mysql/repository.go new file mode 100644 index 00000000..9e90c72b --- /dev/null +++ b/services/game-service/internal/storage/mysql/repository.go @@ -0,0 +1,546 @@ +package mysql + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "strings" + "time" + + _ "github.com/go-sql-driver/mysql" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + gamedomain "hyapp/services/game-service/internal/domain/game" + gameservice "hyapp/services/game-service/internal/service/game" +) + +// Repository 是 game-service 的 MySQL 持久化入口。 +type Repository struct { + db *sql.DB +} + +// Open 创建连接池并执行幂等迁移,保证本地已有数据库也能补齐游戏表。 +func Open(ctx context.Context, dsn string) (*Repository, error) { + if strings.TrimSpace(dsn) == "" { + return nil, xerr.New(xerr.InvalidArgument, "mysql_dsn is required") + } + db, err := sql.Open("mysql", dsn) + if err != nil { + return nil, err + } + if err := db.PingContext(ctx); err != nil { + _ = db.Close() + return nil, err + } + repo := &Repository{db: db} + if err := repo.Migrate(ctx); err != nil { + _ = db.Close() + return nil, err + } + return repo, nil +} + +func (r *Repository) Close() error { + if r == nil || r.db == nil { + return nil + } + return r.db.Close() +} + +func (r *Repository) Ping(ctx context.Context) error { + if r == nil || r.db == nil { + return xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + return r.db.PingContext(ctx) +} + +// Migrate 与 initdb 保持同构;开发阶段不保留旧 schema 兼容分支。 +func (r *Repository) Migrate(ctx context.Context) error { + statements := []string{ + `CREATE TABLE IF NOT EXISTS game_platforms ( + app_code VARCHAR(32) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + platform_name VARCHAR(128) NOT NULL, + status VARCHAR(32) NOT NULL, + api_base_url VARCHAR(512) NOT NULL DEFAULT '', + callback_secret_ciphertext VARCHAR(1024) NOT NULL DEFAULT '', + callback_ip_whitelist JSON NULL, + adapter_config JSON NULL, + sort_order INT NOT NULL DEFAULT 0, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, platform_code) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS game_catalog ( + app_code VARCHAR(32) NOT NULL, + game_id VARCHAR(96) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + provider_game_id VARCHAR(128) NOT NULL, + game_name VARCHAR(128) NOT NULL, + category VARCHAR(64) NOT NULL DEFAULT '', + icon_url VARCHAR(512) NOT NULL DEFAULT '', + cover_url VARCHAR(512) NOT NULL DEFAULT '', + launch_mode VARCHAR(32) NOT NULL DEFAULT 'h5_popup', + orientation VARCHAR(32) NOT NULL DEFAULT 'portrait', + min_coin BIGINT NOT NULL DEFAULT 0, + status VARCHAR(32) NOT NULL, + sort_order INT NOT NULL DEFAULT 0, + tags JSON NULL, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, game_id), + UNIQUE KEY uk_game_provider(app_code, platform_code, provider_game_id), + KEY idx_game_platform_status(app_code, platform_code, status, sort_order) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS game_display_rules ( + app_code VARCHAR(32) NOT NULL, + rule_id VARCHAR(96) NOT NULL, + game_id VARCHAR(96) NOT NULL, + scene VARCHAR(64) NOT NULL, + region_id BIGINT NOT NULL DEFAULT 0, + language VARCHAR(32) NOT NULL DEFAULT '', + platform VARCHAR(16) NOT NULL DEFAULT '', + visible TINYINT(1) NOT NULL DEFAULT 1, + enabled TINYINT(1) NOT NULL DEFAULT 1, + sort_order INT NOT NULL DEFAULT 0, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, rule_id), + KEY idx_game_display(app_code, scene, region_id, visible, sort_order) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS game_launch_sessions ( + app_code VARCHAR(32) NOT NULL, + session_id VARCHAR(96) NOT NULL, + user_id BIGINT NOT NULL, + display_user_id VARCHAR(32) NOT NULL, + room_id VARCHAR(96) NOT NULL DEFAULT '', + scene VARCHAR(64) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + game_id VARCHAR(96) NOT NULL, + provider_game_id VARCHAR(128) NOT NULL, + launch_token_hash VARCHAR(128) NOT NULL, + status VARCHAR(32) NOT NULL, + expires_at_ms BIGINT NOT NULL, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, session_id), + KEY idx_game_launch_user(app_code, user_id, created_at_ms), + KEY idx_game_launch_token(app_code, launch_token_hash) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS game_orders ( + app_code VARCHAR(32) NOT NULL, + order_id VARCHAR(96) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + provider_order_id VARCHAR(160) NOT NULL, + provider_round_id VARCHAR(160) NOT NULL DEFAULT '', + game_id VARCHAR(96) NOT NULL, + provider_game_id VARCHAR(128) NOT NULL, + user_id BIGINT NOT NULL, + room_id VARCHAR(96) NOT NULL DEFAULT '', + op_type VARCHAR(32) NOT NULL, + coin_amount BIGINT NOT NULL, + status VARCHAR(32) NOT NULL, + wallet_transaction_id VARCHAR(96) NOT NULL DEFAULT '', + wallet_balance_after BIGINT NOT NULL DEFAULT 0, + request_hash VARCHAR(128) NOT NULL, + raw_payload_ref VARCHAR(256) NOT NULL DEFAULT '', + failure_code VARCHAR(64) NOT NULL DEFAULT '', + failure_message VARCHAR(256) NOT NULL DEFAULT '', + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, order_id), + UNIQUE KEY uk_game_provider_order(app_code, platform_code, provider_order_id), + KEY idx_game_order_user_time(app_code, user_id, created_at_ms), + KEY idx_game_order_round(app_code, platform_code, provider_round_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS game_callback_logs ( + app_code VARCHAR(32) NOT NULL, + callback_id VARCHAR(96) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + operation VARCHAR(64) NOT NULL, + request_id VARCHAR(96) NOT NULL DEFAULT '', + provider_request_id VARCHAR(160) NOT NULL DEFAULT '', + signature_valid TINYINT(1) NOT NULL DEFAULT 0, + request_hash VARCHAR(128) NOT NULL, + response_code VARCHAR(64) NOT NULL DEFAULT '', + response_body_hash VARCHAR(128) NOT NULL DEFAULT '', + status VARCHAR(32) NOT NULL, + created_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, callback_id), + KEY idx_game_callback_platform_time(app_code, platform_code, created_at_ms) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS game_repair_orders ( + app_code VARCHAR(32) NOT NULL, + repair_id VARCHAR(96) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + provider_order_id VARCHAR(160) NOT NULL, + reason VARCHAR(128) NOT NULL, + status VARCHAR(32) NOT NULL, + operator_admin_id BIGINT NOT NULL DEFAULT 0, + attempts INT NOT NULL DEFAULT 0, + next_run_at_ms BIGINT NOT NULL DEFAULT 0, + last_error_code VARCHAR(64) NOT NULL DEFAULT '', + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, repair_id), + UNIQUE KEY uk_game_repair_provider_order(app_code, platform_code, provider_order_id) + ) 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 r.seedDefaults(ctx) +} + +func (r *Repository) seedDefaults(ctx context.Context) error { + if _, err := r.db.ExecContext(ctx, + `INSERT INTO game_platforms (app_code, platform_code, platform_name, status, api_base_url, sort_order, created_at_ms, updated_at_ms) + VALUES ('lalu', 'demo', 'Demo Games', 'active', 'https://game.example.local/h5', 10, 0, 0) + ON DUPLICATE KEY UPDATE platform_name = VALUES(platform_name)`); err != nil { + return err + } + if _, err := r.db.ExecContext(ctx, + `INSERT INTO game_catalog ( + app_code, game_id, platform_code, provider_game_id, game_name, category, icon_url, cover_url, + launch_mode, orientation, min_coin, status, sort_order, tags, created_at_ms, updated_at_ms + ) VALUES ( + 'lalu', 'demo_rocket_001', 'demo', 'rocket_001', 'Rocket', 'casual', + 'https://cdn.example.com/game/rocket.png', 'https://cdn.example.com/game/rocket-cover.png', + 'h5_popup', 'portrait', 100, 'active', 10, JSON_ARRAY('demo','voice_room'), 0, 0 + ) ON DUPLICATE KEY UPDATE game_name = VALUES(game_name)`); err != nil { + return err + } + _, err := r.db.ExecContext(ctx, + `INSERT INTO game_display_rules ( + app_code, rule_id, game_id, scene, region_id, language, platform, visible, enabled, sort_order, created_at_ms, updated_at_ms + ) VALUES ( + 'lalu', 'demo_rocket_voice_default', 'demo_rocket_001', 'voice_room', 0, '', '', 1, 1, 10, 0, 0 + ) ON DUPLICATE KEY UPDATE visible = VALUES(visible), enabled = VALUES(enabled)`) + return err +} + +func (r *Repository) ListGames(ctx context.Context, query gameservice.ListGamesQuery) ([]gamedomain.AppGame, error) { + rows, err := r.db.QueryContext(ctx, + `SELECT c.game_id, c.platform_code, c.game_name, c.icon_url, c.cover_url, c.category, + c.launch_mode, c.orientation, c.min_coin, c.status, p.status, + MIN(CASE WHEN r.sort_order = 0 THEN c.sort_order ELSE r.sort_order END) AS display_sort + FROM game_catalog c + JOIN game_platforms p ON p.app_code = c.app_code AND p.platform_code = c.platform_code + JOIN game_display_rules r ON r.app_code = c.app_code AND r.game_id = c.game_id + WHERE c.app_code = ? + AND c.status IN ('active', 'maintenance') + AND p.status IN ('active', 'maintenance') + AND r.scene = ? + AND r.enabled = 1 + AND r.visible = 1 + AND (r.region_id = 0 OR r.region_id = ?) + AND (r.language = '' OR r.language = ?) + AND (r.platform = '' OR r.platform = ?) + GROUP BY c.game_id, c.platform_code, c.game_name, c.icon_url, c.cover_url, c.category, + c.launch_mode, c.orientation, c.min_coin, c.status, p.status, c.sort_order, p.sort_order + ORDER BY display_sort ASC, p.sort_order ASC, c.sort_order ASC, c.game_id ASC`, + appcode.Normalize(query.AppCode), query.Scene, query.RegionID, query.Language, query.ClientPlatform, + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := []gamedomain.AppGame{} + for rows.Next() { + var item gamedomain.AppGame + var gameStatus, platformStatus string + if err := rows.Scan(&item.GameID, &item.PlatformCode, &item.Name, &item.IconURL, &item.CoverURL, &item.Category, &item.LaunchMode, &item.Orientation, &item.MinCoin, &gameStatus, &platformStatus, &item.SortOrder); err != nil { + return nil, err + } + item.NameKey = "game." + item.GameID + ".name" + item.Enabled = gameStatus == gamedomain.StatusActive && platformStatus == gamedomain.StatusActive + item.Maintenance = gameStatus == gamedomain.StatusMaintenance || platformStatus == gamedomain.StatusMaintenance + items = append(items, item) + } + return items, rows.Err() +} + +func (r *Repository) GetLaunchableGame(ctx context.Context, appCode string, gameID string) (gamedomain.LaunchableGame, error) { + row := r.db.QueryRowContext(ctx, + `SELECT c.app_code, c.game_id, c.platform_code, c.provider_game_id, c.game_name, c.category, + c.icon_url, c.cover_url, c.launch_mode, c.orientation, c.min_coin, c.status, + c.sort_order, COALESCE(CAST(c.tags AS CHAR), '[]'), c.created_at_ms, c.updated_at_ms, + p.platform_name, p.status, p.api_base_url + FROM game_catalog c + JOIN game_platforms p ON p.app_code = c.app_code AND p.platform_code = c.platform_code + WHERE c.app_code = ? AND c.game_id = ?`, + appcode.Normalize(appCode), strings.TrimSpace(gameID), + ) + var item gamedomain.LaunchableGame + var tagsJSON string + if err := row.Scan(&item.AppCode, &item.GameID, &item.PlatformCode, &item.ProviderGameID, &item.GameName, &item.Category, &item.IconURL, &item.CoverURL, &item.LaunchMode, &item.Orientation, &item.MinCoin, &item.Status, &item.SortOrder, &tagsJSON, &item.CreatedAtMS, &item.UpdatedAtMS, &item.PlatformName, &item.PlatformStatus, &item.APIBaseURL); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return gamedomain.LaunchableGame{}, xerr.New(xerr.NotFound, "game not found") + } + return gamedomain.LaunchableGame{}, err + } + _ = json.Unmarshal([]byte(tagsJSON), &item.Tags) + return item, nil +} + +func (r *Repository) CreateLaunchSession(ctx context.Context, session gamedomain.LaunchSession) error { + _, err := r.db.ExecContext(ctx, + `INSERT INTO game_launch_sessions ( + app_code, session_id, user_id, display_user_id, room_id, scene, platform_code, game_id, + provider_game_id, launch_token_hash, status, expires_at_ms, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + appcode.Normalize(session.AppCode), session.SessionID, session.UserID, session.DisplayUserID, session.RoomID, session.Scene, + session.PlatformCode, session.GameID, session.ProviderGameID, session.LaunchTokenHash, session.Status, + session.ExpiresAtMS, session.CreatedAtMS, session.UpdatedAtMS, + ) + return err +} + +func (r *Repository) InsertCallbackLog(ctx context.Context, log gamedomain.CallbackLog) error { + _, err := r.db.ExecContext(ctx, + `INSERT INTO game_callback_logs ( + app_code, callback_id, platform_code, operation, request_id, provider_request_id, + signature_valid, request_hash, response_code, response_body_hash, status, created_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + appcode.Normalize(log.AppCode), log.CallbackID, log.PlatformCode, log.Operation, log.RequestID, log.ProviderRequestID, + log.SignatureValid, log.RequestHash, log.ResponseCode, log.ResponseBodyHash, log.Status, log.CreatedAtMS, + ) + return err +} + +func (r *Repository) CreateGameOrder(ctx context.Context, order gamedomain.GameOrder) (gamedomain.GameOrder, bool, error) { + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return gamedomain.GameOrder{}, false, err + } + defer func() { _ = tx.Rollback() }() + + existing, exists, err := queryOrderForUpdate(ctx, tx, order.AppCode, order.PlatformCode, order.ProviderOrderID) + if err != nil { + return gamedomain.GameOrder{}, false, err + } + if exists { + if existing.RequestHash != order.RequestHash { + _, _ = tx.ExecContext(ctx, + `UPDATE game_orders SET status = ?, failure_code = ?, failure_message = ?, updated_at_ms = ? + WHERE app_code = ? AND order_id = ?`, + gamedomain.OrderStatusConflict, string(xerr.IdempotencyConflict), "provider order payload conflict", + time.Now().UnixMilli(), appcode.Normalize(order.AppCode), existing.OrderID, + ) + _ = tx.Commit() + return gamedomain.GameOrder{}, true, xerr.New(xerr.IdempotencyConflict, "provider order payload conflict") + } + if err := tx.Commit(); err != nil { + return gamedomain.GameOrder{}, false, err + } + return existing, true, nil + } + nowMs := time.Now().UnixMilli() + order.AppCode = appcode.Normalize(order.AppCode) + order.CreatedAtMS = nowMs + order.UpdatedAtMS = nowMs + if _, err := tx.ExecContext(ctx, + `INSERT INTO game_orders ( + app_code, order_id, platform_code, provider_order_id, provider_round_id, game_id, provider_game_id, + user_id, room_id, op_type, coin_amount, status, request_hash, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + order.AppCode, order.OrderID, order.PlatformCode, order.ProviderOrderID, order.ProviderRoundID, order.GameID, order.ProviderGameID, + order.UserID, order.RoomID, order.OpType, order.CoinAmount, order.Status, order.RequestHash, order.CreatedAtMS, order.UpdatedAtMS, + ); err != nil { + return gamedomain.GameOrder{}, false, err + } + if err := tx.Commit(); err != nil { + return gamedomain.GameOrder{}, false, err + } + return order, false, nil +} + +func queryOrderForUpdate(ctx context.Context, tx *sql.Tx, appCode string, platformCode string, providerOrderID string) (gamedomain.GameOrder, bool, error) { + row := tx.QueryRowContext(ctx, + `SELECT app_code, order_id, platform_code, provider_order_id, provider_round_id, game_id, provider_game_id, + user_id, room_id, op_type, coin_amount, status, wallet_transaction_id, wallet_balance_after, + request_hash, failure_code, failure_message, created_at_ms, updated_at_ms + FROM game_orders + WHERE app_code = ? AND platform_code = ? AND provider_order_id = ? + FOR UPDATE`, + appcode.Normalize(appCode), strings.TrimSpace(platformCode), strings.TrimSpace(providerOrderID), + ) + var order gamedomain.GameOrder + if err := row.Scan(&order.AppCode, &order.OrderID, &order.PlatformCode, &order.ProviderOrderID, &order.ProviderRoundID, &order.GameID, &order.ProviderGameID, &order.UserID, &order.RoomID, &order.OpType, &order.CoinAmount, &order.Status, &order.WalletTransactionID, &order.WalletBalanceAfter, &order.RequestHash, &order.FailureCode, &order.FailureMessage, &order.CreatedAtMS, &order.UpdatedAtMS); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return gamedomain.GameOrder{}, false, nil + } + return gamedomain.GameOrder{}, false, err + } + return order, true, nil +} + +func (r *Repository) MarkOrderSucceeded(ctx context.Context, appCode string, orderID string, walletTransactionID string, balanceAfter int64, nowMs int64) error { + _, err := r.db.ExecContext(ctx, + `UPDATE game_orders + SET status = ?, wallet_transaction_id = ?, wallet_balance_after = ?, failure_code = '', failure_message = '', updated_at_ms = ? + WHERE app_code = ? AND order_id = ?`, + gamedomain.OrderStatusSucceeded, walletTransactionID, balanceAfter, nowMs, appcode.Normalize(appCode), orderID, + ) + return err +} + +func (r *Repository) MarkOrderFailed(ctx context.Context, appCode string, orderID string, status string, code string, message string, nowMs int64) error { + _, err := r.db.ExecContext(ctx, + `UPDATE game_orders SET status = ?, failure_code = ?, failure_message = ?, updated_at_ms = ? + WHERE app_code = ? AND order_id = ?`, + status, code, truncate(message, 256), nowMs, appcode.Normalize(appCode), orderID, + ) + return err +} + +func (r *Repository) ListPlatforms(ctx context.Context, appCode string, status string) ([]gamedomain.Platform, error) { + query := `SELECT app_code, platform_code, platform_name, status, api_base_url, sort_order, created_at_ms, updated_at_ms + FROM game_platforms WHERE app_code = ?` + args := []any{appcode.Normalize(appCode)} + if strings.TrimSpace(status) != "" { + query += " AND status = ?" + args = append(args, strings.TrimSpace(status)) + } + query += " ORDER BY sort_order ASC, platform_code ASC" + rows, err := r.db.QueryContext(ctx, query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + var items []gamedomain.Platform + for rows.Next() { + var item gamedomain.Platform + if err := rows.Scan(&item.AppCode, &item.PlatformCode, &item.PlatformName, &item.Status, &item.APIBaseURL, &item.SortOrder, &item.CreatedAtMS, &item.UpdatedAtMS); err != nil { + return nil, err + } + items = append(items, item) + } + return items, rows.Err() +} + +func (r *Repository) UpsertPlatform(ctx context.Context, platform gamedomain.Platform) (gamedomain.Platform, error) { + nowMs := time.Now().UnixMilli() + platform.AppCode = appcode.Normalize(platform.AppCode) + if platform.CreatedAtMS == 0 { + platform.CreatedAtMS = nowMs + } + platform.UpdatedAtMS = nowMs + _, err := r.db.ExecContext(ctx, + `INSERT INTO game_platforms (app_code, platform_code, platform_name, status, api_base_url, sort_order, created_at_ms, updated_at_ms) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE platform_name = VALUES(platform_name), status = VALUES(status), + api_base_url = VALUES(api_base_url), sort_order = VALUES(sort_order), updated_at_ms = VALUES(updated_at_ms)`, + platform.AppCode, platform.PlatformCode, platform.PlatformName, platform.Status, platform.APIBaseURL, platform.SortOrder, platform.CreatedAtMS, platform.UpdatedAtMS, + ) + return platform, err +} + +func (r *Repository) ListCatalog(ctx context.Context, query gameservice.ListCatalogQuery) ([]gamedomain.CatalogItem, string, error) { + sqlText := `SELECT app_code, game_id, platform_code, provider_game_id, game_name, category, icon_url, cover_url, + launch_mode, orientation, min_coin, status, sort_order, COALESCE(CAST(tags AS CHAR), '[]'), created_at_ms, updated_at_ms + FROM game_catalog WHERE app_code = ?` + args := []any{appcode.Normalize(query.AppCode)} + if strings.TrimSpace(query.PlatformCode) != "" { + sqlText += " AND platform_code = ?" + args = append(args, strings.TrimSpace(query.PlatformCode)) + } + if strings.TrimSpace(query.Status) != "" { + sqlText += " AND status = ?" + args = append(args, strings.TrimSpace(query.Status)) + } + sqlText += " ORDER BY sort_order ASC, game_id ASC" + if query.PageSize > 0 { + sqlText += " LIMIT ?" + args = append(args, query.PageSize) + } + rows, err := r.db.QueryContext(ctx, sqlText, args...) + if err != nil { + return nil, "", err + } + defer rows.Close() + var items []gamedomain.CatalogItem + for rows.Next() { + item, err := scanCatalog(rows) + if err != nil { + return nil, "", err + } + items = append(items, item) + } + return items, "", rows.Err() +} + +func (r *Repository) UpsertCatalog(ctx context.Context, item gamedomain.CatalogItem) (gamedomain.CatalogItem, error) { + nowMs := time.Now().UnixMilli() + item.AppCode = appcode.Normalize(item.AppCode) + if item.CreatedAtMS == 0 { + item.CreatedAtMS = nowMs + } + item.UpdatedAtMS = nowMs + tagsJSON, _ := json.Marshal(item.Tags) + _, err := r.db.ExecContext(ctx, + `INSERT INTO game_catalog ( + app_code, game_id, platform_code, provider_game_id, game_name, category, icon_url, cover_url, + launch_mode, orientation, min_coin, status, sort_order, tags, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE platform_code = VALUES(platform_code), provider_game_id = VALUES(provider_game_id), + game_name = VALUES(game_name), category = VALUES(category), icon_url = VALUES(icon_url), cover_url = VALUES(cover_url), + launch_mode = VALUES(launch_mode), orientation = VALUES(orientation), min_coin = VALUES(min_coin), + status = VALUES(status), sort_order = VALUES(sort_order), tags = VALUES(tags), updated_at_ms = VALUES(updated_at_ms)`, + item.AppCode, item.GameID, item.PlatformCode, item.ProviderGameID, item.GameName, item.Category, item.IconURL, item.CoverURL, + item.LaunchMode, item.Orientation, item.MinCoin, item.Status, item.SortOrder, string(tagsJSON), item.CreatedAtMS, item.UpdatedAtMS, + ) + return item, err +} + +func (r *Repository) SetGameStatus(ctx context.Context, appCode string, gameID string, status string, nowMs int64) (gamedomain.CatalogItem, error) { + if _, err := r.db.ExecContext(ctx, + `UPDATE game_catalog SET status = ?, updated_at_ms = ? WHERE app_code = ? AND game_id = ?`, + status, nowMs, appcode.Normalize(appCode), strings.TrimSpace(gameID), + ); err != nil { + return gamedomain.CatalogItem{}, err + } + return r.getCatalog(ctx, appCode, gameID) +} + +func (r *Repository) getCatalog(ctx context.Context, appCode string, gameID string) (gamedomain.CatalogItem, error) { + row := r.db.QueryRowContext(ctx, + `SELECT app_code, game_id, platform_code, provider_game_id, game_name, category, icon_url, cover_url, + launch_mode, orientation, min_coin, status, sort_order, COALESCE(CAST(tags AS CHAR), '[]'), created_at_ms, updated_at_ms + FROM game_catalog WHERE app_code = ? AND game_id = ?`, + appcode.Normalize(appCode), strings.TrimSpace(gameID), + ) + item, err := scanCatalog(row) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return gamedomain.CatalogItem{}, xerr.New(xerr.NotFound, "game not found") + } + return gamedomain.CatalogItem{}, err + } + return item, nil +} + +type catalogScanner interface { + Scan(dest ...any) error +} + +func scanCatalog(scanner catalogScanner) (gamedomain.CatalogItem, error) { + var item gamedomain.CatalogItem + var tagsJSON string + if err := scanner.Scan(&item.AppCode, &item.GameID, &item.PlatformCode, &item.ProviderGameID, &item.GameName, &item.Category, &item.IconURL, &item.CoverURL, &item.LaunchMode, &item.Orientation, &item.MinCoin, &item.Status, &item.SortOrder, &tagsJSON, &item.CreatedAtMS, &item.UpdatedAtMS); err != nil { + return gamedomain.CatalogItem{}, err + } + _ = json.Unmarshal([]byte(tagsJSON), &item.Tags) + return item, nil +} + +func truncate(value string, max int) string { + value = strings.TrimSpace(value) + if len(value) <= max { + return value + } + return value[:max] +} diff --git a/services/game-service/internal/transport/grpc/server.go b/services/game-service/internal/transport/grpc/server.go new file mode 100644 index 00000000..76492e6b --- /dev/null +++ b/services/game-service/internal/transport/grpc/server.go @@ -0,0 +1,333 @@ +package grpc + +import ( + "context" + "strings" + "time" + + gamev1 "hyapp.local/api/proto/game/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + gamedomain "hyapp/services/game-service/internal/domain/game" + gameservice "hyapp/services/game-service/internal/service/game" +) + +// Server 把 game-service 用例层适配为 gRPC 契约。 +type Server struct { + gamev1.UnimplementedGameAppServiceServer + gamev1.UnimplementedGameCallbackServiceServer + gamev1.UnimplementedGameAdminServiceServer + + svc *gameservice.Service +} + +func NewServer(svc *gameservice.Service) *Server { + return &Server{svc: svc} +} + +func (s *Server) ListGames(ctx context.Context, req *gamev1.ListGamesRequest) (*gamev1.ListGamesResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + items, serverTimeMS, err := s.svc.ListGames(ctx, gameservice.ListGamesQuery{ + AppCode: req.GetMeta().GetAppCode(), + UserID: req.GetUserId(), + Scene: req.GetScene(), + RoomID: req.GetRoomId(), + RegionID: req.GetRegionId(), + Language: req.GetLanguage(), + ClientPlatform: req.GetClientPlatform(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &gamev1.ListGamesResponse{ServerTimeMs: serverTimeMS, Games: make([]*gamev1.AppGame, 0, len(items))} + for _, item := range items { + resp.Games = append(resp.Games, appGameToProto(item)) + } + return resp, nil +} + +func (s *Server) LaunchGame(ctx context.Context, req *gamev1.LaunchGameRequest) (*gamev1.LaunchGameResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + result, err := s.svc.LaunchGame(ctx, gameservice.LaunchCommand{ + AppCode: req.GetMeta().GetAppCode(), + RequestID: req.GetMeta().GetRequestId(), + UserID: req.GetUserId(), + DisplayUserID: req.GetDisplayUserId(), + GameID: req.GetGameId(), + Scene: req.GetScene(), + RoomID: req.GetRoomId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.LaunchGameResponse{ + SessionId: result.SessionID, + LaunchUrl: result.LaunchURL, + ExpiresAtMs: result.ExpiresAtMS, + Orientation: result.Orientation, + ServerTimeMs: result.ServerTimeMS, + }, nil +} + +func (s *Server) HandleCallback(ctx context.Context, req *gamev1.CallbackRequest) (*gamev1.CallbackResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + raw, contentType, err := s.svc.HandleCallback(ctx, req) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.CallbackResponse{RawBody: raw, ContentType: contentType}, nil +} + +func (s *Server) ListPlatforms(ctx context.Context, req *gamev1.ListPlatformsRequest) (*gamev1.ListPlatformsResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + items, err := s.svcRepository().ListPlatforms(ctx, req.GetMeta().GetAppCode(), req.GetStatus()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &gamev1.ListPlatformsResponse{ServerTimeMs: time.Now().UnixMilli(), Platforms: make([]*gamev1.GamePlatform, 0, len(items))} + for _, item := range items { + resp.Platforms = append(resp.Platforms, platformToProto(item)) + } + return resp, nil +} + +func (s *Server) UpsertPlatform(ctx context.Context, req *gamev1.UpsertPlatformRequest) (*gamev1.PlatformResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + platform := platformFromProto(req.GetPlatform()) + platform.AppCode = req.GetMeta().GetAppCode() + normalized, err := normalizePlatform(platform) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + saved, err := s.svcRepository().UpsertPlatform(ctx, normalized) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.PlatformResponse{Platform: platformToProto(saved), ServerTimeMs: time.Now().UnixMilli()}, nil +} + +func (s *Server) ListCatalog(ctx context.Context, req *gamev1.ListCatalogRequest) (*gamev1.ListCatalogResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + items, next, err := s.svcRepository().ListCatalog(ctx, gameservice.ListCatalogQuery{ + AppCode: req.GetMeta().GetAppCode(), + PlatformCode: req.GetPlatformCode(), + Status: req.GetStatus(), + PageSize: req.GetPageSize(), + Cursor: req.GetCursor(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &gamev1.ListCatalogResponse{NextCursor: next, ServerTimeMs: time.Now().UnixMilli(), Games: make([]*gamev1.GameCatalogItem, 0, len(items))} + for _, item := range items { + resp.Games = append(resp.Games, catalogToProto(item)) + } + return resp, nil +} + +func (s *Server) UpsertCatalog(ctx context.Context, req *gamev1.UpsertCatalogRequest) (*gamev1.CatalogResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + item := catalogFromProto(req.GetGame()) + item.AppCode = req.GetMeta().GetAppCode() + normalized, err := normalizeCatalog(item) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + saved, err := s.svcRepository().UpsertCatalog(ctx, normalized) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.CatalogResponse{Game: catalogToProto(saved), ServerTimeMs: time.Now().UnixMilli()}, nil +} + +func (s *Server) SetGameStatus(ctx context.Context, req *gamev1.SetGameStatusRequest) (*gamev1.CatalogResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + status := normalizeStatus(req.GetStatus()) + if status == "" { + return nil, xerr.ToGRPCError(xerr.New(xerr.InvalidArgument, "status is invalid")) + } + item, err := s.svcRepository().SetGameStatus(ctx, req.GetMeta().GetAppCode(), req.GetGameId(), status, time.Now().UnixMilli()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.CatalogResponse{Game: catalogToProto(item), ServerTimeMs: time.Now().UnixMilli()}, nil +} + +func (s *Server) svcRepository() gameservice.Repository { + if s == nil || s.svc == nil || s.svc.Repository() == nil { + return unavailableRepository{} + } + return s.svc.Repository() +} + +type unavailableRepository struct{} + +func (unavailableRepository) Ping(context.Context) error { + return xerr.New(xerr.Unavailable, "game repository is not configured") +} +func (unavailableRepository) ListGames(context.Context, gameservice.ListGamesQuery) ([]gamedomain.AppGame, error) { + return nil, xerr.New(xerr.Unavailable, "game repository is not configured") +} +func (unavailableRepository) GetLaunchableGame(context.Context, string, string) (gamedomain.LaunchableGame, error) { + return gamedomain.LaunchableGame{}, xerr.New(xerr.Unavailable, "game repository is not configured") +} +func (unavailableRepository) CreateLaunchSession(context.Context, gamedomain.LaunchSession) error { + return xerr.New(xerr.Unavailable, "game repository is not configured") +} +func (unavailableRepository) InsertCallbackLog(context.Context, gamedomain.CallbackLog) error { + return xerr.New(xerr.Unavailable, "game repository is not configured") +} +func (unavailableRepository) CreateGameOrder(context.Context, gamedomain.GameOrder) (gamedomain.GameOrder, bool, error) { + return gamedomain.GameOrder{}, false, xerr.New(xerr.Unavailable, "game repository is not configured") +} +func (unavailableRepository) MarkOrderSucceeded(context.Context, string, string, string, int64, int64) error { + return xerr.New(xerr.Unavailable, "game repository is not configured") +} +func (unavailableRepository) MarkOrderFailed(context.Context, string, string, string, string, string, int64) error { + return xerr.New(xerr.Unavailable, "game repository is not configured") +} +func (unavailableRepository) ListPlatforms(context.Context, string, string) ([]gamedomain.Platform, error) { + return nil, xerr.New(xerr.Unavailable, "game repository is not configured") +} +func (unavailableRepository) UpsertPlatform(context.Context, gamedomain.Platform) (gamedomain.Platform, error) { + return gamedomain.Platform{}, xerr.New(xerr.Unavailable, "game repository is not configured") +} +func (unavailableRepository) ListCatalog(context.Context, gameservice.ListCatalogQuery) ([]gamedomain.CatalogItem, string, error) { + return nil, "", xerr.New(xerr.Unavailable, "game repository is not configured") +} +func (unavailableRepository) UpsertCatalog(context.Context, gamedomain.CatalogItem) (gamedomain.CatalogItem, error) { + return gamedomain.CatalogItem{}, xerr.New(xerr.Unavailable, "game repository is not configured") +} +func (unavailableRepository) SetGameStatus(context.Context, string, string, string, int64) (gamedomain.CatalogItem, error) { + return gamedomain.CatalogItem{}, xerr.New(xerr.Unavailable, "game repository is not configured") +} + +func appGameToProto(item gamedomain.AppGame) *gamev1.AppGame { + return &gamev1.AppGame{ + GameId: item.GameID, + PlatformCode: item.PlatformCode, + NameKey: item.NameKey, + Name: item.Name, + IconUrl: item.IconURL, + CoverUrl: item.CoverURL, + Category: item.Category, + LaunchMode: item.LaunchMode, + Orientation: item.Orientation, + MinCoin: item.MinCoin, + Enabled: item.Enabled, + Maintenance: item.Maintenance, + SortOrder: item.SortOrder, + } +} + +func platformToProto(item gamedomain.Platform) *gamev1.GamePlatform { + return &gamev1.GamePlatform{ + AppCode: item.AppCode, + PlatformCode: item.PlatformCode, + PlatformName: item.PlatformName, + Status: item.Status, + ApiBaseUrl: item.APIBaseURL, + SortOrder: item.SortOrder, + CreatedAtMs: item.CreatedAtMS, + UpdatedAtMs: item.UpdatedAtMS, + } +} + +func platformFromProto(item *gamev1.GamePlatform) gamedomain.Platform { + if item == nil { + return gamedomain.Platform{} + } + return gamedomain.Platform{ + AppCode: item.GetAppCode(), + PlatformCode: item.GetPlatformCode(), + PlatformName: item.GetPlatformName(), + Status: item.GetStatus(), + APIBaseURL: item.GetApiBaseUrl(), + SortOrder: item.GetSortOrder(), + } +} + +func catalogToProto(item gamedomain.CatalogItem) *gamev1.GameCatalogItem { + return &gamev1.GameCatalogItem{ + AppCode: item.AppCode, + GameId: item.GameID, + PlatformCode: item.PlatformCode, + ProviderGameId: item.ProviderGameID, + GameName: item.GameName, + Category: item.Category, + IconUrl: item.IconURL, + CoverUrl: item.CoverURL, + LaunchMode: item.LaunchMode, + Orientation: item.Orientation, + MinCoin: item.MinCoin, + Status: item.Status, + SortOrder: item.SortOrder, + Tags: item.Tags, + CreatedAtMs: item.CreatedAtMS, + UpdatedAtMs: item.UpdatedAtMS, + } +} + +func catalogFromProto(item *gamev1.GameCatalogItem) gamedomain.CatalogItem { + if item == nil { + return gamedomain.CatalogItem{} + } + return gamedomain.CatalogItem{ + AppCode: item.GetAppCode(), + GameID: item.GetGameId(), + PlatformCode: item.GetPlatformCode(), + ProviderGameID: item.GetProviderGameId(), + GameName: item.GetGameName(), + Category: item.GetCategory(), + IconURL: item.GetIconUrl(), + CoverURL: item.GetCoverUrl(), + LaunchMode: item.GetLaunchMode(), + Orientation: item.GetOrientation(), + MinCoin: item.GetMinCoin(), + Status: item.GetStatus(), + SortOrder: item.GetSortOrder(), + Tags: item.GetTags(), + } +} + +func normalizePlatform(item gamedomain.Platform) (gamedomain.Platform, error) { + item.PlatformCode = strings.TrimSpace(item.PlatformCode) + item.PlatformName = strings.TrimSpace(item.PlatformName) + item.Status = normalizeStatus(item.Status) + item.APIBaseURL = strings.TrimSpace(item.APIBaseURL) + if item.PlatformCode == "" || item.PlatformName == "" || item.Status == "" { + return gamedomain.Platform{}, xerr.New(xerr.InvalidArgument, "platform is incomplete") + } + return item, nil +} + +func normalizeCatalog(item gamedomain.CatalogItem) (gamedomain.CatalogItem, error) { + item.GameID = strings.TrimSpace(item.GameID) + item.PlatformCode = strings.TrimSpace(item.PlatformCode) + item.ProviderGameID = strings.TrimSpace(item.ProviderGameID) + item.GameName = strings.TrimSpace(item.GameName) + item.Status = normalizeStatus(item.Status) + if item.LaunchMode == "" { + item.LaunchMode = gamedomain.LaunchModeH5Popup + } + if item.Orientation == "" { + item.Orientation = "portrait" + } + if item.GameID == "" || item.PlatformCode == "" || item.ProviderGameID == "" || item.GameName == "" || item.Status == "" { + return gamedomain.CatalogItem{}, xerr.New(xerr.InvalidArgument, "game is incomplete") + } + return item, nil +} + +func normalizeStatus(status string) string { + switch strings.ToLower(strings.TrimSpace(status)) { + case gamedomain.StatusActive: + return gamedomain.StatusActive + case gamedomain.StatusMaintenance: + return gamedomain.StatusMaintenance + case gamedomain.StatusDisabled: + return gamedomain.StatusDisabled + default: + return "" + } +} diff --git a/services/gateway-service/configs/config.docker.yaml b/services/gateway-service/configs/config.docker.yaml index 5a41f43f..2282e3de 100644 --- a/services/gateway-service/configs/config.docker.yaml +++ b/services/gateway-service/configs/config.docker.yaml @@ -13,6 +13,7 @@ room_service_addr: "room-service:13001" user_service_addr: "user-service:13005" wallet_service_addr: "wallet-service:13004" activity_service_addr: "activity-service:13006" +game_service_addr: "game-service:13008" app_config: # H5 地址由后台 APP配置/H5配置 写入 hyapp_admin.admin_app_configs,gateway 只读下发给 App。 mysql_dsn: "hyapp:hyapp@tcp(mysql:3306)/hyapp_admin?parseTime=true&charset=utf8mb4&loc=UTC" diff --git a/services/gateway-service/configs/config.yaml b/services/gateway-service/configs/config.yaml index 6bb77869..3cea4fb2 100644 --- a/services/gateway-service/configs/config.yaml +++ b/services/gateway-service/configs/config.yaml @@ -13,6 +13,7 @@ room_service_addr: "127.0.0.1:13001" user_service_addr: "127.0.0.1:13005" wallet_service_addr: "127.0.0.1:13004" activity_service_addr: "127.0.0.1:13006" +game_service_addr: "127.0.0.1:13008" app_config: # H5 地址由后台 APP配置/H5配置 写入 hyapp_admin.admin_app_configs,gateway 只读下发给 App。 mysql_dsn: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_admin?parseTime=true&charset=utf8mb4&loc=UTC" diff --git a/services/gateway-service/internal/app/app.go b/services/gateway-service/internal/app/app.go index 337b2112..94fc0023 100644 --- a/services/gateway-service/internal/app/app.go +++ b/services/gateway-service/internal/app/app.go @@ -30,6 +30,7 @@ type App struct { userConn *grpc.ClientConn walletConn *grpc.ClientConn activityConn *grpc.ClientConn + gameConn *grpc.ClientConn appConfig *appconfig.MySQLReader redisClose func() error health *healthcheck.State @@ -66,6 +67,14 @@ func New(cfg config.Config) (*App, error) { _ = walletConn.Close() return nil, err } + gameConn, err := grpc.Dial(cfg.GameServiceAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + _ = roomConn.Close() + _ = userConn.Close() + _ = walletConn.Close() + _ = activityConn.Close() + return nil, err + } var roomClient client.RoomClient = client.NewGRPCRoomClient(roomConn) var roomGuardClient client.RoomGuardClient = client.NewGRPCRoomGuardClient(roomConn) @@ -82,12 +91,14 @@ func New(cfg config.Config) (*App, error) { var messageClient client.MessageInboxClient = client.NewGRPCMessageInboxClient(activityConn) var taskClient client.TaskClient = client.NewGRPCTaskClient(activityConn) var broadcastClient client.BroadcastClient = client.NewGRPCBroadcastClient(activityConn) + var gameClient client.GameClient = client.NewGRPCGameClient(gameConn) appConfigReader, err := openAppConfigReader(cfg.AppConfig) if err != nil { _ = roomConn.Close() _ = userConn.Close() _ = walletConn.Close() _ = activityConn.Close() + _ = gameConn.Close() return nil, err } handler := httptransport.NewHandlerWithConfig(roomClient, roomGuardClient, userClient, userIdentityClient, httptransport.TencentIMConfig{ @@ -107,6 +118,7 @@ func New(cfg config.Config) (*App, error) { handler.SetMessageInboxClient(messageClient) handler.SetTaskClient(taskClient) handler.SetBroadcastClient(broadcastClient) + handler.SetGameClient(gameClient) if appConfigReader != nil { handler.SetAppConfigReader(appConfigReader) } @@ -135,6 +147,7 @@ func New(cfg config.Config) (*App, error) { _ = userConn.Close() _ = walletConn.Close() _ = activityConn.Close() + _ = gameConn.Close() return nil, err } handler.SetObjectUploader(uploader) @@ -170,6 +183,7 @@ func New(cfg config.Config) (*App, error) { _ = userConn.Close() _ = walletConn.Close() _ = activityConn.Close() + _ = gameConn.Close() return nil, err } loginRiskRedisClose, err := configureLoginRisk(handler, cfg.LoginRisk, loginRiskConfig) @@ -184,6 +198,7 @@ func New(cfg config.Config) (*App, error) { _ = userConn.Close() _ = walletConn.Close() _ = activityConn.Close() + _ = gameConn.Close() return nil, err } redisClose = joinClose(redisClose, loginRiskRedisClose) @@ -208,6 +223,7 @@ func New(cfg config.Config) (*App, error) { _ = userConn.Close() _ = walletConn.Close() _ = activityConn.Close() + _ = gameConn.Close() return nil, err } @@ -222,6 +238,7 @@ func New(cfg config.Config) (*App, error) { userConn: userConn, walletConn: walletConn, activityConn: activityConn, + gameConn: gameConn, appConfig: appConfigReader, redisClose: redisClose, health: healthState, @@ -323,6 +340,9 @@ func (a *App) Close() error { if a.activityConn != nil { err = errors.Join(err, a.activityConn.Close()) } + if a.gameConn != nil { + err = errors.Join(err, a.gameConn.Close()) + } if a.appConfig != nil { err = errors.Join(err, a.appConfig.Close()) } diff --git a/services/gateway-service/internal/client/game_client.go b/services/gateway-service/internal/client/game_client.go new file mode 100644 index 00000000..ce2d00c0 --- /dev/null +++ b/services/gateway-service/internal/client/game_client.go @@ -0,0 +1,39 @@ +package client + +import ( + "context" + + "google.golang.org/grpc" + gamev1 "hyapp.local/api/proto/game/v1" +) + +// GameClient 抽象 gateway 对 game-service App 与回调 RPC 的依赖。 +type GameClient interface { + ListGames(ctx context.Context, req *gamev1.ListGamesRequest) (*gamev1.ListGamesResponse, error) + LaunchGame(ctx context.Context, req *gamev1.LaunchGameRequest) (*gamev1.LaunchGameResponse, error) + HandleCallback(ctx context.Context, req *gamev1.CallbackRequest) (*gamev1.CallbackResponse, error) +} + +type grpcGameClient struct { + app gamev1.GameAppServiceClient + callback gamev1.GameCallbackServiceClient +} + +func NewGRPCGameClient(conn *grpc.ClientConn) GameClient { + return &grpcGameClient{ + app: gamev1.NewGameAppServiceClient(conn), + callback: gamev1.NewGameCallbackServiceClient(conn), + } +} + +func (c *grpcGameClient) ListGames(ctx context.Context, req *gamev1.ListGamesRequest) (*gamev1.ListGamesResponse, error) { + return c.app.ListGames(ctx, req) +} + +func (c *grpcGameClient) LaunchGame(ctx context.Context, req *gamev1.LaunchGameRequest) (*gamev1.LaunchGameResponse, error) { + return c.app.LaunchGame(ctx, req) +} + +func (c *grpcGameClient) HandleCallback(ctx context.Context, req *gamev1.CallbackRequest) (*gamev1.CallbackResponse, error) { + return c.callback.HandleCallback(ctx, req) +} diff --git a/services/gateway-service/internal/client/room_client.go b/services/gateway-service/internal/client/room_client.go index 132a43c8..3089414b 100644 --- a/services/gateway-service/internal/client/room_client.go +++ b/services/gateway-service/internal/client/room_client.go @@ -18,6 +18,7 @@ type RoomClient interface { MicDown(ctx context.Context, req *roomv1.MicDownRequest) (*roomv1.MicDownResponse, error) ChangeMicSeat(ctx context.Context, req *roomv1.ChangeMicSeatRequest) (*roomv1.ChangeMicSeatResponse, error) ConfirmMicPublishing(ctx context.Context, req *roomv1.ConfirmMicPublishingRequest) (*roomv1.ConfirmMicPublishingResponse, error) + SetMicMute(ctx context.Context, req *roomv1.SetMicMuteRequest) (*roomv1.SetMicMuteResponse, error) ApplyRTCEvent(ctx context.Context, req *roomv1.ApplyRTCEventRequest) (*roomv1.ApplyRTCEventResponse, error) SetMicSeatLock(ctx context.Context, req *roomv1.SetMicSeatLockRequest) (*roomv1.SetMicSeatLockResponse, error) SetChatEnabled(ctx context.Context, req *roomv1.SetChatEnabledRequest) (*roomv1.SetChatEnabledResponse, error) @@ -43,6 +44,7 @@ type RoomQueryClient interface { GetMyRoom(ctx context.Context, req *roomv1.GetMyRoomRequest) (*roomv1.GetMyRoomResponse, error) GetCurrentRoom(ctx context.Context, req *roomv1.GetCurrentRoomRequest) (*roomv1.GetCurrentRoomResponse, error) GetRoomSnapshot(ctx context.Context, req *roomv1.GetRoomSnapshotRequest) (*roomv1.GetRoomSnapshotResponse, error) + ListRoomOnlineUsers(ctx context.Context, req *roomv1.ListRoomOnlineUsersRequest) (*roomv1.ListRoomOnlineUsersResponse, error) } type grpcRoomClient struct { @@ -114,6 +116,10 @@ func (c *grpcRoomClient) ConfirmMicPublishing(ctx context.Context, req *roomv1.C return c.client.ConfirmMicPublishing(ctx, req) } +func (c *grpcRoomClient) SetMicMute(ctx context.Context, req *roomv1.SetMicMuteRequest) (*roomv1.SetMicMuteResponse, error) { + return c.client.SetMicMute(ctx, req) +} + func (c *grpcRoomClient) ApplyRTCEvent(ctx context.Context, req *roomv1.ApplyRTCEventRequest) (*roomv1.ApplyRTCEventResponse, error) { return c.client.ApplyRTCEvent(ctx, req) } @@ -177,3 +183,7 @@ func (c *grpcRoomQueryClient) GetCurrentRoom(ctx context.Context, req *roomv1.Ge func (c *grpcRoomQueryClient) GetRoomSnapshot(ctx context.Context, req *roomv1.GetRoomSnapshotRequest) (*roomv1.GetRoomSnapshotResponse, error) { return c.client.GetRoomSnapshot(ctx, req) } + +func (c *grpcRoomQueryClient) ListRoomOnlineUsers(ctx context.Context, req *roomv1.ListRoomOnlineUsersRequest) (*roomv1.ListRoomOnlineUsersResponse, error) { + return c.client.ListRoomOnlineUsers(ctx, req) +} diff --git a/services/gateway-service/internal/config/config.go b/services/gateway-service/internal/config/config.go index f442a8d6..72f63d0d 100644 --- a/services/gateway-service/internal/config/config.go +++ b/services/gateway-service/internal/config/config.go @@ -21,6 +21,7 @@ type Config struct { UserServiceAddr string `yaml:"user_service_addr"` WalletServiceAddr string `yaml:"wallet_service_addr"` ActivityServiceAddr string `yaml:"activity_service_addr"` + GameServiceAddr string `yaml:"game_service_addr"` AuthRateLimit AuthRateLimitConfig `yaml:"auth_rate_limit"` LoginRisk LoginRiskConfig `yaml:"login_risk"` AppConfig AppConfigConfig `yaml:"app_config"` @@ -145,6 +146,7 @@ func Default() Config { UserServiceAddr: "127.0.0.1:13005", WalletServiceAddr: "127.0.0.1:13004", ActivityServiceAddr: "127.0.0.1:13006", + GameServiceAddr: "127.0.0.1:13008", AppConfig: AppConfigConfig{ MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_admin?parseTime=true&charset=utf8mb4&loc=UTC", }, @@ -222,12 +224,16 @@ func (cfg *Config) Normalize() error { cfg.UserServiceAddr = strings.TrimSpace(cfg.UserServiceAddr) cfg.WalletServiceAddr = strings.TrimSpace(cfg.WalletServiceAddr) cfg.ActivityServiceAddr = strings.TrimSpace(cfg.ActivityServiceAddr) + cfg.GameServiceAddr = strings.TrimSpace(cfg.GameServiceAddr) if cfg.WalletServiceAddr == "" { cfg.WalletServiceAddr = "127.0.0.1:13004" } if cfg.ActivityServiceAddr == "" { cfg.ActivityServiceAddr = "127.0.0.1:13006" } + if cfg.GameServiceAddr == "" { + cfg.GameServiceAddr = "127.0.0.1:13008" + } cfg.AppConfig.MySQLDSN = strings.TrimSpace(cfg.AppConfig.MySQLDSN) cfg.LoginRisk.RedisAddr = strings.TrimSpace(cfg.LoginRisk.RedisAddr) cfg.LoginRisk.KeyPrefix = strings.TrimSpace(cfg.LoginRisk.KeyPrefix) diff --git a/services/gateway-service/internal/transport/http/game_handler.go b/services/gateway-service/internal/transport/http/game_handler.go new file mode 100644 index 00000000..00cee693 --- /dev/null +++ b/services/gateway-service/internal/transport/http/game_handler.go @@ -0,0 +1,154 @@ +package http + +import ( + "io" + "net/http" + "strings" + "time" + + gamev1 "hyapp.local/api/proto/game/v1" + userv1 "hyapp.local/api/proto/user/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/gateway-service/internal/auth" + "hyapp/services/gateway-service/internal/transport/http/httpkit" +) + +func (h *Handler) listGames(writer http.ResponseWriter, request *http.Request) { + if h.gameClient == nil || h.userProfileClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + userID := auth.UserIDFromContext(request.Context()) + userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ + Meta: authRequestMeta(request, ""), + UserId: userID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + resp, err := h.gameClient.ListGames(request.Context(), &gamev1.ListGamesRequest{ + Meta: gameMeta(request), + UserId: userID, + Scene: strings.TrimSpace(request.URL.Query().Get("scene")), + RoomId: strings.TrimSpace(request.URL.Query().Get("room_id")), + RegionId: userResp.GetUser().GetRegionId(), + Language: requestLanguage(request), + ClientPlatform: firstHeader(request, "X-App-Platform", "X-Platform"), + }) + write(writer, request, gameListDataFromProto(resp), err) +} + +func (h *Handler) launchGame(writer http.ResponseWriter, request *http.Request) { + if h.gameClient == nil || h.userProfileClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + gameID := strings.TrimSpace(request.PathValue("game_id")) + if gameID == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + var body struct { + Scene string `json:"scene"` + RoomID string `json:"room_id"` + } + if !decode(writer, request, &body) { + return + } + userID := auth.UserIDFromContext(request.Context()) + userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ + Meta: authRequestMeta(request, ""), + UserId: userID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + resp, err := h.gameClient.LaunchGame(request.Context(), &gamev1.LaunchGameRequest{ + Meta: gameMeta(request), + UserId: userID, + DisplayUserId: userResp.GetUser().GetDisplayUserId(), + GameId: gameID, + Scene: body.Scene, + RoomId: body.RoomID, + }) + write(writer, request, gameLaunchDataFromProto(resp), err) +} + +func (h *Handler) handleGameCallback(writer http.ResponseWriter, request *http.Request) { + if h.gameClient == nil { + http.Error(writer, "upstream service error", http.StatusBadGateway) + return + } + raw, err := io.ReadAll(request.Body) + if err != nil { + http.Error(writer, "invalid request body", http.StatusBadRequest) + return + } + resp, err := h.gameClient.HandleCallback(request.Context(), &gamev1.CallbackRequest{ + Meta: gameMeta(request), + PlatformCode: strings.TrimSpace(request.PathValue("platform_code")), + Operation: strings.TrimSpace(request.PathValue("operation")), + RawBody: raw, + Headers: flattenedHeaders(request), + Query: flattenedQuery(request), + RemoteAddr: request.RemoteAddr, + }) + if err != nil { + status := xerr.SpecOf(xerr.ReasonFromGRPC(err)).HTTPStatus + http.Error(writer, xerr.SpecOf(xerr.ReasonFromGRPC(err)).PublicMessage, status) + return + } + contentType := strings.TrimSpace(resp.GetContentType()) + if contentType == "" { + contentType = "application/json" + } + writer.Header().Set("Content-Type", contentType) + writer.WriteHeader(http.StatusOK) + _, _ = writer.Write(resp.GetRawBody()) +} + +func gameMeta(request *http.Request) *gamev1.RequestMeta { + return &gamev1.RequestMeta{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + Caller: "gateway-service", + GatewayNodeId: "", + ActorUserId: auth.UserIDFromContext(request.Context()), + SentAtMs: time.Now().UnixMilli(), + AppCode: appcode.FromContext(request.Context()), + } +} + +func requestLanguage(request *http.Request) string { + value := firstHeader(request, "X-App-Language", "X-Language") + if value == "" { + value = request.Header.Get("Accept-Language") + } + if index := strings.Index(value, ","); index >= 0 { + value = value[:index] + } + return strings.TrimSpace(value) +} + +func flattenedHeaders(request *http.Request) map[string]string { + headers := make(map[string]string, len(request.Header)) + for key, values := range request.Header { + if len(values) > 0 { + headers[key] = values[0] + } + } + return headers +} + +func flattenedQuery(request *http.Request) map[string]string { + query := request.URL.Query() + result := make(map[string]string, len(query)) + for key, values := range query { + if len(values) > 0 { + result[key] = values[0] + } + } + return result +} diff --git a/services/gateway-service/internal/transport/http/game_handler_test.go b/services/gateway-service/internal/transport/http/game_handler_test.go new file mode 100644 index 00000000..82d20872 --- /dev/null +++ b/services/gateway-service/internal/transport/http/game_handler_test.go @@ -0,0 +1,131 @@ +package http + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + gamev1 "hyapp.local/api/proto/game/v1" + "hyapp/services/gateway-service/internal/auth" + "hyapp/services/gateway-service/internal/transport/http/httpkit" +) + +func TestListGamesUsesAuthenticatedUserRegionAndReturnsEnvelope(t *testing.T) { + gameClient := &fakeGatewayGameClient{listResp: &gamev1.ListGamesResponse{ + Games: []*gamev1.AppGame{{ + GameId: "demo_rocket_001", + PlatformCode: "demo", + NameKey: "game.demo_rocket_001.name", + Name: "Rocket", + LaunchMode: "h5_popup", + Orientation: "portrait", + Enabled: true, + }}, + ServerTimeMs: 1700000000000, + }} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{regionID: 1001}) + handler.SetGameClient(gameClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodGet, "/api/v1/games?scene=voice_room&room_id=room-1", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + request.Header.Set("X-App-Language", "en") + 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 gameClient.lastList.GetUserId() != 42 || gameClient.lastList.GetRegionId() != 1001 || gameClient.lastList.GetScene() != "voice_room" || gameClient.lastList.GetRoomId() != "room-1" { + t.Fatalf("ListGames request mismatch: %+v", gameClient.lastList) + } + var envelope httpkit.ResponseEnvelope + if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { + t.Fatalf("decode response failed: %v", err) + } + data := envelope.Data.(map[string]any) + games := data["games"].([]any) + first := games[0].(map[string]any) + if first["platform_code"] != "demo" || first["game_id"] != "demo_rocket_001" { + t.Fatalf("game response mismatch: %+v", first) + } +} + +func TestLaunchGameForwardsDisplayUserID(t *testing.T) { + gameClient := &fakeGatewayGameClient{launchResp: &gamev1.LaunchGameResponse{ + SessionId: "game_sess_1", + LaunchUrl: "https://provider.example/h5?session_token=token", + ExpiresAtMs: 1700000900000, + Orientation: "portrait", + }} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + handler.SetGameClient(gameClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/games/demo_rocket_001/launch", bytes.NewReader([]byte(`{"scene":"voice_room","room_id":"room-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 gameClient.lastLaunch.GetGameId() != "demo_rocket_001" || gameClient.lastLaunch.GetDisplayUserId() != "100001" || gameClient.lastLaunch.GetRoomId() != "room-1" { + t.Fatalf("LaunchGame request mismatch: %+v", gameClient.lastLaunch) + } +} + +func TestGameCallbackIsRawPublicResponse(t *testing.T) { + gameClient := &fakeGatewayGameClient{callbackResp: &gamev1.CallbackResponse{RawBody: []byte(`{"code":0}`), ContentType: "application/json"}} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + handler.SetGameClient(gameClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/game-callbacks/demo/change_coin?nonce=1", bytes.NewReader([]byte(`{"provider_order_id":"p1"}`))) + request.Header.Set("X-App-Code", "lalu") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK || recorder.Body.String() != `{"code":0}` { + t.Fatalf("callback response must not use app envelope: status=%d body=%s", recorder.Code, recorder.Body.String()) + } + if gameClient.lastCallback.GetPlatformCode() != "demo" || gameClient.lastCallback.GetOperation() != "change_coin" || string(gameClient.lastCallback.GetRawBody()) != `{"provider_order_id":"p1"}` || gameClient.lastCallback.GetQuery()["nonce"] != "1" { + t.Fatalf("callback request mismatch: %+v", gameClient.lastCallback) + } +} + +type fakeGatewayGameClient struct { + listResp *gamev1.ListGamesResponse + launchResp *gamev1.LaunchGameResponse + callbackResp *gamev1.CallbackResponse + lastList *gamev1.ListGamesRequest + lastLaunch *gamev1.LaunchGameRequest + lastCallback *gamev1.CallbackRequest +} + +func (f *fakeGatewayGameClient) ListGames(_ context.Context, req *gamev1.ListGamesRequest) (*gamev1.ListGamesResponse, error) { + f.lastList = req + if f.listResp != nil { + return f.listResp, nil + } + return &gamev1.ListGamesResponse{}, nil +} + +func (f *fakeGatewayGameClient) LaunchGame(_ context.Context, req *gamev1.LaunchGameRequest) (*gamev1.LaunchGameResponse, error) { + f.lastLaunch = req + if f.launchResp != nil { + return f.launchResp, nil + } + return &gamev1.LaunchGameResponse{}, nil +} + +func (f *fakeGatewayGameClient) HandleCallback(_ context.Context, req *gamev1.CallbackRequest) (*gamev1.CallbackResponse, error) { + f.lastCallback = req + if f.callbackResp != nil { + return f.callbackResp, nil + } + return &gamev1.CallbackResponse{RawBody: []byte(`{"code":0}`), ContentType: "application/json"}, nil +} diff --git a/services/gateway-service/internal/transport/http/game_view.go b/services/gateway-service/internal/transport/http/game_view.go new file mode 100644 index 00000000..8fe0b717 --- /dev/null +++ b/services/gateway-service/internal/transport/http/game_view.go @@ -0,0 +1,70 @@ +package http + +import gamev1 "hyapp.local/api/proto/game/v1" + +type gameListData struct { + Games []gameItemData `json:"games"` + ServerTimeMS int64 `json:"server_time_ms"` +} + +type gameItemData struct { + GameID string `json:"game_id"` + PlatformCode string `json:"platform_code"` + NameKey string `json:"name_key"` + Name string `json:"name"` + IconURL string `json:"icon_url"` + CoverURL string `json:"cover_url"` + Category string `json:"category"` + LaunchMode string `json:"launch_mode"` + Orientation string `json:"orientation"` + MinCoin int64 `json:"min_coin"` + Enabled bool `json:"enabled"` + Maintenance bool `json:"maintenance"` + SortOrder int32 `json:"sort_order"` +} + +type gameLaunchData struct { + SessionID string `json:"session_id"` + LaunchURL string `json:"launch_url"` + ExpiresAtMS int64 `json:"expires_at_ms"` + Orientation string `json:"orientation"` + ServerTimeMS int64 `json:"server_time_ms"` +} + +func gameListDataFromProto(resp *gamev1.ListGamesResponse) gameListData { + if resp == nil { + return gameListData{} + } + items := make([]gameItemData, 0, len(resp.GetGames())) + for _, item := range resp.GetGames() { + items = append(items, gameItemData{ + GameID: item.GetGameId(), + PlatformCode: item.GetPlatformCode(), + NameKey: item.GetNameKey(), + Name: item.GetName(), + IconURL: item.GetIconUrl(), + CoverURL: item.GetCoverUrl(), + Category: item.GetCategory(), + LaunchMode: item.GetLaunchMode(), + Orientation: item.GetOrientation(), + MinCoin: item.GetMinCoin(), + Enabled: item.GetEnabled(), + Maintenance: item.GetMaintenance(), + SortOrder: item.GetSortOrder(), + }) + } + return gameListData{Games: items, ServerTimeMS: resp.GetServerTimeMs()} +} + +func gameLaunchDataFromProto(resp *gamev1.LaunchGameResponse) gameLaunchData { + if resp == nil { + return gameLaunchData{} + } + return gameLaunchData{ + SessionID: resp.GetSessionId(), + LaunchURL: resp.GetLaunchUrl(), + ExpiresAtMS: resp.GetExpiresAtMs(), + Orientation: resp.GetOrientation(), + ServerTimeMS: resp.GetServerTimeMs(), + } +} diff --git a/services/gateway-service/internal/transport/http/handler.go b/services/gateway-service/internal/transport/http/handler.go index 17bf1cf1..726b0520 100644 --- a/services/gateway-service/internal/transport/http/handler.go +++ b/services/gateway-service/internal/transport/http/handler.go @@ -37,6 +37,7 @@ type Handler struct { messageClient client.MessageInboxClient taskClient client.TaskClient broadcastClient client.BroadcastClient + gameClient client.GameClient appConfigReader appConfigReader tencentIM TencentIMConfig tencentRTC TencentRTCConfig @@ -177,6 +178,11 @@ func (h *Handler) SetBroadcastClient(broadcastClient client.BroadcastClient) { h.broadcastClient = broadcastClient } +// SetGameClient 注入 game-service 游戏列表、启动和回调 client。 +func (h *Handler) SetGameClient(gameClient client.GameClient) { + h.gameClient = gameClient +} + // SetAppConfigReader 注入后台 App 配置读取依赖。 func (h *Handler) SetAppConfigReader(reader appConfigReader) { h.appConfigReader = reader diff --git a/services/gateway-service/internal/transport/http/httproutes/router.go b/services/gateway-service/internal/transport/http/httproutes/router.go index 8017d6f4..980d7ce9 100644 --- a/services/gateway-service/internal/transport/http/httproutes/router.go +++ b/services/gateway-service/internal/transport/http/httproutes/router.go @@ -23,6 +23,7 @@ type Config struct { Task TaskHandlers Wallet WalletHandlers VIP VIPHandlers + Game GameHandlers } type AuthHandlers struct { @@ -53,6 +54,7 @@ type AppHandlers struct { type UserHandlers struct { ResolveDisplayUserID http.HandlerFunc BatchUserProfiles http.HandlerFunc + BatchRoomDisplayProfiles http.HandlerFunc GetMyOverview http.HandlerFunc GetMyIdentity http.HandlerFunc GetMyHostIdentity http.HandlerFunc @@ -84,15 +86,20 @@ type RoomHandlers struct { ListRooms http.HandlerFunc GetCurrentRoom http.HandlerFunc GetRoomSnapshot http.HandlerFunc + GetRoomDetail http.HandlerFunc + ListRoomOnlineUsers http.HandlerFunc + GetRoomGiftPanel http.HandlerFunc CreateRoom http.HandlerFunc UpdateRoomProfile http.HandlerFunc JoinRoom http.HandlerFunc RoomHeartbeat http.HandlerFunc LeaveRoom http.HandlerFunc + CloseRoom http.HandlerFunc MicUp http.HandlerFunc MicDown http.HandlerFunc ChangeMicSeat http.HandlerFunc ConfirmMicPublishing http.HandlerFunc + SetMicMute http.HandlerFunc SetMicSeatLock http.HandlerFunc SetChatEnabled http.HandlerFunc SetRoomAdmin http.HandlerFunc @@ -132,6 +139,12 @@ type VIPHandlers struct { PurchaseVIP http.HandlerFunc } +type GameHandlers struct { + ListGames http.HandlerFunc + LaunchGame http.HandlerFunc + HandleCallback http.HandlerFunc +} + type routes struct { mux *http.ServeMux config Config @@ -152,6 +165,7 @@ func New(config Config) http.Handler { routes.registerTaskRoutes() routes.registerWalletRoutes() routes.registerVIPRoutes() + routes.registerGameRoutes() return mux } @@ -207,6 +221,7 @@ func (r routes) registerUserRoutes() { h := r.config.User r.public("/users/by-display-user-id/{display_user_id}", "", h.ResolveDisplayUserID) r.profile("/users/profiles:batch", "", h.BatchUserProfiles) + r.profile("/users/room-display-profiles:batch", http.MethodGet, h.BatchRoomDisplayProfiles) r.profile("/users/me/overview", "", h.GetMyOverview) r.auth("/users/me/identity", "", h.GetMyIdentity) r.profile("/users/me/host-identity", "", h.GetMyHostIdentity) @@ -240,15 +255,20 @@ func (r routes) registerRoomRoutes() { r.profile("/rooms", http.MethodGet, h.ListRooms) r.profile("/rooms/current", http.MethodGet, h.GetCurrentRoom) r.profile("/rooms/snapshot", http.MethodGet, h.GetRoomSnapshot) + r.profile("/rooms/{room_id}/detail", http.MethodGet, h.GetRoomDetail) + r.profile("/rooms/{room_id}/online-users", http.MethodGet, h.ListRoomOnlineUsers) + r.profile("/rooms/{room_id}/gift-panel", http.MethodGet, h.GetRoomGiftPanel) r.profile("/rooms/create", http.MethodPost, h.CreateRoom) r.profile("/rooms/profile/update", http.MethodPost, h.UpdateRoomProfile) r.profile("/rooms/join", http.MethodPost, h.JoinRoom) r.profile("/rooms/heartbeat", http.MethodPost, h.RoomHeartbeat) r.profile("/rooms/leave", http.MethodPost, h.LeaveRoom) + r.profile("/rooms/close", http.MethodPost, h.CloseRoom) r.profile("/rooms/mic/up", http.MethodPost, h.MicUp) r.profile("/rooms/mic/down", http.MethodPost, h.MicDown) r.profile("/rooms/mic/change", http.MethodPost, h.ChangeMicSeat) r.profile("/rooms/mic/publishing/confirm", http.MethodPost, h.ConfirmMicPublishing) + r.profile("/rooms/mic/mute", http.MethodPost, h.SetMicMute) r.profile("/rooms/mic/lock", http.MethodPost, h.SetMicSeatLock) r.profile("/rooms/chat/enabled", http.MethodPost, h.SetChatEnabled) r.profile("/rooms/admin/set", http.MethodPost, h.SetRoomAdmin) @@ -291,3 +311,10 @@ func (r routes) registerVIPRoutes() { r.profile("/vip/packages", http.MethodGet, h.ListVIPPackages) r.profile("/vip/purchase", http.MethodPost, h.PurchaseVIP) } + +func (r routes) registerGameRoutes() { + h := r.config.Game + r.profile("/games", http.MethodGet, h.ListGames) + r.profile("/games/{game_id}/launch", http.MethodPost, h.LaunchGame) + r.public("/game-callbacks/{platform_code}/{operation}", http.MethodPost, h.HandleCallback) +} diff --git a/services/gateway-service/internal/transport/http/response_test.go b/services/gateway-service/internal/transport/http/response_test.go index c349bba3..df7168f4 100644 --- a/services/gateway-service/internal/transport/http/response_test.go +++ b/services/gateway-service/internal/transport/http/response_test.go @@ -41,6 +41,7 @@ type fakeRoomClient struct { lastMicDown *roomv1.MicDownRequest lastChangeMicSeat *roomv1.ChangeMicSeatRequest lastConfirmMic *roomv1.ConfirmMicPublishingRequest + lastSetMicMute *roomv1.SetMicMuteRequest lastRTCEvent *roomv1.ApplyRTCEventRequest lastMicSeatLock *roomv1.SetMicSeatLockRequest lastChatEnabled *roomv1.SetChatEnabledRequest @@ -157,6 +158,11 @@ func (f *fakeRoomClient) ConfirmMicPublishing(_ context.Context, req *roomv1.Con return &roomv1.ConfirmMicPublishingResponse{Result: &roomv1.CommandResult{Applied: true, RoomVersion: 7}}, nil } +func (f *fakeRoomClient) SetMicMute(_ context.Context, req *roomv1.SetMicMuteRequest) (*roomv1.SetMicMuteResponse, error) { + f.lastSetMicMute = req + return &roomv1.SetMicMuteResponse{Result: &roomv1.CommandResult{Applied: true, RoomVersion: 7}}, nil +} + func (f *fakeRoomClient) ApplyRTCEvent(_ context.Context, req *roomv1.ApplyRTCEventRequest) (*roomv1.ApplyRTCEventResponse, error) { f.lastRTCEvent = req return &roomv1.ApplyRTCEventResponse{Result: &roomv1.CommandResult{Applied: true, RoomVersion: 8}}, nil @@ -249,16 +255,19 @@ type fakeRoomQueryClient struct { lastMyRoom *roomv1.GetMyRoomRequest lastCurrent *roomv1.GetCurrentRoomRequest lastSnapshot *roomv1.GetRoomSnapshotRequest + lastOnline *roomv1.ListRoomOnlineUsersRequest resp *roomv1.ListRoomsResponse feedsResp *roomv1.ListRoomsResponse myRoomResp *roomv1.GetMyRoomResponse currentResp *roomv1.GetCurrentRoomResponse snapshotResp *roomv1.GetRoomSnapshotResponse + onlineResp *roomv1.ListRoomOnlineUsersResponse err error feedsErr error myRoomErr error currentErr error snapshotErr error + onlineErr error } type fakeUserDeviceClient struct { @@ -646,6 +655,17 @@ func (f *fakeRoomQueryClient) GetRoomSnapshot(_ context.Context, req *roomv1.Get return &roomv1.GetRoomSnapshotResponse{}, nil } +func (f *fakeRoomQueryClient) ListRoomOnlineUsers(_ context.Context, req *roomv1.ListRoomOnlineUsersRequest) (*roomv1.ListRoomOnlineUsersResponse, error) { + f.lastOnline = req + if f.onlineErr != nil { + return nil, f.onlineErr + } + if f.onlineResp != nil { + return f.onlineResp, nil + } + return &roomv1.ListRoomOnlineUsersResponse{Page: req.GetPage(), PageSize: req.GetPageSize()}, nil +} + func (f *fakeUserDeviceClient) BindPushToken(_ context.Context, req *userv1.BindPushTokenRequest) (*userv1.BindPushTokenResponse, error) { f.lastBind = req if f.bindErr != nil { diff --git a/services/gateway-service/internal/transport/http/room_handler.go b/services/gateway-service/internal/transport/http/room_handler.go index 7c428fd7..1272d88c 100644 --- a/services/gateway-service/internal/transport/http/room_handler.go +++ b/services/gateway-service/internal/transport/http/room_handler.go @@ -10,6 +10,7 @@ import ( 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/roomid" "hyapp/pkg/xerr" @@ -369,6 +370,133 @@ func (h *Handler) getRoomSnapshot(writer http.ResponseWriter, request *http.Requ write(writer, request, roomSnapshotDataFromProto(resp), err) } +// getRoomDetail 返回语音房详情页首屏可直接渲染的数据。 +// 它是 GetRoomSnapshot 的 App 聚合版:补 viewer 权限、IM/RTC 入房参数和首屏展示资料。 +func (h *Handler) getRoomDetail(writer http.ResponseWriter, request *http.Request) { + if h.roomQueryClient == 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()) + resp, err := h.roomQueryClient.GetRoomSnapshot(request.Context(), &roomv1.GetRoomSnapshotRequest{ + Meta: meta(request, roomID, ""), + RoomId: roomID, + ViewerUserId: viewerUserID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + profiles := h.roomDisplayProfiles(request, roomInitialProfileUserIDs(resp.GetRoom(), viewerUserID, joinRoomContributionRankLimit)) + httpkit.WriteOK(writer, request, roomDetailDataFromSnapshot(resp, viewerUserID, h.joinRoomRTCData(roomID, viewerUserID), profiles)) +} + +// listRoomOnlineUsers 分页返回右上角在线人数弹窗需要的用户列表。 +func (h *Handler) listRoomOnlineUsers(writer http.ResponseWriter, request *http.Request) { + if h.roomQueryClient == 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 + } + page, ok := parsePositiveInt32Query(request, "page", 1) + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + pageSize, ok := parsePositiveInt32Query(request, "page_size", 50) + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + + resp, err := h.roomQueryClient.ListRoomOnlineUsers(request.Context(), &roomv1.ListRoomOnlineUsersRequest{ + Meta: meta(request, roomID, ""), + RoomId: roomID, + ViewerUserId: auth.UserIDFromContext(request.Context()), + Page: page, + PageSize: pageSize, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + userIDs := make([]int64, 0, len(resp.GetUsers())) + for _, user := range resp.GetUsers() { + userIDs = append(userIDs, user.GetUserId()) + } + httpkit.WriteOK(writer, request, roomOnlineUserDataFromProto(resp, h.roomDisplayProfileMap(request, userIDs))) +} + +// 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: meta(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 + } + giftResp, err := h.walletClient.ListGiftConfigs(request.Context(), &walletv1.ListGiftConfigsRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + AppCode: appcode.FromContext(request.Context()), + Page: 1, + PageSize: 500, + ActiveOnly: true, + RegionId: snapshot.GetVisibleRegionId(), + FilterRegion: true, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + gifts := make([]giftConfigData, 0, len(giftResp.GetGifts())) + for _, gift := range giftResp.GetGifts() { + gifts = append(gifts, giftFromProto(gift)) + } + recipientProfiles := h.roomDisplayProfileMap(request, roomGiftRecipientUserIDs(snapshot)) + httpkit.WriteOK(writer, request, roomGiftPanelData{ + CoinBalance: coinBalanceFromProto(balances), + Recipients: roomGiftRecipients(snapshot, recipientProfiles), + Tabs: roomGiftTabs(gifts), + Gifts: gifts, + QuantityPresets: []int32{1, 9, 99, 999}, + }) +} + // createRoom 把创建房间 HTTP JSON 请求转换为 room-service CreateRoom 命令。 func (h *Handler) createRoom(writer http.ResponseWriter, request *http.Request) { var body struct { @@ -590,6 +718,11 @@ func (h *Handler) leaveRoom(writer http.ResponseWriter, request *http.Request) { write(writer, request, resp, err) } +// closeRoom 是设计稿右上角电源按钮入口:这里只退出当前用户,不关闭房间生命周期。 +func (h *Handler) closeRoom(writer http.ResponseWriter, request *http.Request) { + h.leaveRoom(writer, request) +} + // micUp 把上麦 HTTP JSON 请求转换为 room-service MicUp 命令。 func (h *Handler) micUp(writer http.ResponseWriter, request *http.Request) { var body struct { @@ -678,6 +811,27 @@ func (h *Handler) confirmMicPublishing(writer http.ResponseWriter, request *http write(writer, request, resp, err) } +// setMicMute 把麦克风静音 HTTP 请求转换为 room-service SetMicMute 命令。 +func (h *Handler) setMicMute(writer http.ResponseWriter, request *http.Request) { + var body struct { + RoomID string `json:"room_id"` + CommandID string `json:"command_id"` + TargetUserID int64 `json:"target_user_id"` + Muted bool `json:"muted"` + } + + if !decode(writer, request, &body) { + return + } + + resp, err := h.roomClient.SetMicMute(request.Context(), &roomv1.SetMicMuteRequest{ + Meta: meta(request, body.RoomID, body.CommandID), + TargetUserId: body.TargetUserID, + Muted: body.Muted, + }) + write(writer, request, resp, err) +} + // setMicSeatLock 把锁麦 HTTP JSON 请求转换为 room-service SetMicSeatLock 命令。 func (h *Handler) setMicSeatLock(writer http.ResponseWriter, request *http.Request) { var body struct { @@ -821,22 +975,190 @@ func (h *Handler) unbanUser(writer http.ResponseWriter, request *http.Request) { // 扣费、幂等、房间表现和 outbox 仍由 room-service 的命令链路统一处理。 func (h *Handler) sendGift(writer http.ResponseWriter, request *http.Request) { var body struct { - RoomID string `json:"room_id"` - CommandID string `json:"command_id"` - TargetUserID int64 `json:"target_user_id"` - GiftID string `json:"gift_id"` - GiftCount int32 `json:"gift_count"` + 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"` } if !decode(writer, request, &body) { return } + targetType := strings.TrimSpace(body.TargetType) + if targetType == "" { + targetType = "user" + } + targetUserIDs := normalizeGiftTargetUserIDs(body.TargetUserID, body.TargetUserIDs) + if targetType == "user" && len(targetUserIDs) != 1 { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } resp, err := h.roomClient.SendGift(request.Context(), &roomv1.SendGiftRequest{ - Meta: meta(request, body.RoomID, body.CommandID), - TargetUserId: body.TargetUserID, - GiftId: body.GiftID, - GiftCount: body.GiftCount, + Meta: meta(request, body.RoomID, body.CommandID), + TargetType: targetType, + TargetUserIds: targetUserIDs, + TargetUserId: firstUserID(targetUserIDs), + GiftId: body.GiftID, + GiftCount: body.GiftCount, }) write(writer, request, resp, err) } + +// batchRoomDisplayProfiles 返回房间和公屏专用展示资料。 +func (h *Handler) batchRoomDisplayProfiles(writer http.ResponseWriter, request *http.Request) { + if h.userProfileClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + userIDs, ok := parseBatchUserIDs(request) + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + httpkit.WriteOK(writer, request, map[string]any{"profiles": h.roomDisplayProfiles(request, userIDs)}) +} + +func (h *Handler) roomDisplayProfiles(request *http.Request, userIDs []int64) []roomDisplayProfileData { + profileMap := h.roomDisplayProfileMap(request, userIDs) + profiles := make([]roomDisplayProfileData, 0, len(userIDs)) + for _, userID := range userIDs { + if profile, ok := profileMap[userID]; ok { + profiles = append(profiles, profile) + } + } + return profiles +} + +func (h *Handler) roomDisplayProfileMap(request *http.Request, userIDs []int64) map[int64]roomDisplayProfileData { + if h.userProfileClient == nil { + return nil + } + userIDs = uniquePositiveUserIDs(userIDs) + if len(userIDs) == 0 { + return nil + } + resp, err := h.userProfileClient.BatchGetUsers(request.Context(), &userv1.BatchGetUsersRequest{ + Meta: authRequestMeta(request, ""), + UserIds: userIDs, + }) + if err != nil { + // 房间资料是展示增强,失败时保留主房间数据,让客户端后续重试 batch 接口。 + return nil + } + profiles := make(map[int64]roomDisplayProfileData, len(resp.GetUsers())) + for _, userID := range userIDs { + user := resp.GetUsers()[userID] + if user == nil { + continue + } + profiles[userID] = roomDisplayProfileData{ + UserID: formatOptionalUserID(user.GetUserId()), + Username: user.GetUsername(), + Avatar: user.GetAvatar(), + DisplayUserID: user.GetDisplayUserId(), + VIP: map[string]any{}, + Level: map[string]any{}, + Badges: []map[string]any{}, + AvatarFrame: map[string]any{}, + Vehicle: map[string]any{}, + Charm: 0, + } + } + return profiles +} + +func roomGiftRecipientUserIDs(snapshot *roomv1.RoomSnapshot) []int64 { + userIDs := make([]int64, 0, len(snapshot.GetMicSeats())+1) + for _, seat := range snapshot.GetMicSeats() { + if seat.GetUserId() > 0 { + userIDs = append(userIDs, seat.GetUserId()) + } + } + if len(userIDs) == 0 { + userIDs = append(userIDs, snapshot.GetHostUserId()) + } + return uniquePositiveUserIDs(userIDs) +} + +func roomGiftRecipients(snapshot *roomv1.RoomSnapshot, profiles map[int64]roomDisplayProfileData) []roomGiftRecipientData { + userIDs := roomGiftRecipientUserIDs(snapshot) + recipients := make([]roomGiftRecipientData, 0, len(userIDs)+1) + if len(userIDs) > 1 { + recipients = append(recipients, roomGiftRecipientData{TargetType: "all_mic", Label: "all_mic"}) + } + seatByUser := make(map[int64]int32, len(snapshot.GetMicSeats())) + for _, seat := range snapshot.GetMicSeats() { + if seat.GetUserId() > 0 { + seatByUser[seat.GetUserId()] = seat.GetSeatNo() + } + } + for _, userID := range userIDs { + recipient := roomGiftRecipientData{ + TargetType: "user", + UserID: formatOptionalUserID(userID), + SeatNo: seatByUser[userID], + } + if profile, ok := profiles[userID]; ok { + copied := profile + recipient.Profile = &copied + recipient.Label = copied.Username + } + recipients = append(recipients, recipient) + } + return recipients +} + +func roomGiftTabs(gifts []giftConfigData) []roomGiftTabData { + tabs := []roomGiftTabData{{Key: "all", Order: 0}} + seen := map[string]bool{"all": true} + for _, gift := range gifts { + key := strings.TrimSpace(gift.GiftTypeCode) + if key == "" || seen[key] { + continue + } + seen[key] = true + tabs = append(tabs, roomGiftTabData{Key: key, Order: int32(len(tabs) * 10)}) + } + return tabs +} + +func coinBalanceFromProto(resp *walletv1.GetBalancesResponse) int64 { + for _, balance := range resp.GetBalances() { + if balance.GetAssetType() == "COIN" { + return balance.GetAvailableAmount() + } + } + return 0 +} + +func uniquePositiveUserIDs(userIDs []int64) []int64 { + seen := make(map[int64]bool, len(userIDs)) + result := make([]int64, 0, len(userIDs)) + for _, userID := range userIDs { + if userID <= 0 || seen[userID] { + continue + } + seen[userID] = true + result = append(result, userID) + } + return result +} + +func normalizeGiftTargetUserIDs(targetUserID int64, targetUserIDs []int64) []int64 { + result := uniquePositiveUserIDs(targetUserIDs) + if len(result) == 0 && targetUserID > 0 { + result = append(result, targetUserID) + } + return result +} + +func firstUserID(userIDs []int64) int64 { + if len(userIDs) == 0 { + return 0 + } + return userIDs[0] +} diff --git a/services/gateway-service/internal/transport/http/room_view.go b/services/gateway-service/internal/transport/http/room_view.go index de1aadce..cd0fa7a7 100644 --- a/services/gateway-service/internal/transport/http/room_view.go +++ b/services/gateway-service/internal/transport/http/room_view.go @@ -51,6 +51,83 @@ type joinRoomData struct { ServerTimeMS int64 `json:"server_time_ms"` } +type roomDetailData struct { + Room roomInitialData `json:"room"` + Viewer roomViewerData `json:"viewer"` + Seats []roomSeatData `json:"seats"` + RankTop []roomRankItemData `json:"rank_top"` + OnlineCount int32 `json:"online_count"` + Permissions roomPermissionsData `json:"permissions"` + Profiles []roomDisplayProfileData `json:"profiles,omitempty"` + IM roomIMData `json:"im"` + RTC roomRTCData `json:"rtc"` + ServerTimeMS int64 `json:"server_time_ms"` +} + +type roomOnlineUsersData struct { + Items []roomOnlineUserData `json:"items"` + Total int64 `json:"total"` + Page int32 `json:"page"` + PageSize int32 `json:"page_size"` + ServerTimeMS int64 `json:"server_time_ms"` +} + +type roomOnlineUserData struct { + UserID string `json:"user_id"` + Role string `json:"role,omitempty"` + LastSeenAtMS int64 `json:"last_seen_at_ms,omitempty"` + Profile *roomDisplayProfileData `json:"profile,omitempty"` +} + +type roomPermissionsData struct { + IsOwner bool `json:"is_owner"` + IsHost bool `json:"is_host"` + IsAdmin bool `json:"is_admin"` + IsMuted bool `json:"is_muted"` + CanSpeak bool `json:"can_speak"` + CanSendGift bool `json:"can_send_gift"` + CanMicUp bool `json:"can_mic_up"` + CanManageRoom bool `json:"can_manage_room"` + CanLeaveRoom bool `json:"can_leave_room"` + CanCloseRoom bool `json:"can_close_room"` + CanMuteMic bool `json:"can_mute_mic"` + CanOpenGiftBox bool `json:"can_open_gift_box"` +} + +type roomDisplayProfileData struct { + UserID string `json:"user_id"` + Username string `json:"username"` + Avatar string `json:"avatar"` + DisplayUserID string `json:"display_user_id"` + VIP map[string]any `json:"vip"` + Level map[string]any `json:"level"` + Badges []map[string]any `json:"badges"` + AvatarFrame map[string]any `json:"avatar_frame"` + Vehicle map[string]any `json:"vehicle"` + Charm int64 `json:"charm"` +} + +type roomGiftPanelData struct { + CoinBalance int64 `json:"coin_balance"` + Recipients []roomGiftRecipientData `json:"recipients"` + Tabs []roomGiftTabData `json:"tabs"` + Gifts []giftConfigData `json:"gifts"` + QuantityPresets []int32 `json:"quantity_presets"` +} + +type roomGiftRecipientData struct { + TargetType string `json:"target_type"` + UserID string `json:"user_id,omitempty"` + SeatNo int32 `json:"seat_no,omitempty"` + Label string `json:"label,omitempty"` + Profile *roomDisplayProfileData `json:"profile,omitempty"` +} + +type roomGiftTabData struct { + Key string `json:"key"` + Order int32 `json:"order"` +} + type updateRoomProfileData struct { Result roomCommandResultData `json:"result"` Room roomInitialData `json:"room"` @@ -87,6 +164,7 @@ type roomInitialData struct { OnlineCount int32 `json:"online_count"` SeatCount int32 `json:"seat_count"` OccupiedSeatCount int32 `json:"occupied_seat_count"` + VisibleRegionID int64 `json:"visible_region_id,omitempty"` Version int64 `json:"version"` } @@ -104,6 +182,7 @@ type roomSeatData struct { PublishState string `json:"publish_state,omitempty"` MicSessionID string `json:"mic_session_id,omitempty"` PublishDeadlineMS int64 `json:"publish_deadline_ms,omitempty"` + MicMuted bool `json:"mic_muted"` } type roomRankItemData struct { @@ -245,6 +324,7 @@ func roomInitialRoomDataFromSnapshot(snapshot *roomv1.RoomSnapshot, roomID strin OnlineCount: int32(len(snapshot.GetOnlineUsers())), SeatCount: seatCount, OccupiedSeatCount: occupiedSeatCount, + VisibleRegionID: snapshot.GetVisibleRegionId(), Version: snapshot.GetVersion(), } } @@ -273,11 +353,58 @@ func roomSeatDataFromSnapshot(snapshot *roomv1.RoomSnapshot) []roomSeatData { PublishState: seat.GetPublishState(), MicSessionID: seat.GetMicSessionId(), PublishDeadlineMS: seat.GetPublishDeadlineMs(), + MicMuted: seat.GetMicMuted(), }) } return items } +func roomDetailDataFromSnapshot(resp *roomv1.GetRoomSnapshotResponse, viewerUserID int64, rtc roomRTCData, profiles []roomDisplayProfileData) roomDetailData { + if resp == nil { + return roomDetailData{} + } + snapshot := resp.GetRoom() + roomID := snapshot.GetRoomId() + viewer := protoRoomUserByID(snapshot, viewerUserID) + return roomDetailData{ + Room: roomInitialRoomDataFromSnapshot(snapshot, roomID), + Viewer: roomViewerDataFromProto(viewer, viewerUserID), + Seats: roomSeatDataFromSnapshot(snapshot), + RankTop: roomRankDataFromSnapshot(snapshot, joinRoomContributionRankLimit), + OnlineCount: int32(len(snapshot.GetOnlineUsers())), + Permissions: roomPermissionsFromSnapshot(snapshot, viewerUserID), + Profiles: profiles, + IM: roomIMData{GroupID: roomIMGroupID(roomID), NeedJoinGroup: true}, + RTC: rtc, + ServerTimeMS: resp.GetServerTimeMs(), + } +} + +func roomPermissionsFromSnapshot(snapshot *roomv1.RoomSnapshot, viewerUserID int64) roomPermissionsData { + if snapshot == nil || viewerUserID <= 0 { + return roomPermissionsData{} + } + isOwner := snapshot.GetOwnerUserId() == viewerUserID + isHost := snapshot.GetHostUserId() == viewerUserID + isAdmin := containsInt64(snapshot.GetAdminUserIds(), viewerUserID) + isMuted := containsInt64(snapshot.GetMuteUserIds(), viewerUserID) + inRoom := protoRoomUserByID(snapshot, viewerUserID) != nil + return roomPermissionsData{ + IsOwner: isOwner, + IsHost: isHost, + IsAdmin: isAdmin, + IsMuted: isMuted, + CanSpeak: inRoom && snapshot.GetChatEnabled() && !isMuted, + CanSendGift: inRoom && snapshot.GetStatus() == "active", + CanMicUp: inRoom && snapshot.GetStatus() == "active" && protoSeatByUserID(snapshot, viewerUserID) == nil, + CanManageRoom: inRoom && (isOwner || isHost || isAdmin), + CanLeaveRoom: inRoom, + CanCloseRoom: inRoom, + CanMuteMic: inRoom && protoSeatByUserID(snapshot, viewerUserID) != nil, + CanOpenGiftBox: inRoom && snapshot.GetStatus() == "active", + } +} + func roomRankDataFromSnapshot(snapshot *roomv1.RoomSnapshot, limit int) []roomRankItemData { rank := snapshot.GetGiftRank() if limit > 0 && len(rank) > limit { @@ -322,6 +449,65 @@ func roomInitialProfileUserIDs(snapshot *roomv1.RoomSnapshot, viewerUserID int64 return userIDs } +func roomOnlineUserDataFromProto(resp *roomv1.ListRoomOnlineUsersResponse, profiles map[int64]roomDisplayProfileData) roomOnlineUsersData { + if resp == nil { + return roomOnlineUsersData{} + } + items := make([]roomOnlineUserData, 0, len(resp.GetUsers())) + for _, user := range resp.GetUsers() { + item := roomOnlineUserData{ + UserID: formatOptionalUserID(user.GetUserId()), + Role: user.GetRole(), + LastSeenAtMS: user.GetLastSeenAtMs(), + } + if profile, ok := profiles[user.GetUserId()]; ok { + copied := profile + item.Profile = &copied + } + items = append(items, item) + } + return roomOnlineUsersData{ + Items: items, + Total: resp.GetTotal(), + Page: resp.GetPage(), + PageSize: resp.GetPageSize(), + ServerTimeMS: resp.GetServerTimeMs(), + } +} + +func protoRoomUserByID(snapshot *roomv1.RoomSnapshot, userID int64) *roomv1.RoomUser { + if snapshot == nil || userID <= 0 { + return nil + } + for _, user := range snapshot.GetOnlineUsers() { + if user.GetUserId() == userID { + return user + } + } + return nil +} + +func protoSeatByUserID(snapshot *roomv1.RoomSnapshot, userID int64) *roomv1.SeatState { + if snapshot == nil || userID <= 0 { + return nil + } + for _, seat := range snapshot.GetMicSeats() { + if seat.GetUserId() == userID { + return seat + } + } + return nil +} + +func containsInt64(values []int64, target int64) bool { + for _, value := range values { + if value == target { + return true + } + } + return false +} + func roomSeatCounts(snapshot *roomv1.RoomSnapshot) (int32, int32) { seatCount := int32(len(snapshot.GetMicSeats())) occupiedSeatCount := int32(0) diff --git a/services/gateway-service/internal/transport/http/router.go b/services/gateway-service/internal/transport/http/router.go index 64ff857c..0d20dbc9 100644 --- a/services/gateway-service/internal/transport/http/router.go +++ b/services/gateway-service/internal/transport/http/router.go @@ -44,6 +44,7 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { User: httproutes.UserHandlers{ ResolveDisplayUserID: h.resolveDisplayUserID, BatchUserProfiles: h.batchUserProfiles, + BatchRoomDisplayProfiles: h.batchRoomDisplayProfiles, GetMyOverview: h.getMyOverview, GetMyIdentity: h.getMyIdentity, GetMyHostIdentity: h.getMyHostIdentity, @@ -73,15 +74,20 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { ListRooms: h.listRooms, GetCurrentRoom: h.getCurrentRoom, GetRoomSnapshot: h.getRoomSnapshot, + GetRoomDetail: h.getRoomDetail, + ListRoomOnlineUsers: h.listRoomOnlineUsers, + GetRoomGiftPanel: h.getRoomGiftPanel, CreateRoom: h.createRoom, UpdateRoomProfile: h.updateRoomProfile, JoinRoom: h.joinRoom, RoomHeartbeat: h.roomHeartbeat, LeaveRoom: h.leaveRoom, + CloseRoom: h.closeRoom, MicUp: h.micUp, MicDown: h.micDown, ChangeMicSeat: h.changeMicSeat, ConfirmMicPublishing: h.confirmMicPublishing, + SetMicMute: h.setMicMute, SetMicSeatLock: h.setMicSeatLock, SetChatEnabled: h.setChatEnabled, SetRoomAdmin: h.setRoomAdmin, @@ -116,5 +122,10 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { ListVIPPackages: h.listVIPPackages, PurchaseVIP: h.purchaseVIP, }, + Game: httproutes.GameHandlers{ + ListGames: h.listGames, + LaunchGame: h.launchGame, + HandleCallback: h.handleGameCallback, + }, }) } diff --git a/services/room-service/configs/config.docker.yaml b/services/room-service/configs/config.docker.yaml index b215e1f2..d6cc94b5 100644 --- a/services/room-service/configs/config.docker.yaml +++ b/services/room-service/configs/config.docker.yaml @@ -10,7 +10,7 @@ log: grpc_addr: ":13001" lease_ttl: "10s" rank_limit: 20 -snapshot_every_n: 1 +snapshot_every_n: 10 wallet_service_addr: "wallet-service:13004" activity_service_addr: "activity-service:13006" tencent_im: @@ -35,9 +35,12 @@ presence_stale_scan_interval: "30s" mic_publish_timeout: "15s" mic_publish_scan_interval: "1s" outbox_worker: - # Docker 本地默认启动补偿 worker,验证同步 IM 失败后的 room_outbox 补投闭环。 + # Docker 本地默认启动补偿 worker,验证 IM/activity 异步投递、退避和死信闭环。 enabled: true poll_interval: "1s" batch_size: 100 publish_timeout: "3s" - retry_strategy: "fixed_interval" + retry_strategy: "exponential_backoff" + max_retry_count: 10 + initial_backoff: "5s" + max_backoff: "5m" diff --git a/services/room-service/configs/config.tencent.example.yaml b/services/room-service/configs/config.tencent.example.yaml index 4a5fa5c3..a7b0e147 100644 --- a/services/room-service/configs/config.tencent.example.yaml +++ b/services/room-service/configs/config.tencent.example.yaml @@ -10,11 +10,11 @@ log: grpc_addr: ":13001" lease_ttl: "10s" rank_limit: 20 -snapshot_every_n: 1 +snapshot_every_n: 10 wallet_service_addr: "wallet-service.internal:13004" activity_service_addr: "activity-service.internal:13006" tencent_im: - # 线上开启后,CreateRoom 会先确保腾讯云 IM 群组存在。 + # 线上开启后,腾讯云 IM 建群和房间系统消息都由 room_outbox worker 异步投递。 enabled: true # 腾讯云 IM 应用 ID,必须和 gateway-service 使用同一个 SDKAppID。 sdk_app_id: 1400000000 @@ -28,7 +28,7 @@ tencent_im: endpoint: "adminapisgp.im.qcloud.com" # 语音房默认用 ChatRoom 保留基础群历史;改 AVChatRoom 前要确认历史和人数语义。 group_type: "ChatRoom" - # 同步建群/发房间系统消息的单次公网调用超时。 + # outbox worker 单次公网调用超时。 request_timeout: "5s" mysql_dsn: "USER:PASSWORD@tcp(TENCENT_CDB_HOST:3306)/hyapp_room?parseTime=true&charset=utf8mb4&loc=UTC&tls=false" mysql_max_open_conns: 80 @@ -42,9 +42,12 @@ presence_stale_scan_interval: "30s" mic_publish_timeout: "15s" mic_publish_scan_interval: "1s" outbox_worker: - # 线上默认启动补偿 worker;腾讯云 IM 抖动时不回滚 Room Cell 状态。 + # 线上默认启动补偿 worker;腾讯云 IM/activity-service 抖动时不回滚 Room Cell 状态。 enabled: true poll_interval: "1s" batch_size: 100 publish_timeout: "3s" - retry_strategy: "fixed_interval" + retry_strategy: "exponential_backoff" + max_retry_count: 10 + initial_backoff: "5s" + max_backoff: "5m" diff --git a/services/room-service/configs/config.yaml b/services/room-service/configs/config.yaml index 974a38a0..b072cb16 100644 --- a/services/room-service/configs/config.yaml +++ b/services/room-service/configs/config.yaml @@ -9,7 +9,7 @@ log: max_payload_bytes: 2048 lease_ttl: "10s" rank_limit: 20 -snapshot_every_n: 1 +snapshot_every_n: 10 grpc_addr: ":13001" wallet_service_addr: "127.0.0.1:13004" activity_service_addr: "127.0.0.1:13006" @@ -24,7 +24,7 @@ tencent_im: endpoint: "adminapisgp.im.qcloud.com" # 语音房群类型。ChatRoom 保留基础群历史;如改 AVChatRoom,要重新评估历史能力。 group_type: "ChatRoom" - # room-service 同步建群和发送房间系统消息的单次 REST 超时。 + # outbox worker 建群和发送房间系统消息的单次 REST 超时。 request_timeout: "5s" mysql_dsn: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_room?parseTime=true&charset=utf8mb4&loc=UTC" mysql_max_open_conns: 20 @@ -38,9 +38,12 @@ presence_stale_scan_interval: "30s" mic_publish_timeout: "15s" mic_publish_scan_interval: "1s" outbox_worker: - # room_outbox 是腾讯云 IM 系统消息补偿通道;失败转为 retryable 并固定间隔重试。 + # room_outbox 是腾讯云 IM 和 activity-service 的异步投递通道;失败退避重试,超过上限转死信。 enabled: true poll_interval: "1s" batch_size: 100 publish_timeout: "3s" - retry_strategy: "fixed_interval" + retry_strategy: "exponential_backoff" + max_retry_count: 10 + initial_backoff: "5s" + max_backoff: "5m" 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 bc62b96d..ff6c359d 100644 --- a/services/room-service/deploy/mysql/initdb/001_room_service.sql +++ b/services/room-service/deploy/mysql/initdb/001_room_service.sql @@ -103,10 +103,12 @@ CREATE TABLE IF NOT EXISTS room_outbox ( envelope LONGBLOB NOT NULL, created_at_ms BIGINT NOT NULL, retry_count INT NOT NULL DEFAULT 0, + next_retry_at_ms BIGINT NOT NULL DEFAULT 0, last_error TEXT NULL, updated_at_ms BIGINT NOT NULL, PRIMARY KEY (app_code, event_id), - KEY idx_room_outbox_pending (app_code, status, created_at_ms), + KEY idx_room_outbox_pending (app_code, status, next_retry_at_ms, created_at_ms), + KEY idx_room_outbox_retry (app_code, status, next_retry_at_ms, created_at_ms), KEY idx_room_outbox_claim (app_code, status, lock_until_ms, created_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/services/room-service/internal/app/app.go b/services/room-service/internal/app/app.go index 477bc15f..dd850f2c 100644 --- a/services/room-service/internal/app/app.go +++ b/services/room-service/internal/app/app.go @@ -106,7 +106,8 @@ func New(cfg config.Config) (*App, error) { directory := router.NewRedisDirectory(redisClient) roomPublisher := integration.NewNoopRoomEventPublisher() - outboxPublisher := integration.NewNoopOutboxPublisher() + activityPublisher := integration.NewActivityOutboxPublisher(activityv1.NewRoomEventConsumerServiceClient(activityConn), time.Second) + outboxPublishers := []integration.OutboxPublisher{activityPublisher} if cfg.TencentIM.Enabled { // 腾讯云 IM 替代自研 IM;room-service 只负责服务端 REST 群消息桥接。 tencentClient, err := tencentim.NewRESTClient(cfg.TencentIM.RESTConfig()) @@ -119,8 +120,9 @@ func New(cfg config.Config) (*App, error) { } tencentPublisher := integration.NewTencentIMPublisher(tencentClient) roomPublisher = tencentPublisher - outboxPublisher = tencentPublisher + outboxPublishers = append(outboxPublishers, tencentPublisher) } + outboxPublisher := integration.NewCompositeOutboxPublisher(outboxPublishers...) // 领域服务只依赖接口,App 层负责选择 MySQL/Redis/gRPC 具体实现。 svc := roomservice.New(roomservice.Config{ @@ -131,7 +133,6 @@ func New(cfg config.Config) (*App, error) { PresenceStaleAfter: cfg.PresenceStaleAfter, MicPublishTimeout: cfg.MicPublishTimeout, }, directory, repository, integration.NewGRPCWalletClient(walletConn), roomPublisher, outboxPublisher) - svc.SetEventPublisher(integration.NewActivityOutboxPublisher(activityv1.NewRoomEventConsumerServiceClient(activityConn), time.Second)) server := grpc.NewServer(grpc.UnaryInterceptor(logx.UnaryServerInterceptor("room-service"))) // 命令服务处理房间状态变更,守卫服务给腾讯云 IM 回调或 gateway 做发言和进群校验。 @@ -196,6 +197,9 @@ func (a *App) Run() error { BatchSize: a.cfg.OutboxWorker.BatchSize, PublishTimeout: a.cfg.OutboxWorker.PublishTimeout, RetryStrategy: a.cfg.OutboxWorker.RetryStrategy, + MaxRetryCount: a.cfg.OutboxWorker.MaxRetryCount, + InitialBackoff: a.cfg.OutboxWorker.InitialBackoff, + MaxBackoff: a.cfg.OutboxWorker.MaxBackoff, }) }) } diff --git a/services/room-service/internal/config/config.go b/services/room-service/internal/config/config.go index 14cb5c1c..f04c6c3b 100644 --- a/services/room-service/internal/config/config.go +++ b/services/room-service/internal/config/config.go @@ -72,8 +72,14 @@ type OutboxWorkerConfig struct { BatchSize int `yaml:"batch_size"` // PublishTimeout 是单条 outbox 投递腾讯云 IM 的最大耗时。 PublishTimeout time.Duration `yaml:"publish_timeout"` - // RetryStrategy 当前只接受 fixed_interval,避免配置错误静默改变投递语义。 + // RetryStrategy 当前只接受 exponential_backoff,避免配置错误静默改变投递语义。 RetryStrategy string `yaml:"retry_strategy"` + // MaxRetryCount 是转入 failed 前允许的最大失败次数。 + MaxRetryCount int `yaml:"max_retry_count"` + // InitialBackoff 是首次失败后的等待时间。 + InitialBackoff time.Duration `yaml:"initial_backoff"` + // MaxBackoff 是指数退避的最大等待时间。 + MaxBackoff time.Duration `yaml:"max_backoff"` } // TencentIMConfig 描述 room-service 调用腾讯云 IM REST API 的配置。 @@ -125,7 +131,7 @@ func Default() Config { GRPCAddr: ":13001", LeaseTTL: 10 * time.Second, RankLimit: 20, - SnapshotEveryN: 1, + SnapshotEveryN: 10, WalletServiceAddr: "127.0.0.1:13004", ActivityServiceAddr: "127.0.0.1:13006", TencentIM: TencentIMConfig{ @@ -156,13 +162,16 @@ func Default() Config { } func defaultOutboxWorkerConfig() OutboxWorkerConfig { - // 默认启动补偿 worker,保证同步腾讯云 IM 失败后 pending outbox 能自动重试。 + // 默认启动补偿 worker,使用退避和死信避免外部故障刷屏。 return OutboxWorkerConfig{ Enabled: true, PollInterval: time.Second, BatchSize: 100, PublishTimeout: 3 * time.Second, - RetryStrategy: "fixed_interval", + RetryStrategy: "exponential_backoff", + MaxRetryCount: 10, + InitialBackoff: 5 * time.Second, + MaxBackoff: 5 * time.Minute, } } @@ -210,12 +219,24 @@ func normalizeOutboxWorkerConfig(cfg OutboxWorkerConfig) (OutboxWorkerConfig, er if cfg.PublishTimeout <= 0 { cfg.PublishTimeout = defaults.PublishTimeout } + if cfg.MaxRetryCount <= 0 { + cfg.MaxRetryCount = defaults.MaxRetryCount + } + if cfg.InitialBackoff <= 0 { + cfg.InitialBackoff = defaults.InitialBackoff + } + if cfg.MaxBackoff <= 0 { + cfg.MaxBackoff = defaults.MaxBackoff + } + if cfg.MaxBackoff < cfg.InitialBackoff { + cfg.MaxBackoff = cfg.InitialBackoff + } cfg.RetryStrategy = strings.TrimSpace(cfg.RetryStrategy) if cfg.RetryStrategy == "" { cfg.RetryStrategy = defaults.RetryStrategy } if cfg.RetryStrategy != defaults.RetryStrategy { - // V1 只实现 fixed_interval;未知策略直接启动失败,避免误以为退避或死信已生效。 + // 当前只实现指数退避;未知策略直接启动失败,避免误以为退避或死信已生效。 return OutboxWorkerConfig{}, fmt.Errorf("unsupported outbox_worker retry_strategy %q", cfg.RetryStrategy) } diff --git a/services/room-service/internal/config/config_test.go b/services/room-service/internal/config/config_test.go index 03c9b881..b28c3c51 100644 --- a/services/room-service/internal/config/config_test.go +++ b/services/room-service/internal/config/config_test.go @@ -28,7 +28,7 @@ func TestLoad(t *testing.T) { if cfg.WalletServiceAddr != "127.0.0.1:13004" { t.Fatalf("unexpected wallet addr: %s", cfg.WalletServiceAddr) } - if !cfg.OutboxWorker.Enabled || cfg.OutboxWorker.PollInterval != time.Second || cfg.OutboxWorker.BatchSize != 100 || cfg.OutboxWorker.PublishTimeout != 3*time.Second || cfg.OutboxWorker.RetryStrategy != "fixed_interval" { + if !cfg.OutboxWorker.Enabled || cfg.OutboxWorker.PollInterval != time.Second || cfg.OutboxWorker.BatchSize != 100 || cfg.OutboxWorker.PublishTimeout != 3*time.Second || cfg.OutboxWorker.RetryStrategy != "exponential_backoff" || cfg.OutboxWorker.MaxRetryCount != 10 || cfg.OutboxWorker.InitialBackoff != 5*time.Second || cfg.OutboxWorker.MaxBackoff != 5*time.Minute { t.Fatalf("unexpected outbox worker config: %+v", cfg.OutboxWorker) } } @@ -55,7 +55,7 @@ func TestLoadTencentExample(t *testing.T) { if cfg.TencentIM.RequestTimeout != 5*time.Second { t.Fatalf("unexpected request timeout: %s", cfg.TencentIM.RequestTimeout) } - if !cfg.OutboxWorker.Enabled || cfg.OutboxWorker.RetryStrategy != "fixed_interval" { + if !cfg.OutboxWorker.Enabled || cfg.OutboxWorker.RetryStrategy != "exponential_backoff" || cfg.OutboxWorker.MaxRetryCount != 10 { t.Fatalf("tencent example must configure outbox worker: %+v", cfg.OutboxWorker) } } @@ -75,12 +75,15 @@ func TestNormalizeOutboxWorkerKeepsSafeDefaults(t *testing.T) { cfg.OutboxWorker.BatchSize = 0 cfg.OutboxWorker.PublishTimeout = 0 cfg.OutboxWorker.RetryStrategy = "" + cfg.OutboxWorker.MaxRetryCount = 0 + cfg.OutboxWorker.InitialBackoff = 0 + cfg.OutboxWorker.MaxBackoff = 0 normalized, err := Normalize(cfg) if err != nil { t.Fatalf("Normalize failed: %v", err) } - if normalized.OutboxWorker.PollInterval != time.Second || normalized.OutboxWorker.BatchSize != 100 || normalized.OutboxWorker.PublishTimeout != 3*time.Second || normalized.OutboxWorker.RetryStrategy != "fixed_interval" { + if normalized.OutboxWorker.PollInterval != time.Second || normalized.OutboxWorker.BatchSize != 100 || normalized.OutboxWorker.PublishTimeout != 3*time.Second || normalized.OutboxWorker.RetryStrategy != "exponential_backoff" || normalized.OutboxWorker.MaxRetryCount != 10 || normalized.OutboxWorker.InitialBackoff != 5*time.Second || normalized.OutboxWorker.MaxBackoff != 5*time.Minute { t.Fatalf("outbox worker defaults not restored: %+v", normalized.OutboxWorker) } } diff --git a/services/room-service/internal/integration/clients.go b/services/room-service/internal/integration/clients.go index 766656c2..402a7da0 100644 --- a/services/room-service/internal/integration/clients.go +++ b/services/room-service/internal/integration/clients.go @@ -15,11 +15,11 @@ type WalletClient interface { DebitGift(ctx context.Context, req *walletv1.DebitGiftRequest) (*walletv1.DebitGiftResponse, error) } -// RoomEventPublisher 抽象 room-service 对腾讯云 IM 的同步房间消息桥接。 +// RoomEventPublisher 保留腾讯云 IM 直接桥接能力;房间命令主链路不再调用它。 type RoomEventPublisher interface { // EnsureRoomGroup 确保房间在腾讯云 IM 中有对应群组。 EnsureRoomGroup(ctx context.Context, roomID string, ownerUserID int64) error - // PublishRoomEvent 把房间系统事件同步推给腾讯云 IM,用于低时延广播。 + // PublishRoomEvent 把房间系统事件推给腾讯云 IM,当前只供异步边界复用。 PublishRoomEvent(ctx context.Context, event tencentim.RoomEvent) error } diff --git a/services/room-service/internal/integration/outbox_composite.go b/services/room-service/internal/integration/outbox_composite.go new file mode 100644 index 00000000..2257517a --- /dev/null +++ b/services/room-service/internal/integration/outbox_composite.go @@ -0,0 +1,45 @@ +package integration + +import ( + "context" + "errors" + + roomeventsv1 "hyapp.local/api/proto/events/room/v1" +) + +// CompositeOutboxPublisher fans one committed room outbox event to every +// asynchronous consumer. Consumers must be idempotent by event_id because a +// partial failure retries the full fanout on the next worker pass. +type CompositeOutboxPublisher struct { + publishers []OutboxPublisher +} + +// NewCompositeOutboxPublisher removes nil publishers and returns a no-op when +// no asynchronous consumers are configured. +func NewCompositeOutboxPublisher(publishers ...OutboxPublisher) OutboxPublisher { + filtered := make([]OutboxPublisher, 0, len(publishers)) + for _, publisher := range publishers { + if publisher != nil { + filtered = append(filtered, publisher) + } + } + if len(filtered) == 0 { + return NewNoopOutboxPublisher() + } + return &CompositeOutboxPublisher{publishers: filtered} +} + +// PublishOutboxEvent calls every configured consumer before returning. Returning +// a joined error keeps the event retryable until every consumer has accepted it. +func (p *CompositeOutboxPublisher) PublishOutboxEvent(ctx context.Context, envelope *roomeventsv1.EventEnvelope) error { + if p == nil || len(p.publishers) == 0 { + return nil + } + var joined error + for _, publisher := range p.publishers { + if err := publisher.PublishOutboxEvent(ctx, envelope); err != nil { + joined = errors.Join(joined, err) + } + } + return joined +} diff --git a/services/room-service/internal/integration/tencent_im.go b/services/room-service/internal/integration/tencent_im.go index 9dfb4743..032609c0 100644 --- a/services/room-service/internal/integration/tencent_im.go +++ b/services/room-service/internal/integration/tencent_im.go @@ -45,6 +45,14 @@ func (p *TencentIMPublisher) PublishOutboxEvent(ctx context.Context, envelope *r if p == nil || p.client == nil || envelope == nil { return nil } + if envelope.GetEventType() == "RoomCreated" { + var body roomeventsv1.RoomCreated + if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil { + return err + } + // 建群是腾讯 IM 的外部副作用,必须留在 outbox worker 内,不能阻塞 CreateRoom 返回。 + return p.client.EnsureRoomGroup(ctx, envelope.GetRoomId(), body.GetOwnerUserId()) + } event, publish, err := roomEventFromEnvelope(envelope) if err != nil || !publish { @@ -89,7 +97,7 @@ func NewNoopOutboxPublisher() OutboxPublisher { // PublishOutboxEvent 直接吞掉 outbox 事件。 func (noopOutboxPublisher) PublishOutboxEvent(context.Context, *roomeventsv1.EventEnvelope) error { - // 腾讯云 IM 同步广播不依赖该 outbox;activity/audit 仍会复用同一事件源。 + // room_outbox worker 会把同一事件源异步投给腾讯云 IM、activity 或后续审计消费者。 return nil } @@ -141,6 +149,7 @@ func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.Room "reason": body.GetReason(), "publish_deadline_ms": fmt.Sprintf("%d", body.GetPublishDeadlineMs()), "publish_event_time_ms": fmt.Sprintf("%d", body.GetPublishEventTimeMs()), + "mic_muted": fmt.Sprintf("%t", body.GetMicMuted()), } return base, true, nil case "RoomMicSeatLocked": diff --git a/services/room-service/internal/room/command/command.go b/services/room-service/internal/room/command/command.go index e072fc09..5cbef080 100644 --- a/services/room-service/internal/room/command/command.go +++ b/services/room-service/internal/room/command/command.go @@ -177,6 +177,18 @@ type ConfirmMicPublishing struct { // Type 返回命令类型。 func (ConfirmMicPublishing) Type() string { return "confirm_mic_publishing" } +// SetMicMute 定义麦克风静音状态同步请求。 +type SetMicMute struct { + Base + // TargetUserID 是被修改麦克风静音态的麦上用户。 + TargetUserID int64 `json:"target_user_id"` + // Muted 为 true 表示麦克风静音,false 表示取消静音。 + Muted bool `json:"muted"` +} + +// Type 返回命令类型。 +func (SetMicMute) Type() string { return "set_mic_mute" } + // ApplyRTCEvent 定义由可信腾讯 RTC 服务端回调驱动的房间状态事件。 type ApplyRTCEvent struct { Base @@ -288,6 +300,10 @@ func (UnbanUser) Type() string { return "unban_user" } // SendGift 定义送礼请求。 type SendGift struct { Base + // TargetType 当前只支持 user;保留字段避免后续 all_mic/all_room/couple 再改命令结构。 + TargetType string `json:"target_type"` + // TargetUserIDs 是新协议目标用户集合;当前 target_type=user 时只允许一个用户。 + TargetUserIDs []int64 `json:"target_user_ids,omitempty"` // TargetUserID 是礼物接收方,当前版本要求接收方在房间内。 TargetUserID int64 `json:"target_user_id"` // GiftID 是礼物配置标识,具体配置有效性当前由调用方和 wallet 链路保证。 @@ -370,6 +386,8 @@ func Deserialize(commandType string, payload []byte) (Command, error) { cmd = &MicDown{} case ConfirmMicPublishing{}.Type(): cmd = &ConfirmMicPublishing{} + case SetMicMute{}.Type(): + cmd = &SetMicMute{} case ApplyRTCEvent{}.Type(): cmd = &ApplyRTCEvent{} case ChangeMicSeat{}.Type(): diff --git a/services/room-service/internal/room/outbox/outbox.go b/services/room-service/internal/room/outbox/outbox.go index 6162ddc8..e36feb1e 100644 --- a/services/room-service/internal/room/outbox/outbox.go +++ b/services/room-service/internal/room/outbox/outbox.go @@ -47,6 +47,8 @@ type Record struct { WorkerID string // LockUntilMS 是 delivering 锁的截止时间。 LockUntilMS int64 + // NextRetryAtMS 是 retryable 事件下一次允许抢占的 UTC 毫秒时间。 + NextRetryAtMS int64 } // Build 把具体事件体封装成统一 outbox 记录。 diff --git a/services/room-service/internal/room/service/create_room.go b/services/room-service/internal/room/service/create_room.go index 2eeda384..9f628c98 100644 --- a/services/room-service/internal/room/service/create_room.go +++ b/services/room-service/internal/room/service/create_room.go @@ -3,11 +3,14 @@ package service import ( "context" "fmt" + "log/slog" "strings" + "time" "unicode/utf8" roomeventsv1 "hyapp.local/api/proto/events/room/v1" roomv1 "hyapp.local/api/proto/room/v1" + "hyapp/pkg/logx" "hyapp/pkg/roomid" "hyapp/pkg/xerr" "hyapp/services/room-service/internal/room/cell" @@ -43,6 +46,13 @@ type roomProfileInput struct { // CreateRoom 创建一个新的房间执行单元,并把首个快照和事件落盘。 func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest) (*roomv1.CreateRoomResponse, error) { ctx = contextFromMeta(ctx, req.GetMeta()) + startedAt := time.Now() + var idempotencyMS int64 + var leaseMS int64 + var saveMutationMS int64 + var snapshotMS int64 + var projectionMS int64 + var saveRoomMetaMS int64 if req.GetMeta() == nil || req.GetMeta().GetRoomId() == "" { // 创建房间必须指定 room_id,后续 lease、meta、snapshot 都依赖它。 return nil, xerr.New(xerr.InvalidArgument, "room_id is required") @@ -87,23 +97,23 @@ func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest) RoomShortID: normalizeRoomShortID(req.GetRoomShortId(), actorUserID), } + idempotencyStartedAt := time.Now() payload, seen, err := s.commandPayloadForIdempotency(ctx, cmd) + idempotencyMS = elapsedMS(idempotencyStartedAt) if err != nil { return nil, err } if seen { - // CreateRoom 幂等重试也要确保腾讯云 IM 群组存在,避免上次创建在外部桥接处失败。 - if err := s.syncPublisher.EnsureRoomGroup(ctx, cmd.RoomID(), cmd.OwnerUserID); err != nil { - return nil, err - } - // 重复 CreateRoom 不再写入状态,直接返回当前快照保证命令幂等。 snapshot, err := s.currentSnapshot(ctx, cmd.RoomID()) if err != nil { return nil, err } // CreateRoom 不经过 mutateRoom 管线,幂等重试也要补齐恢复读模型,避免旧请求成功但投影缺失。 + projectionStartedAt := time.Now() s.projectRoomPresenceBestEffort(ctx, snapshot) + projectionMS = elapsedMS(projectionStartedAt) + logRoomCommandTiming(ctx, cmd.RoomID(), cmd.ID(), cmd.Type(), false, elapsedMS(startedAt), idempotencyMS, 0, 0, 0, projectionMS, 0, 0, 0, false, false) return &roomv1.CreateRoomResponse{ Result: commandResult(false, snapshot.GetVersion(), s.clock.Now()), @@ -112,7 +122,9 @@ func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest) } // 创建房间前先获得 owner lease,避免两个节点同时创建同一个 room_id。 + leaseStartedAt := time.Now() lease, err := s.directory.EnsureOwner(ctx, runtimeRoomKeyFromContext(ctx, cmd.RoomID()), s.nodeID, s.clock.Now(), s.leaseTTL) + leaseMS = elapsedMS(leaseStartedAt) if err != nil { return nil, err } @@ -135,10 +147,6 @@ func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest) return nil, xerr.New(xerr.Conflict, fmt.Sprintf("owner already has room: %s", existing.RoomID)) } - if err := s.syncPublisher.EnsureRoomGroup(ctx, cmd.RoomID(), cmd.OwnerUserID); err != nil { - // 房间创建必须先保证外部群组可用。 - return nil, err - } if err := s.ensureLeaseStillOwned(ctx, cmd.RoomID(), lease); err != nil { return nil, err } @@ -147,6 +155,7 @@ func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest) now := s.clock.Now() roomState := state.NewRoomState(cmd.RoomID(), cmd.OwnerUserID, cmd.HostUserID, cmd.SeatCount, cmd.Mode) roomState.Version = 1 + roomState.VisibleRegionID = cmd.VisibleRegionID applyRoomProfileExt(roomState, roomProfileInput{ Name: cmd.RoomName, Avatar: cmd.RoomAvatar, @@ -175,6 +184,7 @@ func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest) } // 首次创建按 meta -> command log -> outbox -> snapshot 的顺序落盘,保证恢复有基础元数据和首版本命令。 + saveMetaStartedAt := time.Now() if err := s.repository.SaveRoomMeta(ctx, RoomMeta{ AppCode: cmd.AppCode, RoomID: cmd.RoomID(), @@ -188,7 +198,9 @@ func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest) }); err != nil { return nil, err } + saveRoomMetaMS = elapsedMS(saveMetaStartedAt) + saveStartedAt := time.Now() if err := s.repository.SaveMutation(ctx, MutationCommit{ Command: CommandRecord{ AppCode: cmd.AppCode, @@ -206,17 +218,25 @@ func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest) }); err != nil { return nil, err } + saveMutationMS = elapsedMS(saveStartedAt) snapshot := snapshotWithApp(ctx, roomState.ToProto()) + snapshotStartedAt := time.Now() if err := s.persistSnapshot(ctx, snapshot); err != nil { return nil, err } + snapshotMS = elapsedMS(snapshotStartedAt) + projectionStartedAt := time.Now() s.projectRoomListBestEffort(ctx, snapshot) // owner 创建房间后已经是在线成员,必须立即投影到用户当前房间读模型。 s.projectRoomPresenceBestEffort(ctx, snapshot) + projectionMS = elapsedMS(projectionStartedAt) // 持久化成功后再安装内存 Cell,避免内存有房间但恢复来源不完整。 s.installCell(ctx, cmd.RoomID(), cell.New(roomState.Clone(), rank.FromState(roomState.GiftRank, s.rankLimit))) + logRoomCommandTiming(ctx, cmd.RoomID(), cmd.ID(), cmd.Type(), true, elapsedMS(startedAt), idempotencyMS, leaseMS, saveMutationMS, snapshotMS, projectionMS, 0, 0, 0, true, true, + slog.Int64("save_room_meta_ms", saveRoomMetaMS), + ) return &roomv1.CreateRoomResponse{ Result: commandResult(true, snapshot.GetVersion(), now), @@ -224,6 +244,28 @@ func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest) }, nil } +func logRoomCommandTiming(ctx context.Context, roomID string, commandID string, commandType string, applied bool, totalMS int64, idempotencyMS int64, leaseMS int64, saveMutationMS int64, snapshotMS int64, projectionMS int64, walletDebitMS int64, activityMS int64, tencentIMMS int64, activityAsyncOutbox bool, tencentIMAsyncOutbox bool, extra ...slog.Attr) { + attrs := []slog.Attr{ + slog.String("room_id", roomID), + slog.String("command_id", commandID), + slog.String("command_type", commandType), + slog.Bool("applied", applied), + slog.Int64("total_ms", totalMS), + slog.Int64("idempotency_ms", idempotencyMS), + slog.Int64("lease_ms", leaseMS), + slog.Int64("save_mutation_ms", saveMutationMS), + slog.Int64("snapshot_ms", snapshotMS), + slog.Int64("projection_ms", projectionMS), + slog.Int64("wallet_debit_ms", walletDebitMS), + slog.Int64("activity_ms", activityMS), + slog.Int64("tencent_im_ms", tencentIMMS), + slog.Bool("activity_async_outbox", activityAsyncOutbox), + slog.Bool("tencent_im_async_outbox", tencentIMAsyncOutbox), + } + attrs = append(attrs, extra...) + logx.Info(ctx, "room_command_timing", attrs...) +} + func normalizeRoomShortID(raw string, actorUserID int64) string { if value := strings.TrimSpace(raw); value != "" { return value diff --git a/services/room-service/internal/room/service/gift.go b/services/room-service/internal/room/service/gift.go index a9779439..3e7bcdf7 100644 --- a/services/room-service/internal/room/service/gift.go +++ b/services/room-service/internal/room/service/gift.go @@ -3,6 +3,7 @@ package service import ( "context" "fmt" + "strings" "time" roomeventsv1 "hyapp.local/api/proto/events/room/v1" @@ -22,10 +23,17 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r ctx = contextFromMeta(ctx, req.GetMeta()) // SendGift 的账务不在 room-service 内结算,但房间表现、热度和榜单由 Room Cell 同步更新。 cmd := command.SendGift{ - Base: baseFromMeta(req.GetMeta()), - TargetUserID: req.GetTargetUserId(), - GiftID: req.GetGiftId(), - GiftCount: req.GetGiftCount(), + Base: baseFromMeta(req.GetMeta()), + TargetType: normalizeGiftTargetType(req.GetTargetType()), + TargetUserIDs: normalizeGiftTargetUserIDs(req.GetTargetUserId(), req.GetTargetUserIds()), + GiftID: req.GetGiftId(), + GiftCount: req.GetGiftCount(), + } + if cmd.TargetType == "" { + cmd.TargetType = "user" + } + if cmd.TargetType == "user" && len(cmd.TargetUserIDs) == 1 { + cmd.TargetUserID = cmd.TargetUserIDs[0] } result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, localRank *rank.LocalRank) (mutationResult, []outbox.Record, error) { @@ -34,6 +42,13 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r return mutationResult{}, nil, xerr.New(xerr.NotFound, "sender not in room") } + if cmd.TargetType != "user" { + // v1 HTTP 契约已预留 all_mic/all_room/couple;账务拆单和房间表现策略补齐前先 fail-close。 + return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "target_type is unsupported") + } + if len(cmd.TargetUserIDs) != 1 || cmd.TargetUserID <= 0 { + return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "target_user_ids must contain one user") + } if _, exists := current.OnlineUsers[cmd.TargetUserID]; !exists { // v1 要求礼物目标在房间内,避免给不存在的房间表现目标计榜。 return mutationResult{}, nil, xerr.New(xerr.NotFound, "target not in room") @@ -52,6 +67,7 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r } // 钱包扣费在房间状态变更前完成;失败时不写 command log、不进入 Room Cell 提交态。 + walletStartedAt := time.Now() billing, err := s.wallet.DebitGift(ctx, &walletv1.DebitGiftRequest{ CommandId: cmd.ID(), RoomId: cmd.RoomID(), @@ -62,6 +78,7 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r AppCode: appcode.FromContext(ctx), RegionId: roomMeta.VisibleRegionID, }) + walletDebitMS := elapsedMS(walletStartedAt) if err != nil { return mutationResult{}, nil, err } @@ -124,6 +141,7 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r roomHeat: current.Heat, giftRank: cloneProtoRank(current.GiftRank), commandPayload: commandPayload, + walletDebitMS: walletDebitMS, syncEvent: &tencentim.RoomEvent{ // 同步广播只选 GiftSent 主事件作为客户端房间系统消息,Heat/Rank 通过字段携带。 EventID: giftEvent.EventID, @@ -158,6 +176,32 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r }, nil } +func normalizeGiftTargetType(raw string) string { + raw = strings.TrimSpace(raw) + switch raw { + case "", "user", "all_mic", "all_room", "couple": + return raw + default: + return raw + } +} + +func normalizeGiftTargetUserIDs(targetUserID int64, targetUserIDs []int64) []int64 { + seen := make(map[int64]bool, len(targetUserIDs)+1) + ids := make([]int64, 0, len(targetUserIDs)+1) + for _, userID := range targetUserIDs { + if userID <= 0 || seen[userID] { + continue + } + seen[userID] = true + ids = append(ids, userID) + } + if len(ids) == 0 && targetUserID > 0 { + ids = append(ids, targetUserID) + } + return ids +} + func findRankItem(items []state.RankItem, userID int64) state.RankItem { // 送礼后通常能找到发送方榜单项;找不到时返回 user_id 占位避免空事件。 for _, item := range items { diff --git a/services/room-service/internal/room/service/mic.go b/services/room-service/internal/room/service/mic.go index 7d16107f..8fdc961f 100644 --- a/services/room-service/internal/room/service/mic.go +++ b/services/room-service/internal/room/service/mic.go @@ -281,6 +281,7 @@ func (s *Service) ChangeMicSeat(ctx context.Context, req *roomv1.ChangeMicSeatRe current.MicSeats[toIndex].PublishDeadlineMS = movedSeat.PublishDeadlineMS current.MicSeats[toIndex].MicSessionRoomVersion = movedSeat.MicSessionRoomVersion current.MicSeats[toIndex].LastPublishEventTimeMS = movedSeat.LastPublishEventTimeMS + current.MicSeats[toIndex].MicMuted = movedSeat.MicMuted current.Version++ micEvent, err := outbox.Build(current.RoomID, "RoomMicChanged", current.Version, now, &roomeventsv1.RoomMicChanged{ @@ -429,6 +430,94 @@ func (s *Service) ConfirmMicPublishing(ctx context.Context, req *roomv1.ConfirmM }, nil } +// SetMicMute 修改麦位上的服务端可见静音态。 +func (s *Service) SetMicMute(ctx context.Context, req *roomv1.SetMicMuteRequest) (*roomv1.SetMicMuteResponse, error) { + ctx = contextFromMeta(ctx, req.GetMeta()) + cmd := command.SetMicMute{ + Base: baseFromMeta(req.GetMeta()), + TargetUserID: req.GetTargetUserId(), + Muted: req.GetMuted(), + } + if cmd.TargetUserID == 0 { + cmd.TargetUserID = cmd.ActorUserID() + } + + result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { + if err := requireActiveRoom(current); err != nil { + return mutationResult{}, nil, err + } + if cmd.TargetUserID <= 0 { + return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "target_user_id is required") + } + if err := requireActorPresent(current, cmd.ActorUserID()); err != nil { + return mutationResult{}, nil, err + } + if err := canSelfOrManageTarget(current, cmd.ActorUserID(), cmd.TargetUserID); err != nil { + return mutationResult{}, nil, err + } + + seat, exists := current.SeatByUser(cmd.TargetUserID) + if !exists { + return mutationResult{}, nil, xerr.New(xerr.NotFound, "target not on seat") + } + if seat.MicMuted == cmd.Muted { + return mutationResult{snapshot: current.ToProto(), seatNo: seat.SeatNo}, nil, nil + } + + index := current.SeatIndex(seat.SeatNo) + current.MicSeats[index].MicMuted = cmd.Muted + current.Version++ + + action := "mic_unmute" + if cmd.Muted { + action = "mic_mute" + } + micEvent, err := outbox.Build(current.RoomID, "RoomMicChanged", current.Version, now, &roomeventsv1.RoomMicChanged{ + ActorUserId: cmd.ActorUserID(), + TargetUserId: cmd.TargetUserID, + FromSeat: seat.SeatNo, + ToSeat: seat.SeatNo, + Action: action, + MicSessionId: seat.MicSessionID, + PublishState: seat.PublishState, + PublishDeadlineMs: seat.PublishDeadlineMS, + MicMuted: cmd.Muted, + }) + if err != nil { + return mutationResult{}, nil, err + } + + return mutationResult{ + snapshot: current.ToProto(), + seatNo: seat.SeatNo, + syncEvent: &tencentim.RoomEvent{ + EventID: micEvent.EventID, + RoomID: current.RoomID, + EventType: "room_mic_mute_changed", + ActorUserID: cmd.ActorUserID(), + TargetUserID: cmd.TargetUserID, + SeatNo: seat.SeatNo, + RoomVersion: current.Version, + Attributes: map[string]string{ + "action": action, + "mic_session_id": seat.MicSessionID, + "publish_state": seat.PublishState, + "mic_muted": boolString(cmd.Muted), + }, + }, + }, []outbox.Record{micEvent}, nil + }) + if err != nil { + return nil, err + } + + return &roomv1.SetMicMuteResponse{ + Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()), + SeatNo: result.seatNo, + Room: result.snapshot, + }, nil +} + func micSessionIDForCommand(roomID string, commandID string) string { sum := sha256.Sum256([]byte(roomID + "\x00" + commandID)) return "mic_" + hex.EncodeToString(sum[:16]) diff --git a/services/room-service/internal/room/service/online_users_query.go b/services/room-service/internal/room/service/online_users_query.go new file mode 100644 index 00000000..fd7198b3 --- /dev/null +++ b/services/room-service/internal/room/service/online_users_query.go @@ -0,0 +1,76 @@ +package service + +import ( + "context" + + roomv1 "hyapp.local/api/proto/room/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/roomid" + "hyapp/pkg/xerr" +) + +const ( + defaultRoomOnlineUsersPageSize = 50 + maxRoomOnlineUsersPageSize = 100 +) + +// ListRoomOnlineUsers 分页读取房间在线用户列表。 +// 该接口用于右上角在线人数弹窗,不能让客户端用完整 RoomSnapshot 做分页。 +func (s *Service) ListRoomOnlineUsers(ctx context.Context, req *roomv1.ListRoomOnlineUsersRequest) (*roomv1.ListRoomOnlineUsersResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + roomID := req.GetRoomId() + viewerUserID := req.GetViewerUserId() + if !roomid.ValidStringID(roomID) { + return nil, xerr.New(xerr.InvalidArgument, "room_id is invalid") + } + if viewerUserID <= 0 { + return nil, xerr.New(xerr.InvalidArgument, "viewer_user_id is required") + } + + presence, exists, err := s.repository.GetCurrentRoomPresence(ctx, viewerUserID) + if err != nil { + return nil, err + } + if !exists || presence.RoomID != roomID || presence.Status != roomPresenceStatusActive { + return nil, xerr.New(xerr.PermissionDenied, "viewer is not in room") + } + + page := int(req.GetPage()) + if page <= 0 { + page = 1 + } + pageSize := int(req.GetPageSize()) + if pageSize <= 0 { + pageSize = defaultRoomOnlineUsersPageSize + } + if pageSize > maxRoomOnlineUsersPageSize { + pageSize = maxRoomOnlineUsersPageSize + } + + result, err := s.repository.ListRoomOnlineUsers(ctx, RoomOnlineUserQuery{ + AppCode: appcode.FromContext(ctx), + RoomID: roomID, + Page: page, + PageSize: pageSize, + }) + if err != nil { + return nil, err + } + + users := make([]*roomv1.RoomUser, 0, len(result.Users)) + for _, user := range result.Users { + users = append(users, &roomv1.RoomUser{ + UserId: user.UserID, + Role: user.Role, + LastSeenAtMs: user.LastSeenAtMS, + }) + } + + return &roomv1.ListRoomOnlineUsersResponse{ + Users: users, + Total: result.Total, + Page: int32(result.Page), + PageSize: int32(result.PageSize), + ServerTimeMs: s.clock.Now().UnixMilli(), + }, nil +} diff --git a/services/room-service/internal/room/service/outbox_worker.go b/services/room-service/internal/room/service/outbox_worker.go index e22f5589..3557e8cf 100644 --- a/services/room-service/internal/room/service/outbox_worker.go +++ b/services/room-service/internal/room/service/outbox_worker.go @@ -9,11 +9,12 @@ import ( "hyapp/pkg/appcode" "hyapp/pkg/logx" + "hyapp/services/room-service/internal/room/outbox" ) const ( - // outboxRetryFixedInterval 是 V1 唯一支持的补偿策略,失败记录下一轮继续扫描。 - outboxRetryFixedInterval = "fixed_interval" + // outboxRetryExponentialBackoff 是房间事件补偿的默认策略,避免外部故障时每秒刷同一条错误。 + outboxRetryExponentialBackoff = "exponential_backoff" // outboxWorkerName 固定写入日志,便于后续按 worker 聚合检索。 outboxWorkerName = "room_outbox" ) @@ -26,17 +27,26 @@ type OutboxWorkerOptions struct { BatchSize int // PublishTimeout 限制单条外部投递耗时,shutdown 时当前事件最多阻塞该时长。 PublishTimeout time.Duration - // RetryStrategy 当前只支持 fixed_interval。 + // RetryStrategy 当前只支持 exponential_backoff。 RetryStrategy string + // MaxRetryCount 是转入 failed 前允许的最大失败次数。 + MaxRetryCount int + // InitialBackoff 是首次失败后的等待时间。 + InitialBackoff time.Duration + // MaxBackoff 是指数退避上限。 + MaxBackoff time.Duration } func defaultOutboxWorkerOptions() OutboxWorkerOptions { - // 默认值对应文档里的 V1 固定间隔补偿策略,优先避免 busy loop 和无限外部调用。 + // 默认值优先避免 busy loop、无限重试和日志污染。 return OutboxWorkerOptions{ PollInterval: time.Second, BatchSize: 100, PublishTimeout: 3 * time.Second, - RetryStrategy: outboxRetryFixedInterval, + RetryStrategy: outboxRetryExponentialBackoff, + MaxRetryCount: 10, + InitialBackoff: 5 * time.Second, + MaxBackoff: 5 * time.Minute, } } @@ -55,9 +65,21 @@ func normalizeOutboxWorkerOptions(options OutboxWorkerOptions) OutboxWorkerOptio // 单条外部投递必须有 deadline,shutdown 才能等待有界。 options.PublishTimeout = defaults.PublishTimeout } + if options.MaxRetryCount <= 0 { + options.MaxRetryCount = defaults.MaxRetryCount + } + if options.InitialBackoff <= 0 { + options.InitialBackoff = defaults.InitialBackoff + } + if options.MaxBackoff <= 0 { + options.MaxBackoff = defaults.MaxBackoff + } + if options.MaxBackoff < options.InitialBackoff { + options.MaxBackoff = options.InitialBackoff + } options.RetryStrategy = strings.TrimSpace(options.RetryStrategy) if options.RetryStrategy == "" { - // 空策略按 fixed_interval 处理,保持和配置默认值一致。 + // 空策略按指数退避处理,保持和配置默认值一致。 options.RetryStrategy = defaults.RetryStrategy } @@ -67,7 +89,7 @@ func normalizeOutboxWorkerOptions(options OutboxWorkerOptions) OutboxWorkerOptio // RunOutboxWorker 持续扫描 pending outbox;它不执行房间命令,只投递已持久化事件。 func (s *Service) RunOutboxWorker(ctx context.Context, options OutboxWorkerOptions) { options = normalizeOutboxWorkerOptions(options) - if options.RetryStrategy != outboxRetryFixedInterval { + if options.RetryStrategy != outboxRetryExponentialBackoff { // 配置层会拒绝未知策略;这里兜底避免测试或手写构造静默改变语义。 logx.Error(ctx, "worker_stopped", fmt.Errorf("unsupported retry_strategy"), slog.String("worker", outboxWorkerName), slog.String("node_id", s.nodeID)) return @@ -95,7 +117,7 @@ func (s *Service) RunOutboxWorker(ctx context.Context, options OutboxWorkerOptio // ProcessPendingOutbox 把待投递事件补偿性地推送给异步消费者。 func (s *Service) ProcessPendingOutbox(ctx context.Context, options OutboxWorkerOptions) error { options = normalizeOutboxWorkerOptions(options) - if options.RetryStrategy != outboxRetryFixedInterval { + if options.RetryStrategy != outboxRetryExponentialBackoff { return fmt.Errorf("unsupported outbox retry_strategy %q", options.RetryStrategy) } @@ -111,14 +133,34 @@ func (s *Service) ProcessPendingOutbox(ctx context.Context, options OutboxWorker return ctx.Err() default: } + if record.RetryCount >= options.MaxRetryCount { + // 已达到最大失败次数的历史事件直接转死信,防止每轮 worker 继续打外部系统。 + markCtx, markCancel := context.WithTimeout(appcode.WithContext(context.Background(), record.AppCode), options.PublishTimeout) + markErr := s.repository.MarkOutboxDead(markCtx, record.EventID, deadLetterReason(record)) + markCancel() + if markErr != nil { + return markErr + } + logx.Error(ctx, "outbox_dead_letter", nil, + slog.String("worker", outboxWorkerName), + slog.String("node_id", s.nodeID), + slog.String("event_id", record.EventID), + slog.String("event_type", record.EventType), + slog.String("room_id", record.RoomID), + slog.Int("retry_count", record.RetryCount), + slog.String("last_error", trimOutboxError(record.LastError)), + ) + continue + } publishCtx, cancel := context.WithTimeout(context.Background(), options.PublishTimeout) err := s.outboxPublisher.PublishOutboxEvent(publishCtx, record.Envelope) cancel() if err != nil { - // 投递失败转为 retryable,并记录错误和重试次数,等待下一轮 worker 再抢占。 + // 投递失败转为 retryable,并写入指数退避时间,等待下一轮 worker 到期后再抢占。 + nextRetryAtMS := time.Now().UTC().Add(outboxBackoff(record.RetryCount+1, options)).UnixMilli() markCtx, markCancel := context.WithTimeout(appcode.WithContext(context.Background(), record.AppCode), options.PublishTimeout) - markErr := s.repository.MarkOutboxFailed(markCtx, record.EventID, trimOutboxError(err.Error())) + markErr := s.repository.MarkOutboxRetryable(markCtx, record.EventID, trimOutboxError(err.Error()), nextRetryAtMS) markCancel() if markErr != nil { return markErr @@ -131,6 +173,7 @@ func (s *Service) ProcessPendingOutbox(ctx context.Context, options OutboxWorker slog.String("event_type", record.EventType), slog.String("room_id", record.RoomID), slog.Int("retry_count", record.RetryCount+1), + slog.Int64("next_retry_at_ms", nextRetryAtMS), slog.String("error", trimOutboxError(err.Error())), ) continue @@ -157,6 +200,30 @@ func (s *Service) ProcessPendingOutbox(ctx context.Context, options OutboxWorker return nil } +func outboxBackoff(retryCount int, options OutboxWorkerOptions) time.Duration { + if retryCount <= 1 { + return options.InitialBackoff + } + backoff := options.InitialBackoff + for i := 1; i < retryCount; i++ { + if backoff >= options.MaxBackoff/2 { + return options.MaxBackoff + } + backoff *= 2 + } + if backoff > options.MaxBackoff { + return options.MaxBackoff + } + return backoff +} + +func deadLetterReason(record outbox.Record) string { + if strings.TrimSpace(record.LastError) == "" { + return "outbox retry limit exceeded" + } + return trimOutboxError("outbox retry limit exceeded: " + record.LastError) +} + func trimOutboxError(value string) string { // last_error 会写入 MySQL 和日志,先去掉首尾空白避免不可见差异影响排障。 value = strings.TrimSpace(value) diff --git a/services/room-service/internal/room/service/pipeline.go b/services/room-service/internal/room/service/pipeline.go index cf655344..272ff98d 100644 --- a/services/room-service/internal/room/service/pipeline.go +++ b/services/room-service/internal/room/service/pipeline.go @@ -3,11 +3,9 @@ package service import ( "bytes" "context" - "log/slog" "time" "hyapp/pkg/appcode" - "hyapp/pkg/logx" "hyapp/pkg/xerr" "hyapp/services/room-service/internal/room/command" "hyapp/services/room-service/internal/room/outbox" @@ -17,6 +15,12 @@ import ( ) 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() + var idempotencyMS int64 + var leaseMS int64 + var saveMutationMS int64 + var snapshotMS int64 + var projectionMS int64 if cmd.RoomID() == "" || cmd.ID() == "" || cmd.ActorUserID() == 0 { // command meta 是幂等、路由、审计和权限判断的共同基础,缺一不可。 return mutationResult{}, xerr.New(xerr.InvalidArgument, "command meta is incomplete") @@ -26,7 +30,9 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl return mutationResult{}, xerr.New(xerr.Unavailable, "room service is draining") } + idempotencyStartedAt := time.Now() payload, seen, err := s.commandPayloadForIdempotency(ctx, cmd) + idempotencyMS = elapsedMS(idempotencyStartedAt) if err != nil { return mutationResult{}, err } @@ -41,7 +47,9 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl } // 确保当前节点持有房间 lease,并在没有内存 Cell 时完成恢复。 + leaseStartedAt := time.Now() roomCell, lease, err := s.ensureCell(ctx, cmd.RoomID()) + leaseMS = elapsedMS(leaseStartedAt) if err != nil { return mutationResult{}, err } @@ -69,6 +77,7 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl if err := s.ensureLeaseStillOwned(ctx, cmd.RoomID(), lease); err != nil { return nil, err } + saveStartedAt := time.Now() if err := s.repository.SaveMutation(ctx, MutationCommit{ Command: CommandRecord{ AppCode: appcode.FromContext(ctx), @@ -88,7 +97,8 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl }); err != nil { return nil, err } - // 保存已提交 outbox 记录,出 Cell 锁后再通知 activity-service;外部服务慢调用不能占住房间串行执行队列。 + saveMutationMS += elapsedMS(saveStartedAt) + // 保存已提交 outbox 记录,出 Cell 锁后由 outbox worker 异步投递 IM/activity,避免外部慢调用占住房间串行执行队列。 result.outboxRecords = outboxRecords // 持久化成功后再替换内存状态,避免内存提交领先于恢复来源。 @@ -102,6 +112,7 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl if err := s.ensureLeaseStillOwned(ctx, cmd.RoomID(), lease); err != nil { return nil, err } + saveStartedAt := time.Now() if err := s.repository.SaveMutation(ctx, MutationCommit{ Command: CommandRecord{ AppCode: appcode.FromContext(ctx), @@ -118,6 +129,7 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl }); err != nil { return nil, err } + saveMutationMS += elapsedMS(saveStartedAt) } return result, nil @@ -130,61 +142,26 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl if result.applied && shouldSnapshot(result.snapshot, s.snapshotEveryN) { // 快照失败时返回错误,因为没有快照会增加恢复成本并可能影响 guard 查询。 + snapshotStartedAt := time.Now() if err := s.persistSnapshot(ctx, result.snapshot); err != nil { return mutationResult{}, err } + snapshotMS = elapsedMS(snapshotStartedAt) } if result.applied { + projectionStartedAt := time.Now() // 列表读模型最终一致,失败只记录日志,不回滚已经提交的 Room Cell 命令。 s.projectRoomListBestEffort(ctx, result.snapshot) // 当前房间恢复读模型同样最终一致;权威校验仍以 Room Cell 快照为准。 s.projectRoomPresenceBestEffort(ctx, result.snapshot) - // activity-service 消费的是已经落库的 room outbox 事实,失败只记日志;outbox 补偿链路仍保留房间 IM 投递能力。 - s.publishRoomEventsBestEffort(ctx, result.outboxRecords) + projectionMS = elapsedMS(projectionStartedAt) } - if result.applied && result.syncEvent != nil { - // 同步腾讯云 IM 失败不回滚房间状态;outbox 后续负责补偿投递。 - if err := s.syncPublisher.PublishRoomEvent(ctx, *result.syncEvent); err == nil { - // 同步投递成功后 best-effort 标记 delivered,避免补偿 worker 对同一 event_id 重复补投。 - markCtx, cancel := context.WithTimeout(appcode.WithContext(context.Background(), appcode.FromContext(ctx)), defaultOutboxWorkerOptions().PublishTimeout) - markErr := s.repository.MarkOutboxDelivered(markCtx, result.syncEvent.EventID) - cancel() - if markErr != nil { - logx.Error(ctx, "outbox_mark_delivered_failed", markErr, - slog.String("worker", outboxWorkerName), - slog.String("node_id", s.nodeID), - slog.String("event_id", result.syncEvent.EventID), - slog.String("room_id", result.syncEvent.RoomID), - ) - } - } - } + logRoomCommandTiming(ctx, cmd.RoomID(), cmd.ID(), cmd.Type(), result.applied, elapsedMS(startedAt), idempotencyMS, leaseMS, saveMutationMS, snapshotMS, projectionMS, result.walletDebitMS, 0, 0, len(result.outboxRecords) > 0, result.syncEvent != nil) return result, nil } -func (s *Service) publishRoomEventsBestEffort(ctx context.Context, records []outbox.Record) { - if s == nil || s.eventPublisher == nil || len(records) == 0 { - return - } - for _, record := range records { - if record.Envelope == nil { - // 只有 protobuf envelope 能跨服务消费;没有 envelope 的历史/本地事件仍只参与原 outbox 投递。 - continue - } - if err := s.eventPublisher.PublishOutboxEvent(ctx, record.Envelope); err != nil { - logx.Warn(ctx, "room_event_consumer_publish_failed", - slog.String("node_id", s.nodeID), - slog.String("event_id", record.EventID), - slog.String("event_type", record.EventType), - slog.String("room_id", record.RoomID), - slog.String("error", err.Error()), - ) - } - } -} - func (s *Service) commandPayloadForIdempotency(ctx context.Context, cmd command.Command) ([]byte, bool, error) { payload, err := command.Serialize(cmd) if err != nil { @@ -230,3 +207,7 @@ func (s *Service) ensureLeaseStillOwned(ctx context.Context, roomID string, leas } return nil } + +func elapsedMS(start time.Time) int64 { + return time.Since(start).Milliseconds() +} diff --git a/services/room-service/internal/room/service/presence.go b/services/room-service/internal/room/service/presence.go index c8058135..72848e56 100644 --- a/services/room-service/internal/room/service/presence.go +++ b/services/room-service/internal/room/service/presence.go @@ -61,7 +61,7 @@ func (s *Service) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*r } current.Version++ - // JoinRoom 成功需要 outbox 事件,同时同步推给腾讯云 IM 做房间系统消息。 + // JoinRoom 成功需要 outbox 事件,腾讯云 IM 房间系统消息由 worker 异步投递。 joinedEvent, err := outbox.Build(current.RoomID, "RoomUserJoined", current.Version, now, &roomeventsv1.RoomUserJoined{ UserId: cmd.ActorUserID(), Role: cmd.Role, @@ -215,7 +215,7 @@ func (s *Service) leaveRoom(ctx context.Context, cmd command.LeaveRoom, reason s records = append(records, micEvent) } - // 离房事件进入 outbox,并尝试同步通知腾讯云 IM;stale 清理会通过 attributes 标记原因。 + // 离房事件进入 outbox;stale 清理会通过 attributes 标记原因,腾讯云 IM 由 worker 异步投递。 leftEvent, err := outbox.Build(current.RoomID, "RoomUserLeft", current.Version, now, &roomeventsv1.RoomUserLeft{ UserId: cmd.ActorUserID(), }) diff --git a/services/room-service/internal/room/service/recovery.go b/services/room-service/internal/room/service/recovery.go index 98464ddd..a3e94eb5 100644 --- a/services/room-service/internal/room/service/recovery.go +++ b/services/room-service/internal/room/service/recovery.go @@ -79,11 +79,13 @@ func (s *Service) recoverRoom(ctx context.Context, roomMeta RoomMeta) (*state.Ro } recovered = state.FromProto(&snapshot) + recovered.VisibleRegionID = roomMeta.VisibleRegionID fromVersion = snapshot.GetVersion() } else { // 没有快照时从 rooms meta 重建初始状态,再从版本 0 回放命令。 recovered = state.NewRoomState(roomMeta.RoomID, roomMeta.OwnerUserID, roomMeta.HostUserID, roomMeta.SeatCount, roomMeta.Mode) recovered.Status = roomMeta.Status + recovered.VisibleRegionID = roomMeta.VisibleRegionID fromVersion = 0 } @@ -117,6 +119,7 @@ func replay(current *state.RoomState, cmd command.Command) error { // replay 只重建房间内状态,不重复写 outbox、不重复调用 wallet、不重复同步腾讯云 IM。 switch typed := cmd.(type) { case *command.CreateRoom: + current.VisibleRegionID = typed.VisibleRegionID // 无快照恢复时,创建命令负责把列表/详情展示资料还原到 RoomExt。 applyRoomProfileExt(current, roomProfileInput{ Name: typed.RoomName, @@ -255,6 +258,15 @@ func replay(current *state.RoomState, cmd command.Command) error { current.MicSeats[toIndex].PublishDeadlineMS = movedSeat.PublishDeadlineMS current.MicSeats[toIndex].MicSessionRoomVersion = movedSeat.MicSessionRoomVersion current.MicSeats[toIndex].LastPublishEventTimeMS = movedSeat.LastPublishEventTimeMS + current.MicSeats[toIndex].MicMuted = movedSeat.MicMuted + current.Version++ + case *command.SetMicMute: + seat, exists := current.SeatByUser(typed.TargetUserID) + if !exists { + return fmt.Errorf("set_mic_mute replay target is not on seat: %d", typed.TargetUserID) + } + index := current.SeatIndex(seat.SeatNo) + current.MicSeats[index].MicMuted = typed.Muted current.Version++ case *command.SetMicSeatLock: index := current.SeatIndex(typed.SeatNo) diff --git a/services/room-service/internal/room/service/repository.go b/services/room-service/internal/room/service/repository.go index 0415a172..7724ef7b 100644 --- a/services/room-service/internal/room/service/repository.go +++ b/services/room-service/internal/room/service/repository.go @@ -156,6 +156,22 @@ type RoomPresence struct { UpdatedAtMS int64 } +// RoomOnlineUserQuery 是房间在线用户分页读模型查询条件。 +type RoomOnlineUserQuery struct { + AppCode string + RoomID string + Page int + PageSize int +} + +// RoomOnlineUserPage 是 room_user_presence 中某房间 active 用户的一页结果。 +type RoomOnlineUserPage struct { + Users []RoomPresence + Total int64 + Page int + PageSize int +} + // RoomListQuery 是 room-service 房间列表读模型查询条件。 type RoomListQuery struct { // AppCode 是列表查询所属 App,通常来自 gateway RequestMeta。 @@ -278,8 +294,10 @@ type Repository interface { ClaimPendingOutbox(ctx context.Context, workerID string, limit int, lockUntilMS int64) ([]outbox.Record, error) // MarkOutboxDelivered 标记事件已经投递成功。 MarkOutboxDelivered(ctx context.Context, eventID string) error - // MarkOutboxFailed 记录一次投递失败并转为 retryable 状态等待重试。 - MarkOutboxFailed(ctx context.Context, eventID string, lastErr string) error + // MarkOutboxRetryable 记录一次投递失败,并写入下一次允许抢占的退避时间。 + MarkOutboxRetryable(ctx context.Context, eventID string, lastErr string, nextRetryAtMS int64) error + // MarkOutboxDead 把超过最大重试次数的事件转入 failed,避免固定污染日志和外部依赖。 + MarkOutboxDead(ctx context.Context, eventID string, lastErr string) error // UpsertRoomListEntry 写入或更新房间列表读模型;失败不应破坏 Room Cell 已提交状态。 UpsertRoomListEntry(ctx context.Context, entry RoomListEntry) error // ListRoomListEntries 按区域和 tab 查询房间列表,不访问 Room Cell 内存。 @@ -294,6 +312,8 @@ type Repository interface { ProjectRoomPresence(ctx context.Context, snapshot RoomPresenceSnapshot) error // GetCurrentRoomPresence 查询用户当前 active 房间 presence,不扫描房间列表或快照。 GetCurrentRoomPresence(ctx context.Context, userID int64) (RoomPresence, bool, error) + // ListRoomOnlineUsers 查询房间 active 在线用户分页,不读取完整 RoomSnapshot。 + ListRoomOnlineUsers(ctx context.Context, query RoomOnlineUserQuery) (RoomOnlineUserPage, error) } // RoomPresenceSnapshot 是 repository 投影接口的稳定入参,避免存储层反向依赖 protobuf。 diff --git a/services/room-service/internal/room/service/service.go b/services/room-service/internal/room/service/service.go index 5989113f..70159328 100644 --- a/services/room-service/internal/room/service/service.go +++ b/services/room-service/internal/room/service/service.go @@ -60,8 +60,6 @@ type Service struct { syncPublisher integration.RoomEventPublisher // outboxPublisher 是补偿 worker 对外部消费者的异步投递抽象。 outboxPublisher integration.OutboxPublisher - // eventPublisher 是命令提交后的房间事实消费者投递通道,不参与 Room Cell 提交回滚。 - eventPublisher integration.OutboxPublisher // draining 表示当前节点正在下线,只允许已经进入执行链路的命令收尾。 draining atomic.Bool @@ -93,7 +91,7 @@ type mutationResult struct { giftRank []*roomv1.RankItem // commandPayload 允许少数命令把外部依赖的结算快照写入 command log,用于恢复。 commandPayload []byte - // syncEvent 是需要同步推给腾讯云 IM 的低时延房间系统事件。 + // syncEvent 标记该命令存在腾讯云 IM 房间消息形态;实际投递只由 outbox worker 异步执行。 syncEvent *tencentim.RoomEvent // outboxRecords 是本次命令已经持久化的房间事实,提交后可投给 activity 等外部消费者。 outboxRecords []outbox.Record @@ -101,6 +99,8 @@ type mutationResult struct { roomStatus string // roomSeatCount 非 nil 时代表命令提交时需要同步更新 rooms 元数据座位数。 roomSeatCount *int32 + // walletDebitMS 记录 SendGift 同步扣费耗时;非钱包命令保持 0。 + walletDebitMS int64 } // New 初始化 room-service 领域服务。 @@ -152,13 +152,6 @@ func New(cfg Config, directory router.Directory, repository Repository, wallet i } } -// SetEventPublisher 注入 room outbox 事实消费者;失败只记录日志,不改变房间主链路。 -func (s *Service) SetEventPublisher(publisher integration.OutboxPublisher) { - if s != nil { - s.eventPublisher = publisher - } -} - // HealthCheck 校验 room-service 核心状态 owner 和持久化依赖已经完成装配。 func (s *Service) HealthCheck() error { if s == nil { diff --git a/services/room-service/internal/room/service/service_test.go b/services/room-service/internal/room/service/service_test.go index 96d859b0..4079865b 100644 --- a/services/room-service/internal/room/service/service_test.go +++ b/services/room-service/internal/room/service/service_test.go @@ -2244,7 +2244,7 @@ func TestOutboxWorkerRetriesFailedPublishAndLaterMarksDelivered(t *testing.T) { t.Fatalf("SaveOutbox failed: %v", err) } - options := roomservice.OutboxWorkerOptions{BatchSize: 10, PublishTimeout: time.Second} + options := roomservice.OutboxWorkerOptions{BatchSize: 10, PublishTimeout: time.Second, InitialBackoff: time.Millisecond, MaxBackoff: time.Millisecond, MaxRetryCount: 3} if err := svc.ProcessPendingOutbox(ctx, options); err != nil { t.Fatalf("first ProcessPendingOutbox should keep batch alive after publish failure: %v", err) } @@ -2252,10 +2252,11 @@ func TestOutboxWorkerRetriesFailedPublishAndLaterMarksDelivered(t *testing.T) { if !exists { t.Fatalf("outbox record missing after failure") } - if failed.Status != outbox.StatusRetryable || failed.RetryCount != 1 || !strings.Contains(failed.LastError, "temporary im failure") { + if failed.Status != outbox.StatusRetryable || failed.RetryCount != 1 || failed.NextRetryAtMS <= time.Now().Add(-time.Second).UnixMilli() || !strings.Contains(failed.LastError, "temporary im failure") { t.Fatalf("failed publish should become retryable with retry metadata: %+v", failed) } + time.Sleep(2 * time.Millisecond) if err := svc.ProcessPendingOutbox(ctx, options); err != nil { t.Fatalf("second ProcessPendingOutbox failed: %v", err) } @@ -2301,8 +2302,8 @@ func TestOutboxClaimPreventsDuplicateWorkersAndRetriesAfterFailure(t *testing.T) t.Fatalf("worker-b should not claim worker-a locked record: %+v", claimedB) } - if err := repository.MarkOutboxFailed(ctx, record.EventID, "temporary failure"); err != nil { - t.Fatalf("MarkOutboxFailed failed: %v", err) + if err := repository.MarkOutboxRetryable(ctx, record.EventID, "temporary failure", time.Now().UnixMilli()); err != nil { + t.Fatalf("MarkOutboxRetryable failed: %v", err) } claimedRetry, err := repository.ClaimPendingOutbox(ctx, "worker-b", 10, time.Now().Add(time.Minute).UnixMilli()) if err != nil { @@ -2313,7 +2314,40 @@ func TestOutboxClaimPreventsDuplicateWorkersAndRetriesAfterFailure(t *testing.T) } } -func TestSyncPublishSuccessMarksOutboxDelivered(t *testing.T) { +func TestOutboxWorkerMarksDeadLetterAfterRetryLimit(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + outboxPublisher := &fakeOutboxPublisher{failCount: 10, err: errors.New("permanent im failure")} + svc := newRoomService("node-a", 1, router.NewMemoryDirectory(), repository, &fakeSyncPublisher{}, outboxPublisher) + + record, err := outbox.Build("room-dead", "RoomGiftSent", 1, time.Now(), &roomeventsv1.RoomGiftSent{ + SenderUserId: 1, + TargetUserId: 2, + GiftId: "rocket", + GiftCount: 1, + GiftValue: 100, + }) + if err != nil { + t.Fatalf("Build outbox failed: %v", err) + } + record.RetryCount = 2 + if err := repository.SaveOutbox(ctx, []outbox.Record{record}); err != nil { + t.Fatalf("SaveOutbox failed: %v", err) + } + + if err := svc.ProcessPendingOutbox(ctx, roomservice.OutboxWorkerOptions{BatchSize: 10, PublishTimeout: time.Second, MaxRetryCount: 2}); err != nil { + t.Fatalf("ProcessPendingOutbox failed: %v", err) + } + dead, exists := repository.OutboxRecord(record.EventID) + if !exists || dead.Status != outbox.StatusFailed || !strings.Contains(dead.LastError, "retry limit") { + t.Fatalf("event should be dead-lettered: exists=%v record=%+v", exists, dead) + } + if len(outboxPublisher.envelopes) != 0 { + t.Fatalf("dead-lettered record must not be published again") + } +} + +func TestRoomEventStaysPendingUntilOutboxWorkerDelivers(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) syncPublisher := &fakeSyncPublisher{} @@ -2333,17 +2367,23 @@ func TestSyncPublishSuccessMarksOutboxDelivered(t *testing.T) { }); err != nil { t.Fatalf("JoinRoom failed: %v", err) } - if len(syncPublisher.events) == 0 { - t.Fatalf("expected sync room event") + if len(syncPublisher.events) != 0 { + t.Fatalf("room events must not be synchronously published: %+v", syncPublisher.events) } - eventID := syncPublisher.events[len(syncPublisher.events)-1].EventID - record, exists := repository.OutboxRecord(eventID) - if !exists { - t.Fatalf("sync event outbox record missing: %s", eventID) + records, err := repository.ListPendingOutbox(ctx, 100) + if err != nil { + t.Fatalf("ListPendingOutbox failed: %v", err) } - if record.Status != outbox.StatusDelivered { - t.Fatalf("sync success should best-effort mark outbox delivered: %+v", record) + if len(records) == 0 { + t.Fatalf("expected pending room event before worker delivery") + } + if err := svc.ProcessPendingOutbox(ctx, roomservice.OutboxWorkerOptions{BatchSize: 100, PublishTimeout: time.Second}); err != nil { + t.Fatalf("ProcessPendingOutbox failed: %v", err) + } + delivered, exists := repository.OutboxRecord(records[0].EventID) + if !exists || delivered.Status != outbox.StatusDelivered { + t.Fatalf("outbox worker should mark event delivered: exists=%v record=%+v", exists, delivered) } } diff --git a/services/room-service/internal/room/state/state.go b/services/room-service/internal/room/state/state.go index babde3f0..7e833dfd 100644 --- a/services/room-service/internal/room/state/state.go +++ b/services/room-service/internal/room/state/state.go @@ -44,6 +44,8 @@ type MicSeat struct { MicSessionRoomVersion int64 // LastPublishEventTimeMS 是已接受的最新 RTC/客户端发布事件时间。 LastPublishEventTimeMS int64 + // MicMuted 是服务端可见的麦克风静音状态,用于同步其他用户 UI。 + MicMuted bool } // RoomUserState 只保存 room-service 真正需要的轻量用户态。 @@ -81,6 +83,8 @@ type RoomState struct { HostUserID int64 // Mode 是房间模式,当前只作为状态字段透出。 Mode string + // VisibleRegionID 是房间礼物、列表和区域可见性策略使用的区域桶。 + VisibleRegionID int64 // Status 是房间生命周期状态。 Status string // ChatEnabled 控制腾讯云 IM 公屏发言守卫。 @@ -143,21 +147,22 @@ func (s *RoomState) Clone() *RoomState { // 所有 map 和 slice 都重新分配,避免 mutate 失败时污染当前 Cell 状态。 cloned := &RoomState{ - RoomID: s.RoomID, - OwnerUserID: s.OwnerUserID, - HostUserID: s.HostUserID, - Mode: s.Mode, - Status: s.Status, - ChatEnabled: s.ChatEnabled, - MicSeats: append([]MicSeat(nil), s.MicSeats...), - OnlineUsers: make(map[int64]*RoomUserState, len(s.OnlineUsers)), - AdminUsers: make(map[int64]bool, len(s.AdminUsers)), - BanUsers: make(map[int64]bool, len(s.BanUsers)), - MuteUsers: make(map[int64]bool, len(s.MuteUsers)), - Heat: s.Heat, - GiftRank: append([]RankItem(nil), s.GiftRank...), - RoomExt: make(map[string]string, len(s.RoomExt)), - Version: s.Version, + RoomID: s.RoomID, + OwnerUserID: s.OwnerUserID, + HostUserID: s.HostUserID, + Mode: s.Mode, + VisibleRegionID: s.VisibleRegionID, + Status: s.Status, + ChatEnabled: s.ChatEnabled, + MicSeats: append([]MicSeat(nil), s.MicSeats...), + OnlineUsers: make(map[int64]*RoomUserState, len(s.OnlineUsers)), + AdminUsers: make(map[int64]bool, len(s.AdminUsers)), + BanUsers: make(map[int64]bool, len(s.BanUsers)), + MuteUsers: make(map[int64]bool, len(s.MuteUsers)), + Heat: s.Heat, + GiftRank: append([]RankItem(nil), s.GiftRank...), + RoomExt: make(map[string]string, len(s.RoomExt)), + Version: s.Version, } for userID, userState := range s.OnlineUsers { @@ -263,6 +268,7 @@ func (s *RoomState) ToProto() *roomv1.RoomSnapshot { PublishDeadlineMs: seat.PublishDeadlineMS, MicSessionRoomVersion: seat.MicSessionRoomVersion, LastPublishEventTimeMs: seat.LastPublishEventTimeMS, + MicMuted: seat.MicMuted, }) } @@ -279,22 +285,23 @@ func (s *RoomState) ToProto() *roomv1.RoomSnapshot { } return &roomv1.RoomSnapshot{ - RoomId: s.RoomID, - OwnerUserId: s.OwnerUserID, - HostUserId: s.HostUserID, - Mode: s.Mode, - Status: s.Status, - ChatEnabled: s.ChatEnabled, - MicSeats: seats, - OnlineUsers: users, - AdminUserIds: sortedSetIDs(s.AdminUsers), - BanUserIds: sortedSetIDs(s.BanUsers), - MuteUserIds: sortedSetIDs(s.MuteUsers), - GiftRank: rankItems, - RoomExt: cloneStringMap(s.RoomExt), - Heat: s.Heat, - Version: s.Version, - RoomShortId: s.RoomExt["room_short_id"], + RoomId: s.RoomID, + OwnerUserId: s.OwnerUserID, + HostUserId: s.HostUserID, + Mode: s.Mode, + VisibleRegionId: s.VisibleRegionID, + Status: s.Status, + ChatEnabled: s.ChatEnabled, + MicSeats: seats, + OnlineUsers: users, + AdminUserIds: sortedSetIDs(s.AdminUsers), + BanUserIds: sortedSetIDs(s.BanUsers), + MuteUserIds: sortedSetIDs(s.MuteUsers), + GiftRank: rankItems, + RoomExt: cloneStringMap(s.RoomExt), + Heat: s.Heat, + Version: s.Version, + RoomShortId: s.RoomExt["room_short_id"], } } @@ -307,21 +314,22 @@ func FromProto(snapshot *roomv1.RoomSnapshot) *RoomState { // 所有集合重新分配,恢复后的状态可以直接交给 Room Cell 独占使用。 restored := &RoomState{ - RoomID: snapshot.GetRoomId(), - OwnerUserID: snapshot.GetOwnerUserId(), - HostUserID: snapshot.GetHostUserId(), - Mode: snapshot.GetMode(), - Status: snapshot.GetStatus(), - ChatEnabled: snapshot.GetChatEnabled(), - MicSeats: make([]MicSeat, 0, len(snapshot.GetMicSeats())), - OnlineUsers: make(map[int64]*RoomUserState, len(snapshot.GetOnlineUsers())), - AdminUsers: make(map[int64]bool, len(snapshot.GetAdminUserIds())), - BanUsers: make(map[int64]bool, len(snapshot.GetBanUserIds())), - MuteUsers: make(map[int64]bool, len(snapshot.GetMuteUserIds())), - GiftRank: make([]RankItem, 0, len(snapshot.GetGiftRank())), - RoomExt: cloneStringMap(snapshot.GetRoomExt()), - Heat: snapshot.GetHeat(), - Version: snapshot.GetVersion(), + RoomID: snapshot.GetRoomId(), + OwnerUserID: snapshot.GetOwnerUserId(), + HostUserID: snapshot.GetHostUserId(), + Mode: snapshot.GetMode(), + VisibleRegionID: snapshot.GetVisibleRegionId(), + Status: snapshot.GetStatus(), + ChatEnabled: snapshot.GetChatEnabled(), + MicSeats: make([]MicSeat, 0, len(snapshot.GetMicSeats())), + OnlineUsers: make(map[int64]*RoomUserState, len(snapshot.GetOnlineUsers())), + AdminUsers: make(map[int64]bool, len(snapshot.GetAdminUserIds())), + BanUsers: make(map[int64]bool, len(snapshot.GetBanUserIds())), + MuteUsers: make(map[int64]bool, len(snapshot.GetMuteUserIds())), + GiftRank: make([]RankItem, 0, len(snapshot.GetGiftRank())), + RoomExt: cloneStringMap(snapshot.GetRoomExt()), + Heat: snapshot.GetHeat(), + Version: snapshot.GetVersion(), } for _, seat := range snapshot.GetMicSeats() { @@ -335,6 +343,7 @@ func FromProto(snapshot *roomv1.RoomSnapshot) *RoomState { PublishDeadlineMS: seat.GetPublishDeadlineMs(), MicSessionRoomVersion: seat.GetMicSessionRoomVersion(), LastPublishEventTimeMS: seat.GetLastPublishEventTimeMs(), + MicMuted: seat.GetMicMuted(), }) } diff --git a/services/room-service/internal/storage/mysql/repository.go b/services/room-service/internal/storage/mysql/repository.go index e5ea5cdd..cfcf320a 100644 --- a/services/room-service/internal/storage/mysql/repository.go +++ b/services/room-service/internal/storage/mysql/repository.go @@ -163,10 +163,11 @@ func (r *Repository) Migrate(ctx context.Context) error { envelope LONGBLOB NOT NULL, created_at_ms BIGINT NOT NULL, retry_count INT NOT NULL DEFAULT 0, + next_retry_at_ms BIGINT NOT NULL DEFAULT 0, last_error TEXT NULL, updated_at_ms BIGINT NOT NULL, PRIMARY KEY (app_code, event_id), - KEY idx_room_outbox_pending (app_code, status, created_at_ms), + KEY idx_room_outbox_pending (app_code, status, next_retry_at_ms, created_at_ms), KEY idx_room_outbox_claim (app_code, status, lock_until_ms, created_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, `CREATE TABLE IF NOT EXISTS room_user_feed_entries ( @@ -206,9 +207,51 @@ func (r *Repository) Migrate(ctx context.Context) error { } } + return r.ensureOutboxRetrySchema(ctx) +} + +func (r *Repository) ensureOutboxRetrySchema(ctx context.Context) error { + hasColumn, err := r.columnExists(ctx, "room_outbox", "next_retry_at_ms") + if err != nil { + return err + } + if !hasColumn { + if _, err := r.db.ExecContext(ctx, `ALTER TABLE room_outbox ADD COLUMN next_retry_at_ms BIGINT NOT NULL DEFAULT 0 AFTER retry_count`); err != nil { + return err + } + } + hasIndex, err := r.indexExists(ctx, "room_outbox", "idx_room_outbox_retry") + if err != nil { + return err + } + if !hasIndex { + if _, err := r.db.ExecContext(ctx, `ALTER TABLE room_outbox ADD INDEX idx_room_outbox_retry (app_code, status, next_retry_at_ms, created_at_ms)`); err != nil { + return err + } + } return nil } +func (r *Repository) columnExists(ctx context.Context, tableName string, columnName string) (bool, error) { + var exists int + err := r.db.QueryRowContext(ctx, ` + SELECT COUNT(*) + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ? + `, tableName, columnName).Scan(&exists) + return exists > 0, err +} + +func (r *Repository) indexExists(ctx context.Context, tableName string, indexName string) (bool, error) { + var exists int + err := r.db.QueryRowContext(ctx, ` + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND INDEX_NAME = ? + `, tableName, indexName).Scan(&exists) + return exists > 0, err +} + // SaveRoomMeta 插入房间元数据。 func (r *Repository) SaveRoomMeta(ctx context.Context, meta roomservice.RoomMeta) error { // rooms 表只保存恢复初始状态需要的元数据,实时态以 snapshot/command log 为准。 @@ -368,8 +411,8 @@ func (r *Repository) SaveMutation(ctx context.Context, commit roomservice.Mutati return err } if _, err := tx.ExecContext(ctx, - `INSERT INTO room_outbox (app_code, event_id, event_type, room_id, status, envelope, created_at_ms, retry_count, last_error, updated_at_ms) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `INSERT INTO room_outbox (app_code, event_id, event_type, room_id, status, envelope, created_at_ms, retry_count, next_retry_at_ms, last_error, updated_at_ms) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE event_id = event_id`, record.AppCode, record.EventID, @@ -379,6 +422,7 @@ func (r *Repository) SaveMutation(ctx context.Context, commit roomservice.Mutati envelopeBytes, record.CreatedAtMS, record.RetryCount, + record.NextRetryAtMS, nullableString(record.LastError), record.CreatedAtMS, ); err != nil { @@ -603,8 +647,8 @@ func (r *Repository) SaveOutbox(ctx context.Context, records []outbox.Record) er } if _, err := tx.ExecContext(ctx, - `INSERT INTO room_outbox (app_code, event_id, event_type, room_id, status, envelope, created_at_ms, retry_count, last_error, updated_at_ms) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `INSERT INTO room_outbox (app_code, event_id, event_type, room_id, status, envelope, created_at_ms, retry_count, next_retry_at_ms, last_error, updated_at_ms) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE event_id = event_id`, record.AppCode, record.EventID, @@ -614,6 +658,7 @@ func (r *Repository) SaveOutbox(ctx context.Context, records []outbox.Record) er envelopeBytes, record.CreatedAtMS, record.RetryCount, + record.NextRetryAtMS, nullableString(record.LastError), record.CreatedAtMS, ); err != nil { @@ -635,15 +680,19 @@ func (r *Repository) ListPendingOutbox(ctx context.Context, limit int) ([]outbox } // 按创建时间顺序扫描 pending,尽量保持事件投递顺序。 + nowMS := time.Now().UTC().UnixMilli() rows, err := r.db.QueryContext(ctx, - `SELECT app_code, event_id, event_type, room_id, status, worker_id, lock_until_ms, envelope, created_at_ms, retry_count, COALESCE(last_error, '') + `SELECT app_code, event_id, event_type, room_id, status, worker_id, lock_until_ms, envelope, created_at_ms, retry_count, next_retry_at_ms, COALESCE(last_error, '') FROM room_outbox WHERE app_code = ? AND status IN (?, ?) + AND (status = ? OR next_retry_at_ms <= ?) ORDER BY created_at_ms ASC LIMIT ?`, appcode.FromContext(ctx), outbox.StatusPending, outbox.StatusRetryable, + outbox.StatusPending, + nowMS, limit, ) if err != nil { @@ -657,7 +706,7 @@ func (r *Repository) ListPendingOutbox(ctx context.Context, limit int) ([]outbox var envelopeBytes []byte var record outbox.Record var lockUntil sql.Null[int64] - if err := rows.Scan(&record.AppCode, &record.EventID, &record.EventType, &record.RoomID, &record.Status, &record.WorkerID, &lockUntil, &envelopeBytes, &record.CreatedAtMS, &record.RetryCount, &record.LastError); err != nil { + if err := rows.Scan(&record.AppCode, &record.EventID, &record.EventType, &record.RoomID, &record.Status, &record.WorkerID, &lockUntil, &envelopeBytes, &record.CreatedAtMS, &record.RetryCount, &record.NextRetryAtMS, &record.LastError); err != nil { return nil, err } if lockUntil.Valid { @@ -698,11 +747,12 @@ func (r *Repository) ClaimPendingOutbox(ctx context.Context, workerID string, li nowMS := time.Now().UTC().UnixMilli() rows, err := tx.QueryContext(ctx, - `SELECT app_code, event_id, event_type, room_id, status, worker_id, lock_until_ms, envelope, created_at_ms, retry_count, COALESCE(last_error, '') + `SELECT app_code, event_id, event_type, room_id, status, worker_id, lock_until_ms, envelope, created_at_ms, retry_count, next_retry_at_ms, COALESCE(last_error, '') FROM room_outbox WHERE app_code = ? AND ( - status IN (?, ?) + status = ? + OR (status = ? AND next_retry_at_ms <= ?) OR (status = ? AND lock_until_ms IS NOT NULL AND lock_until_ms <= ?) ) ORDER BY created_at_ms ASC @@ -711,6 +761,7 @@ func (r *Repository) ClaimPendingOutbox(ctx context.Context, workerID string, li appcode.FromContext(ctx), outbox.StatusPending, outbox.StatusRetryable, + nowMS, outbox.StatusDelivering, nowMS, limit, @@ -726,7 +777,7 @@ func (r *Repository) ClaimPendingOutbox(ctx context.Context, workerID string, li var envelopeBytes []byte var record outbox.Record var lockUntilValue sql.Null[int64] - if err := rows.Scan(&record.AppCode, &record.EventID, &record.EventType, &record.RoomID, &record.Status, &record.WorkerID, &lockUntilValue, &envelopeBytes, &record.CreatedAtMS, &record.RetryCount, &record.LastError); err != nil { + if err := rows.Scan(&record.AppCode, &record.EventID, &record.EventType, &record.RoomID, &record.Status, &record.WorkerID, &lockUntilValue, &envelopeBytes, &record.CreatedAtMS, &record.RetryCount, &record.NextRetryAtMS, &record.LastError); err != nil { _ = rows.Close() _ = tx.Rollback() return nil, err @@ -802,14 +853,31 @@ func (r *Repository) MarkOutboxDelivered(ctx context.Context, eventID string) er return err } -// MarkOutboxFailed 标记事件投递失败并累计重试次数。 -func (r *Repository) MarkOutboxFailed(ctx context.Context, eventID string, lastErr string) error { - // 失败转入 retryable,worker 下一轮可重新抢占。 +// MarkOutboxRetryable 标记事件投递失败并写入下一次重试时间。 +func (r *Repository) MarkOutboxRetryable(ctx context.Context, eventID string, lastErr string, nextRetryAtMS int64) error { + // 失败转入 retryable,worker 只有到 next_retry_at_ms 后才会重新抢占。 _, err := r.db.ExecContext(ctx, `UPDATE room_outbox - SET status = ?, worker_id = '', lock_until_ms = NULL, retry_count = retry_count + 1, last_error = ?, updated_at_ms = ? + SET status = ?, worker_id = '', lock_until_ms = NULL, retry_count = retry_count + 1, next_retry_at_ms = ?, last_error = ?, updated_at_ms = ? WHERE app_code = ? AND event_id = ?`, outbox.StatusRetryable, + nextRetryAtMS, + lastErr, + time.Now().UTC().UnixMilli(), + appcode.FromContext(ctx), + eventID, + ) + + return err +} + +// MarkOutboxDead 把超过最大重试次数的事件转入 failed。 +func (r *Repository) MarkOutboxDead(ctx context.Context, eventID string, lastErr string) error { + _, err := r.db.ExecContext(ctx, + `UPDATE room_outbox + SET status = ?, worker_id = '', lock_until_ms = NULL, last_error = ?, updated_at_ms = ? + WHERE app_code = ? AND event_id = ?`, + outbox.StatusFailed, lastErr, time.Now().UTC().UnixMilli(), appcode.FromContext(ctx), @@ -1270,6 +1338,79 @@ func (r *Repository) GetCurrentRoomPresence(ctx context.Context, userID int64) ( return presence, true, nil } +// ListRoomOnlineUsers 分页查询某个房间当前 active presence。 +func (r *Repository) ListRoomOnlineUsers(ctx context.Context, query roomservice.RoomOnlineUserQuery) (roomservice.RoomOnlineUserPage, error) { + page := query.Page + if page <= 0 { + page = 1 + } + pageSize := query.PageSize + if pageSize <= 0 { + pageSize = 50 + } + offset := (page - 1) * pageSize + appCode := normalizedRecordAppCode(ctx, query.AppCode) + + var total int64 + if err := r.db.QueryRowContext(ctx, + `SELECT COUNT(*) + FROM room_user_presence + WHERE app_code = ? AND room_id = ? AND status = ?`, + appCode, + query.RoomID, + roomPresenceStatusActiveSQL, + ).Scan(&total); err != nil { + return roomservice.RoomOnlineUserPage{}, err + } + + rows, err := r.db.QueryContext(ctx, + `SELECT app_code, user_id, room_id, role, publish_state, mic_session_id, room_version, status, last_seen_at_ms, updated_at_ms + FROM room_user_presence + WHERE app_code = ? AND room_id = ? AND status = ? + ORDER BY last_seen_at_ms DESC, user_id ASC + LIMIT ? OFFSET ?`, + appCode, + query.RoomID, + roomPresenceStatusActiveSQL, + pageSize, + offset, + ) + if err != nil { + return roomservice.RoomOnlineUserPage{}, err + } + defer rows.Close() + + users := make([]roomservice.RoomPresence, 0, pageSize) + for rows.Next() { + var presence roomservice.RoomPresence + if err := rows.Scan( + &presence.AppCode, + &presence.UserID, + &presence.RoomID, + &presence.Role, + &presence.PublishState, + &presence.MicSessionID, + &presence.RoomVersion, + &presence.Status, + &presence.LastSeenAtMS, + &presence.UpdatedAtMS, + ); err != nil { + return roomservice.RoomOnlineUserPage{}, err + } + users = append(users, presence) + } + if err := rows.Err(); err != nil { + return roomservice.RoomOnlineUserPage{}, err + } + + return roomservice.RoomOnlineUserPage{ + Users: users, + Total: total, + Page: page, + PageSize: pageSize, + }, nil +} + const ( roomPresenceStatusActiveSQL = "active" roomPresenceStatusLeftSQL = "left" diff --git a/services/room-service/internal/testutil/mysqltest/mysqltest.go b/services/room-service/internal/testutil/mysqltest/mysqltest.go index 84ab8457..b4af5a12 100644 --- a/services/room-service/internal/testutil/mysqltest/mysqltest.go +++ b/services/room-service/internal/testutil/mysqltest/mysqltest.go @@ -77,7 +77,7 @@ func (r *Repository) OutboxRecord(eventID string) (outbox.Record, bool) { r.t.Helper() row := r.schema.DB.QueryRowContext(context.Background(), ` - SELECT app_code, event_id, event_type, room_id, status, worker_id, lock_until_ms, envelope, created_at_ms, retry_count, COALESCE(last_error, '') + SELECT app_code, event_id, event_type, room_id, status, worker_id, lock_until_ms, envelope, created_at_ms, retry_count, next_retry_at_ms, COALESCE(last_error, '') FROM room_outbox WHERE app_code = ? AND event_id = ? `, appcode.Default, eventID) @@ -85,7 +85,7 @@ func (r *Repository) OutboxRecord(eventID string) (outbox.Record, bool) { var envelopeBytes []byte var record outbox.Record var lockUntil sql.Null[int64] - if err := row.Scan(&record.AppCode, &record.EventID, &record.EventType, &record.RoomID, &record.Status, &record.WorkerID, &lockUntil, &envelopeBytes, &record.CreatedAtMS, &record.RetryCount, &record.LastError); err != nil { + if err := row.Scan(&record.AppCode, &record.EventID, &record.EventType, &record.RoomID, &record.Status, &record.WorkerID, &lockUntil, &envelopeBytes, &record.CreatedAtMS, &record.RetryCount, &record.NextRetryAtMS, &record.LastError); err != nil { if err == sql.ErrNoRows { return outbox.Record{}, false } diff --git a/services/room-service/internal/transport/grpc/server.go b/services/room-service/internal/transport/grpc/server.go index cd46f474..35ca43c5 100644 --- a/services/room-service/internal/transport/grpc/server.go +++ b/services/room-service/internal/transport/grpc/server.go @@ -110,6 +110,13 @@ func (s *Server) ConfirmMicPublishing(ctx context.Context, req *roomv1.ConfirmMi return mapServiceResult(s.svc.ConfirmMicPublishing(ctx, req)) } +// SetMicMute 代理到领域服务。 +func (s *Server) SetMicMute(ctx context.Context, req *roomv1.SetMicMuteRequest) (*roomv1.SetMicMuteResponse, error) { + // 服务端可见静音态必须经 Room Cell 提交,确保其他用户 UI 和系统消息一致。 + ctx = contextWithMetaApp(ctx, req.GetMeta()) + return mapServiceResult(s.svc.SetMicMute(ctx, req)) +} + // ApplyRTCEvent 代理到领域服务。 func (s *Server) ApplyRTCEvent(ctx context.Context, req *roomv1.ApplyRTCEventRequest) (*roomv1.ApplyRTCEventResponse, error) { // 腾讯 RTC 回调已在 gateway 验签;房间状态变化仍必须由 Room Cell 串行提交。 @@ -222,6 +229,13 @@ func (s *Server) GetRoomSnapshot(ctx context.Context, req *roomv1.GetRoomSnapsho return mapServiceResult(s.svc.GetRoomSnapshot(ctx, req)) } +// ListRoomOnlineUsers 代理到房间在线用户分页读模型。 +func (s *Server) ListRoomOnlineUsers(ctx context.Context, req *roomv1.ListRoomOnlineUsersRequest) (*roomv1.ListRoomOnlineUsersResponse, error) { + // 在线人数弹窗只读 room_user_presence 投影,不拉完整 RoomSnapshot 做分页。 + ctx = contextWithMetaApp(ctx, req.GetMeta()) + return mapServiceResult(s.svc.ListRoomOnlineUsers(ctx, req)) +} + func contextWithMetaApp(ctx context.Context, meta *roomv1.RequestMeta) context.Context { if meta == nil { return appcode.WithContext(ctx, "") diff --git a/services/wallet-service/internal/domain/ledger/ledger.go b/services/wallet-service/internal/domain/ledger/ledger.go index 1314ecab..a8bf02ac 100644 --- a/services/wallet-service/internal/domain/ledger/ledger.go +++ b/services/wallet-service/internal/domain/ledger/ledger.go @@ -28,6 +28,15 @@ const ( VipStatusActive = "active" // VipStatusDisabled 表示 VIP 配置被后台停用。 VipStatusDisabled = "disabled" + + // GameOpDebit 表示游戏平台下注、局内道具等扣金币行为。 + GameOpDebit = "debit" + // GameOpCredit 表示游戏平台中奖、派奖等加金币行为。 + GameOpCredit = "credit" + // GameOpRefund 表示游戏平台扣款失败或取消后的退款入账。 + GameOpRefund = "refund" + // GameOpReverse 表示平台冲正;首版按扣回用户 COIN 处理。 + GameOpReverse = "reverse" ) // DebitGiftCommand 是 room-service 送礼扣费的账务命令。 @@ -331,6 +340,28 @@ type TaskRewardReceipt struct { GrantedAtMS int64 } +// GameCoinChangeCommand 是 game-service 对钱包发起的游戏专用金币改账命令。 +type GameCoinChangeCommand struct { + AppCode string + CommandID string + UserID int64 + PlatformCode string + GameID string + ProviderOrderID string + ProviderRoundID string + OpType string + CoinAmount int64 + RoomID string + RequestHash string +} + +// GameCoinChangeReceipt 是游戏改账成功或幂等重放后的稳定回执。 +type GameCoinChangeReceipt struct { + TransactionID string + BalanceAfter int64 + IdempotentReplay bool +} + // ValidAssetType 限定账本允许落账的资产类型,避免写入任意字符串资产。 func ValidAssetType(assetType string) bool { switch assetType { @@ -369,3 +400,18 @@ func NormalizeCoinSellerStockType(stockType string) string { return "" } } + +func NormalizeGameOpType(opType string) string { + switch strings.ToLower(strings.TrimSpace(opType)) { + case GameOpDebit: + return GameOpDebit + case GameOpCredit: + return GameOpCredit + case GameOpRefund: + return GameOpRefund + case GameOpReverse: + return GameOpReverse + default: + return "" + } +} diff --git a/services/wallet-service/internal/service/wallet/service.go b/services/wallet-service/internal/service/wallet/service.go index e5d00fc1..1dfac5f3 100644 --- a/services/wallet-service/internal/service/wallet/service.go +++ b/services/wallet-service/internal/service/wallet/service.go @@ -18,6 +18,7 @@ type Repository interface { AdminCreditCoinSellerStock(ctx context.Context, command ledger.CoinSellerStockCreditCommand) (ledger.CoinSellerStockCreditReceipt, error) TransferCoinFromSeller(ctx context.Context, command ledger.CoinSellerTransferCommand) (ledger.CoinSellerTransferReceipt, error) CreditTaskReward(ctx context.Context, command ledger.TaskRewardCommand) (ledger.TaskRewardReceipt, error) + ApplyGameCoinChange(ctx context.Context, command ledger.GameCoinChangeCommand) (ledger.GameCoinChangeReceipt, error) ListRechargeBills(ctx context.Context, query ledger.ListRechargeBillsQuery) ([]ledger.RechargeBill, int64, error) GetWalletOverview(ctx context.Context, userID int64) (ledger.WalletOverview, error) GetWalletValueSummary(ctx context.Context, userID int64) (ledger.WalletValueSummary, error) @@ -219,6 +220,36 @@ func (s *Service) CreditTaskReward(ctx context.Context, command ledger.TaskRewar return s.repository.CreditTaskReward(ctx, command) } +// ApplyGameCoinChange 是游戏平台唯一的 COIN 改账入口,方向由 op_type 控制。 +func (s *Service) ApplyGameCoinChange(ctx context.Context, command ledger.GameCoinChangeCommand) (ledger.GameCoinChangeReceipt, error) { + if command.CommandID == "" || command.UserID <= 0 || command.PlatformCode == "" || command.GameID == "" || command.ProviderOrderID == "" { + return ledger.GameCoinChangeReceipt{}, xerr.New(xerr.InvalidArgument, "game coin command is incomplete") + } + command.OpType = ledger.NormalizeGameOpType(command.OpType) + if command.OpType == "" { + return ledger.GameCoinChangeReceipt{}, xerr.New(xerr.InvalidArgument, "game op_type is invalid") + } + if command.CoinAmount <= 0 { + return ledger.GameCoinChangeReceipt{}, xerr.New(xerr.InvalidArgument, "coin_amount must be positive") + } + command.RequestHash = strings.TrimSpace(command.RequestHash) + if command.RequestHash == "" { + return ledger.GameCoinChangeReceipt{}, xerr.New(xerr.InvalidArgument, "request_hash is required") + } + if s.repository == nil { + return ledger.GameCoinChangeReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + command.AppCode = appcode.Normalize(command.AppCode) + command.PlatformCode = strings.TrimSpace(command.PlatformCode) + command.GameID = strings.TrimSpace(command.GameID) + command.ProviderOrderID = strings.TrimSpace(command.ProviderOrderID) + command.ProviderRoundID = strings.TrimSpace(command.ProviderRoundID) + command.RoomID = strings.TrimSpace(command.RoomID) + ctx = appcode.WithContext(ctx, command.AppCode) + + return s.repository.ApplyGameCoinChange(ctx, command) +} + // ListRechargeBills 返回充值账单只读列表;所有充值来源必须先在 wallet-service 落充值记录。 func (s *Service) ListRechargeBills(ctx context.Context, query ledger.ListRechargeBillsQuery) ([]ledger.RechargeBill, int64, error) { if s.repository == nil { diff --git a/services/wallet-service/internal/service/wallet/service_test.go b/services/wallet-service/internal/service/wallet/service_test.go index 4ae12ee8..81aaaf0e 100644 --- a/services/wallet-service/internal/service/wallet/service_test.go +++ b/services/wallet-service/internal/service/wallet/service_test.go @@ -1223,6 +1223,111 @@ func TestWalletOverviewAndDiamondExchangeConfigComeFromWalletService(t *testing. } } +func TestApplyGameCoinChangeDebitCreditAndIdempotency(t *testing.T) { + repository := mysqltest.NewRepository(t) + repository.SetBalance(88001, 500) + svc := walletservice.New(repository) + + debit := ledger.GameCoinChangeCommand{ + AppCode: "lalu", + CommandID: "game:demo:order-1", + UserID: 88001, + PlatformCode: "demo", + GameID: "demo_rocket_001", + ProviderOrderID: "order-1", + ProviderRoundID: "round-1", + OpType: ledger.GameOpDebit, + CoinAmount: 120, + RoomID: "room-game-1", + RequestHash: "hash-order-1", + } + first, err := svc.ApplyGameCoinChange(context.Background(), debit) + if err != nil { + t.Fatalf("game debit failed: %v", err) + } + if first.BalanceAfter != 380 || first.IdempotentReplay { + t.Fatalf("game debit receipt mismatch: %+v", first) + } + second, err := svc.ApplyGameCoinChange(context.Background(), debit) + if err != nil { + t.Fatalf("game debit replay failed: %v", err) + } + if second.TransactionID != first.TransactionID || !second.IdempotentReplay || second.BalanceAfter != 380 { + t.Fatalf("game debit replay mismatch: first=%+v second=%+v", first, second) + } + credit, err := svc.ApplyGameCoinChange(context.Background(), ledger.GameCoinChangeCommand{ + AppCode: "lalu", + CommandID: "game:demo:order-2", + UserID: 88001, + PlatformCode: "demo", + GameID: "demo_rocket_001", + ProviderOrderID: "order-2", + OpType: ledger.GameOpCredit, + CoinAmount: 70, + RequestHash: "hash-order-2", + }) + if err != nil { + t.Fatalf("game credit failed: %v", err) + } + if credit.BalanceAfter != 450 { + t.Fatalf("game credit balance mismatch: %+v", credit) + } + balances, err := svc.GetBalances(context.Background(), 88001, []string{ledger.AssetCoin}) + if err != nil { + t.Fatalf("GetBalances failed: %v", err) + } + if balanceAmount(balances, ledger.AssetCoin) != 450 { + t.Fatalf("final balance mismatch: %+v", balances) + } + if got := repository.CountRows("wallet_transactions", "biz_type IN ('game_debit','game_credit')"); got != 2 { + t.Fatalf("game coin changes should write two transactions, got %d", got) + } + if got := repository.CountRows("wallet_outbox", "event_type = ?", "WalletBalanceChanged"); got < 2 { + t.Fatalf("game coin changes should publish balance outbox events, got %d", got) + } +} + +func TestApplyGameCoinChangeRejectsHashConflictAndInsufficientBalance(t *testing.T) { + repository := mysqltest.NewRepository(t) + repository.SetBalance(88002, 50) + svc := walletservice.New(repository) + command := ledger.GameCoinChangeCommand{ + AppCode: "lalu", + CommandID: "game:demo:order-conflict", + UserID: 88002, + PlatformCode: "demo", + GameID: "demo_rocket_001", + ProviderOrderID: "order-conflict", + OpType: ledger.GameOpDebit, + CoinAmount: 20, + RequestHash: "hash-a", + } + if _, err := svc.ApplyGameCoinChange(context.Background(), command); err != nil { + t.Fatalf("first game debit failed: %v", err) + } + command.RequestHash = "hash-b" + if _, err := svc.ApplyGameCoinChange(context.Background(), command); !xerr.IsCode(err, xerr.IdempotencyConflict) { + t.Fatalf("expected IDEMPOTENCY_CONFLICT, got %v", err) + } + _, err := svc.ApplyGameCoinChange(context.Background(), ledger.GameCoinChangeCommand{ + AppCode: "lalu", + CommandID: "game:demo:order-insufficient", + UserID: 88002, + PlatformCode: "demo", + GameID: "demo_rocket_001", + ProviderOrderID: "order-insufficient", + OpType: ledger.GameOpDebit, + CoinAmount: 1000, + RequestHash: "hash-insufficient", + }) + if !xerr.IsCode(err, xerr.InsufficientBalance) { + t.Fatalf("expected INSUFFICIENT_BALANCE, got %v", err) + } + if got := repository.CountRows("wallet_transactions", "command_id = ?", "game:demo:order-insufficient"); got != 0 { + t.Fatalf("insufficient game debit must not write transaction, got %d", got) + } +} + func balanceAmount(balances []ledger.AssetBalance, assetType string) int64 { for _, balance := range balances { if balance.AssetType == assetType { diff --git a/services/wallet-service/internal/storage/mysql/repository.go b/services/wallet-service/internal/storage/mysql/repository.go index df89d189..50f3b3b6 100644 --- a/services/wallet-service/internal/storage/mysql/repository.go +++ b/services/wallet-service/internal/storage/mysql/repository.go @@ -28,6 +28,10 @@ const ( bizTypeCoinSellerTransfer = "coin_seller_transfer" bizTypeCoinSellerStockPurchase = "coin_seller_stock_purchase" bizTypeCoinSellerCoinCompensation = "coin_seller_coin_compensation" + bizTypeGameDebit = "game_debit" + bizTypeGameCredit = "game_credit" + bizTypeGameRefund = "game_refund" + bizTypeGameReverse = "game_reverse" outboxStatusPending = "pending" ) @@ -420,6 +424,92 @@ func (r *Repository) CreditTaskReward(ctx context.Context, command ledger.TaskRe return receiptFromTaskRewardMetadata(transactionID, metadata), nil } +// ApplyGameCoinChange 在同一事务内完成游戏 COIN 改账、分录和 outbox。 +func (r *Repository) ApplyGameCoinChange(ctx context.Context, command ledger.GameCoinChangeCommand) (ledger.GameCoinChangeReceipt, error) { + if r == nil || r.db == nil { + return ledger.GameCoinChangeReceipt{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, command.AppCode) + command.AppCode = appcode.FromContext(ctx) + + bizType, delta, err := gameBizTypeAndDelta(command.OpType, command.CoinAmount) + if err != nil { + return ledger.GameCoinChangeReceipt{}, err + } + + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return ledger.GameCoinChangeReceipt{}, err + } + defer func() { _ = tx.Rollback() }() + + if txRow, exists, err := r.lookupTransactionWithConflictCode(ctx, tx, command.CommandID, command.RequestHash, bizType, xerr.IdempotencyConflict); err != nil || exists { + if err != nil || !exists { + return ledger.GameCoinChangeReceipt{}, err + } + receipt, receiptErr := r.receiptForGameTransaction(ctx, tx, txRow.TransactionID) + receipt.IdempotentReplay = true + return receipt, receiptErr + } + + nowMs := time.Now().UnixMilli() + createIfMissing := delta > 0 + account, err := r.lockAccount(ctx, tx, command.UserID, ledger.AssetCoin, createIfMissing, nowMs) + if err != nil { + return ledger.GameCoinChangeReceipt{}, err + } + if delta < 0 && account.AvailableAmount < -delta { + return ledger.GameCoinChangeReceipt{}, xerr.New(xerr.InsufficientBalance, "insufficient balance") + } + + transactionID := transactionID(command.AppCode, command.CommandID) + balanceAfter := account.AvailableAmount + delta + metadata := gameCoinMetadata{ + AppCode: command.AppCode, + UserID: command.UserID, + PlatformCode: command.PlatformCode, + GameID: command.GameID, + ProviderOrderID: command.ProviderOrderID, + ProviderRoundID: command.ProviderRoundID, + OpType: command.OpType, + AssetType: ledger.AssetCoin, + CoinAmount: command.CoinAmount, + AvailableDelta: delta, + RoomID: command.RoomID, + BalanceAfter: balanceAfter, + AppliedAtMS: nowMs, + } + if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizType, command.RequestHash, command.ProviderOrderID, metadata, nowMs); err != nil { + return ledger.GameCoinChangeReceipt{}, err + } + if err := r.applyAccountDelta(ctx, tx, account, delta, 0, nowMs); err != nil { + return ledger.GameCoinChangeReceipt{}, err + } + if err := r.insertEntry(ctx, tx, walletEntry{ + TransactionID: transactionID, + UserID: command.UserID, + AssetType: ledger.AssetCoin, + AvailableDelta: delta, + FrozenDelta: 0, + AvailableAfter: balanceAfter, + FrozenAfter: account.FrozenAmount, + RoomID: command.RoomID, + CreatedAtMS: nowMs, + }); err != nil { + return ledger.GameCoinChangeReceipt{}, err + } + if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{ + balanceChangedEvent(transactionID, command.CommandID, command.UserID, ledger.AssetCoin, delta, 0, balanceAfter, account.FrozenAmount, metadata, nowMs), + }); err != nil { + return ledger.GameCoinChangeReceipt{}, err + } + if err := tx.Commit(); err != nil { + return ledger.GameCoinChangeReceipt{}, err + } + + return receiptFromGameMetadata(transactionID, metadata, false), nil +} + // AdminCreditCoinSellerStock 在同一事务里给币商 COIN_SELLER_COIN 库存入账,并记录专用进货明细。 func (r *Repository) AdminCreditCoinSellerStock(ctx context.Context, command ledger.CoinSellerStockCreditCommand) (ledger.CoinSellerStockCreditReceipt, error) { if r == nil || r.db == nil { @@ -1126,6 +1216,22 @@ type taskRewardMetadata struct { GrantedAtMS int64 `json:"granted_at_ms"` } +type gameCoinMetadata struct { + AppCode string `json:"app_code"` + UserID int64 `json:"user_id"` + PlatformCode string `json:"platform_code"` + GameID string `json:"game_id"` + ProviderOrderID string `json:"provider_order_id"` + ProviderRoundID string `json:"provider_round_id"` + OpType string `json:"op_type"` + AssetType string `json:"asset_type"` + CoinAmount int64 `json:"coin_amount"` + AvailableDelta int64 `json:"available_delta"` + RoomID string `json:"room_id"` + BalanceAfter int64 `json:"balance_after"` + AppliedAtMS int64 `json:"applied_at_ms"` +} + type coinSellerTransferMetadata struct { AppCode string `json:"app_code"` SellerUserID int64 `json:"seller_user_id"` @@ -1214,6 +1320,32 @@ func receiptFromTaskRewardMetadata(transactionID string, metadata taskRewardMeta } } +func (r *Repository) receiptForGameTransaction(ctx context.Context, tx *sql.Tx, transactionID string) (ledger.GameCoinChangeReceipt, 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.GameCoinChangeReceipt{}, err + } + var metadata gameCoinMetadata + if err := json.Unmarshal([]byte(metadataJSON), &metadata); err != nil { + return ledger.GameCoinChangeReceipt{}, err + } + return receiptFromGameMetadata(transactionID, metadata, false), nil +} + +func receiptFromGameMetadata(transactionID string, metadata gameCoinMetadata, idempotentReplay bool) ledger.GameCoinChangeReceipt { + return ledger.GameCoinChangeReceipt{ + TransactionID: transactionID, + BalanceAfter: metadata.BalanceAfter, + IdempotentReplay: idempotentReplay, + } +} + func (r *Repository) receiptForCoinSellerStockTransaction(ctx context.Context, tx *sql.Tx, transactionID string) (ledger.CoinSellerStockCreditReceipt, error) { row := tx.QueryRowContext(ctx, `SELECT seller_user_id, stock_type, coin_amount, paid_currency_code, paid_amount_micro, @@ -1497,6 +1629,22 @@ func taskRewardRequestHash(command ledger.TaskRewardCommand) string { )) } +func gameBizTypeAndDelta(opType string, coinAmount int64) (string, int64, error) { + switch ledger.NormalizeGameOpType(opType) { + case ledger.GameOpDebit: + return bizTypeGameDebit, -coinAmount, nil + case ledger.GameOpCredit: + return bizTypeGameCredit, coinAmount, nil + case ledger.GameOpRefund: + return bizTypeGameRefund, coinAmount, nil + case ledger.GameOpReverse: + // 冲正首版按“从用户 COIN 扣回”处理;后续接入平台若语义不同,必须在 adapter 层拆成 debit/refund。 + return bizTypeGameReverse, -coinAmount, nil + default: + return "", 0, xerr.New(xerr.InvalidArgument, "game op_type is invalid") + } +} + func coinSellerStockRequestHash(command ledger.CoinSellerStockCreditCommand) string { return stableHash(fmt.Sprintf("coin_seller_stock|%s|%d|%s|%d|%s|%d|%s|%s|%d|%s", appcode.Normalize(command.AppCode), diff --git a/services/wallet-service/internal/transport/grpc/server.go b/services/wallet-service/internal/transport/grpc/server.go index 59250b96..8f4aa8e9 100644 --- a/services/wallet-service/internal/transport/grpc/server.go +++ b/services/wallet-service/internal/transport/grpc/server.go @@ -385,6 +385,32 @@ func (s *Server) CreditTaskReward(ctx context.Context, req *walletv1.CreditTaskR }, nil } +// ApplyGameCoinChange 处理 game-service 发起的游戏专用金币改账。 +func (s *Server) ApplyGameCoinChange(ctx context.Context, req *walletv1.ApplyGameCoinChangeRequest) (*walletv1.ApplyGameCoinChangeResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + receipt, err := s.svc.ApplyGameCoinChange(ctx, ledger.GameCoinChangeCommand{ + AppCode: req.GetAppCode(), + CommandID: req.GetCommandId(), + UserID: req.GetUserId(), + PlatformCode: req.GetPlatformCode(), + GameID: req.GetGameId(), + ProviderOrderID: req.GetProviderOrderId(), + ProviderRoundID: req.GetProviderRoundId(), + OpType: req.GetOpType(), + CoinAmount: req.GetCoinAmount(), + RoomID: req.GetRoomId(), + RequestHash: req.GetRequestHash(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.ApplyGameCoinChangeResponse{ + WalletTransactionId: receipt.TransactionID, + BalanceAfter: receipt.BalanceAfter, + IdempotentReplay: receipt.IdempotentReplay, + }, nil +} + // AdminCreditCoinSellerStock 处理后台币商进货或金币补偿,币商身份由 admin-server 先行校验。 func (s *Server) AdminCreditCoinSellerStock(ctx context.Context, req *walletv1.AdminCreditCoinSellerStockRequest) (*walletv1.AdminCreditCoinSellerStockResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) diff --git a/tools/trtc-smoke/README.md b/tools/trtc-smoke/README.md new file mode 100644 index 00000000..f012e777 --- /dev/null +++ b/tools/trtc-smoke/README.md @@ -0,0 +1,30 @@ +# TRTC Smoke Demo + +This local demo isolates Tencent RTC entry from the app flow: + +- The Go server reads `gateway-service` `tencent_rtc` config. +- The Go server signs a RTC UserSig with `pkg/tencentrtc`. +- The browser page loads Tencent TRTC Web SDK v5 and calls `enterRoom` with `strRoomId`. +- The browser never receives `secret_key`. + +Run: + +```bash +go run ./tools/trtc-smoke \ + -config services/gateway-service/configs/config.yaml \ + -addr 127.0.0.1:13100 +``` + +Open: + +```text +http://127.0.0.1:13100/ +``` + +Expected result: + +- `enterRoom.success` means `SDKAppID + SDKSecretKey + UserSig + strRoomId` are accepted by Tencent RTC from the Web SDK. +- `enterRoom.error` with the same `-3301 [-100038]` means the failure is still Tencent RTC account/service/region/package status, not the app room chain. +- If this Web demo succeeds but Flutter still fails, compare Flutter native scene/role initialization and cached token usage. + +Default mode is `live + audience` to stay close to the app's listener flow. Switch to `rtc` if you want to validate the default RTC room scene. diff --git a/tools/trtc-smoke/main.go b/tools/trtc-smoke/main.go new file mode 100644 index 00000000..c3be126f --- /dev/null +++ b/tools/trtc-smoke/main.go @@ -0,0 +1,366 @@ +package main + +import ( + "encoding/json" + "errors" + "flag" + "fmt" + "log" + "net/http" + "os" + "strconv" + "strings" + "time" + + "gopkg.in/yaml.v3" + "hyapp/pkg/tencentrtc" +) + +type fileConfig struct { + TencentRTC rtcYAMLConfig `yaml:"tencent_rtc"` +} + +type rtcYAMLConfig struct { + Enabled bool `yaml:"enabled"` + SDKAppID int64 `yaml:"sdk_app_id"` + SecretKey string `yaml:"secret_key"` + UserSigTTL time.Duration `yaml:"user_sig_ttl"` + RoomIDType string `yaml:"room_id_type"` + AppScene string `yaml:"app_scene"` +} + +type server struct { + rtcConfig tencentrtc.TokenConfig +} + +func main() { + configPath := flag.String("config", "services/gateway-service/configs/config.yaml", "gateway config containing tencent_rtc") + addr := flag.String("addr", "127.0.0.1:13100", "listen address") + flag.Parse() + + rtcConfig, err := loadRTCConfig(*configPath) + if err != nil { + log.Fatalf("load rtc config failed: %v", err) + } + if err := tencentrtc.ValidateConfig(rtcConfig); err != nil { + log.Fatalf("rtc config is not usable: %v", err) + } + + s := &server{rtcConfig: rtcConfig} + mux := http.NewServeMux() + mux.HandleFunc("/", s.index) + mux.HandleFunc("/api/token", s.token) + mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte("ok")) + }) + + log.Printf("TRTC smoke demo listening on http://%s", *addr) + log.Printf("SDKAppID=%d room_id_type=%s app_scene=%s", rtcConfig.SDKAppID, rtcConfig.RoomIDType, rtcConfig.AppScene) + if err := http.ListenAndServe(*addr, mux); err != nil { + log.Fatal(err) + } +} + +func loadRTCConfig(path string) (tencentrtc.TokenConfig, error) { + content, err := os.ReadFile(path) + if err != nil { + return tencentrtc.TokenConfig{}, err + } + var cfg fileConfig + if err := yaml.Unmarshal(content, &cfg); err != nil { + return tencentrtc.TokenConfig{}, err + } + if cfg.TencentRTC.UserSigTTL <= 0 { + return tencentrtc.TokenConfig{}, errors.New("tencent_rtc.user_sig_ttl is required") + } + return tencentrtc.TokenConfig{ + Enabled: cfg.TencentRTC.Enabled, + SDKAppID: cfg.TencentRTC.SDKAppID, + SecretKey: strings.TrimSpace(cfg.TencentRTC.SecretKey), + TTL: cfg.TencentRTC.UserSigTTL, + RoomIDType: strings.TrimSpace(cfg.TencentRTC.RoomIDType), + AppScene: strings.TrimSpace(cfg.TencentRTC.AppScene), + }, nil +} + +func (s *server) index(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + http.ServeContent(w, r, "index.html", time.Now(), strings.NewReader(indexHTML)) +} + +func (s *server) token(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + if r.Method != http.MethodGet { + http.Error(w, `{"error":"method not allowed"}`, http.StatusMethodNotAllowed) + return + } + + userID, err := parseUserID(r.URL.Query().Get("user_id")) + if err != nil { + writeJSONError(w, http.StatusBadRequest, err) + return + } + roomID := strings.TrimSpace(r.URL.Query().Get("room_id")) + if roomID == "" { + writeJSONError(w, http.StatusBadRequest, errors.New("room_id is required")) + return + } + + token, err := tencentrtc.GenerateToken(s.rtcConfig, userID, roomID, time.Now().UTC()) + if err != nil { + writeJSONError(w, http.StatusBadRequest, err) + return + } + _ = json.NewEncoder(w).Encode(token) +} + +func parseUserID(raw string) (int64, error) { + raw = strings.TrimSpace(raw) + if raw == "" { + return 0, errors.New("user_id is required") + } + value, err := strconv.ParseInt(raw, 10, 64) + if err != nil || value <= 0 { + return 0, fmt.Errorf("user_id must be a positive int64") + } + return value, nil +} + +func writeJSONError(w http.ResponseWriter, status int, err error) { + w.WriteHeader(status) + _ = json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) +} + +const indexHTML = ` + + + + + TRTC Smoke + + + + +
+

TRTC Smoke

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + + +
+
+
+

Ready

+

+    
+
+ + + +`