Merge branch 'test'

This commit is contained in:
zhx 2026-07-03 17:38:41 +08:00
commit c53b710539
61 changed files with 7065 additions and 4058 deletions

File diff suppressed because it is too large Load Diff

View File

@ -526,6 +526,8 @@ message RoomSnapshot {
RoomRocketState rocket = 20;
// ban_states ban ban_user_ids
repeated RoomBanState ban_states = 21;
// online_count lite online_users 线
int32 online_count = 22;
}
// RoomBackgroundImage
@ -656,6 +658,8 @@ message JoinRoomRequest {
bool actor_is_robot = 7;
// actor_display_profile room-service IM/outbox RoomState
RoomUserDisplayProfile actor_display_profile = 8;
// response_mode=lite Room Cell outbox IM
string response_mode = 9;
}
// JoinRoomResponse
@ -1189,6 +1193,18 @@ message ListRoomsRequest {
bool all_visible_regions = 10;
}
// ListRoomsByOwnersRequest active
// owner_user_ids gateway
message ListRoomsByOwnersRequest {
RequestMeta meta = 1;
int64 viewer_user_id = 2;
repeated int64 owner_user_ids = 3;
string tab = 4;
string cursor = 5;
int32 limit = 6;
string query = 7;
}
// ListRoomFeedsRequest Mine visited/friend/following/followed
//
message ListRoomFeedsRequest {
@ -1456,6 +1472,7 @@ service RoomQueryService {
rpc AdminListRobotRooms(AdminListRobotRoomsRequest) returns (AdminListRobotRoomsResponse);
rpc AdminFilterAvailableRoomRobots(AdminFilterAvailableRoomRobotsRequest) returns (AdminFilterAvailableRoomRobotsResponse);
rpc ListRooms(ListRoomsRequest) returns (ListRoomsResponse);
rpc ListRoomsByOwners(ListRoomsByOwnersRequest) returns (ListRoomsResponse);
rpc ListRoomFeeds(ListRoomFeedsRequest) returns (ListRoomsResponse);
rpc ListRoomGiftLeaderboard(ListRoomGiftLeaderboardRequest) returns (ListRoomGiftLeaderboardResponse);
rpc GetMyRoom(GetMyRoomRequest) returns (GetMyRoomResponse);

View File

@ -1570,6 +1570,7 @@ const (
RoomQueryService_AdminListRobotRooms_FullMethodName = "/hyapp.room.v1.RoomQueryService/AdminListRobotRooms"
RoomQueryService_AdminFilterAvailableRoomRobots_FullMethodName = "/hyapp.room.v1.RoomQueryService/AdminFilterAvailableRoomRobots"
RoomQueryService_ListRooms_FullMethodName = "/hyapp.room.v1.RoomQueryService/ListRooms"
RoomQueryService_ListRoomsByOwners_FullMethodName = "/hyapp.room.v1.RoomQueryService/ListRoomsByOwners"
RoomQueryService_ListRoomFeeds_FullMethodName = "/hyapp.room.v1.RoomQueryService/ListRoomFeeds"
RoomQueryService_ListRoomGiftLeaderboard_FullMethodName = "/hyapp.room.v1.RoomQueryService/ListRoomGiftLeaderboard"
RoomQueryService_GetMyRoom_FullMethodName = "/hyapp.room.v1.RoomQueryService/GetMyRoom"
@ -1596,6 +1597,7 @@ type RoomQueryServiceClient interface {
AdminListRobotRooms(ctx context.Context, in *AdminListRobotRoomsRequest, opts ...grpc.CallOption) (*AdminListRobotRoomsResponse, error)
AdminFilterAvailableRoomRobots(ctx context.Context, in *AdminFilterAvailableRoomRobotsRequest, opts ...grpc.CallOption) (*AdminFilterAvailableRoomRobotsResponse, error)
ListRooms(ctx context.Context, in *ListRoomsRequest, opts ...grpc.CallOption) (*ListRoomsResponse, error)
ListRoomsByOwners(ctx context.Context, in *ListRoomsByOwnersRequest, opts ...grpc.CallOption) (*ListRoomsResponse, error)
ListRoomFeeds(ctx context.Context, in *ListRoomFeedsRequest, opts ...grpc.CallOption) (*ListRoomsResponse, error)
ListRoomGiftLeaderboard(ctx context.Context, in *ListRoomGiftLeaderboardRequest, opts ...grpc.CallOption) (*ListRoomGiftLeaderboardResponse, error)
GetMyRoom(ctx context.Context, in *GetMyRoomRequest, opts ...grpc.CallOption) (*GetMyRoomResponse, error)
@ -1705,6 +1707,16 @@ func (c *roomQueryServiceClient) ListRooms(ctx context.Context, in *ListRoomsReq
return out, nil
}
func (c *roomQueryServiceClient) ListRoomsByOwners(ctx context.Context, in *ListRoomsByOwnersRequest, opts ...grpc.CallOption) (*ListRoomsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListRoomsResponse)
err := c.cc.Invoke(ctx, RoomQueryService_ListRoomsByOwners_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *roomQueryServiceClient) ListRoomFeeds(ctx context.Context, in *ListRoomFeedsRequest, opts ...grpc.CallOption) (*ListRoomsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListRoomsResponse)
@ -1810,6 +1822,7 @@ type RoomQueryServiceServer interface {
AdminListRobotRooms(context.Context, *AdminListRobotRoomsRequest) (*AdminListRobotRoomsResponse, error)
AdminFilterAvailableRoomRobots(context.Context, *AdminFilterAvailableRoomRobotsRequest) (*AdminFilterAvailableRoomRobotsResponse, error)
ListRooms(context.Context, *ListRoomsRequest) (*ListRoomsResponse, error)
ListRoomsByOwners(context.Context, *ListRoomsByOwnersRequest) (*ListRoomsResponse, error)
ListRoomFeeds(context.Context, *ListRoomFeedsRequest) (*ListRoomsResponse, error)
ListRoomGiftLeaderboard(context.Context, *ListRoomGiftLeaderboardRequest) (*ListRoomGiftLeaderboardResponse, error)
GetMyRoom(context.Context, *GetMyRoomRequest) (*GetMyRoomResponse, error)
@ -1856,6 +1869,9 @@ func (UnimplementedRoomQueryServiceServer) AdminFilterAvailableRoomRobots(contex
func (UnimplementedRoomQueryServiceServer) ListRooms(context.Context, *ListRoomsRequest) (*ListRoomsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRooms not implemented")
}
func (UnimplementedRoomQueryServiceServer) ListRoomsByOwners(context.Context, *ListRoomsByOwnersRequest) (*ListRoomsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomsByOwners not implemented")
}
func (UnimplementedRoomQueryServiceServer) ListRoomFeeds(context.Context, *ListRoomFeedsRequest) (*ListRoomsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomFeeds not implemented")
}
@ -2066,6 +2082,24 @@ func _RoomQueryService_ListRooms_Handler(srv interface{}, ctx context.Context, d
return interceptor(ctx, in, info, handler)
}
func _RoomQueryService_ListRoomsByOwners_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListRoomsByOwnersRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RoomQueryServiceServer).ListRoomsByOwners(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: RoomQueryService_ListRoomsByOwners_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RoomQueryServiceServer).ListRoomsByOwners(ctx, req.(*ListRoomsByOwnersRequest))
}
return interceptor(ctx, in, info, handler)
}
func _RoomQueryService_ListRoomFeeds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListRoomFeedsRequest)
if err := dec(in); err != nil {
@ -2271,6 +2305,10 @@ var RoomQueryService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ListRooms",
Handler: _RoomQueryService_ListRooms_Handler,
},
{
MethodName: "ListRoomsByOwners",
Handler: _RoomQueryService_ListRoomsByOwners_Handler,
},
{
MethodName: "ListRoomFeeds",
Handler: _RoomQueryService_ListRoomFeeds_Handler,

View File

@ -5266,6 +5266,136 @@ func (x *SetAgencyJoinEnabledResponse) GetAgency() *Agency {
return nil
}
type UpdateAgencyProfileRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
CommandId string `protobuf:"bytes,2,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"`
OperatorUserId int64 `protobuf:"varint,3,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"`
AgencyId int64 `protobuf:"varint,4,opt,name=agency_id,json=agencyId,proto3" json:"agency_id,omitempty"`
Name *string `protobuf:"bytes,5,opt,name=name,proto3,oneof" json:"name,omitempty"`
Avatar *string `protobuf:"bytes,6,opt,name=avatar,proto3,oneof" json:"avatar,omitempty"`
}
func (x *UpdateAgencyProfileRequest) Reset() {
*x = UpdateAgencyProfileRequest{}
mi := &file_proto_user_v1_host_proto_msgTypes[77]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UpdateAgencyProfileRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UpdateAgencyProfileRequest) ProtoMessage() {}
func (x *UpdateAgencyProfileRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_user_v1_host_proto_msgTypes[77]
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 UpdateAgencyProfileRequest.ProtoReflect.Descriptor instead.
func (*UpdateAgencyProfileRequest) Descriptor() ([]byte, []int) {
return file_proto_user_v1_host_proto_rawDescGZIP(), []int{77}
}
func (x *UpdateAgencyProfileRequest) GetMeta() *RequestMeta {
if x != nil {
return x.Meta
}
return nil
}
func (x *UpdateAgencyProfileRequest) GetCommandId() string {
if x != nil {
return x.CommandId
}
return ""
}
func (x *UpdateAgencyProfileRequest) GetOperatorUserId() int64 {
if x != nil {
return x.OperatorUserId
}
return 0
}
func (x *UpdateAgencyProfileRequest) GetAgencyId() int64 {
if x != nil {
return x.AgencyId
}
return 0
}
func (x *UpdateAgencyProfileRequest) GetName() string {
if x != nil && x.Name != nil {
return *x.Name
}
return ""
}
func (x *UpdateAgencyProfileRequest) GetAvatar() string {
if x != nil && x.Avatar != nil {
return *x.Avatar
}
return ""
}
type UpdateAgencyProfileResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Agency *Agency `protobuf:"bytes,1,opt,name=agency,proto3" json:"agency,omitempty"`
}
func (x *UpdateAgencyProfileResponse) Reset() {
*x = UpdateAgencyProfileResponse{}
mi := &file_proto_user_v1_host_proto_msgTypes[78]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UpdateAgencyProfileResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UpdateAgencyProfileResponse) ProtoMessage() {}
func (x *UpdateAgencyProfileResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_user_v1_host_proto_msgTypes[78]
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 UpdateAgencyProfileResponse.ProtoReflect.Descriptor instead.
func (*UpdateAgencyProfileResponse) Descriptor() ([]byte, []int) {
return file_proto_user_v1_host_proto_rawDescGZIP(), []int{78}
}
func (x *UpdateAgencyProfileResponse) GetAgency() *Agency {
if x != nil {
return x.Agency
}
return nil
}
var File_proto_user_v1_host_proto protoreflect.FileDescriptor
var file_proto_user_v1_host_proto_rawDesc = []byte{
@ -6128,226 +6258,254 @@ var file_proto_user_v1_host_proto_rawDesc = []byte{
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65,
0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x06, 0x61, 0x67, 0x65,
0x6e, 0x63, 0x79, 0x32, 0xd5, 0x12, 0x0a, 0x0f, 0x55, 0x73, 0x65, 0x72, 0x48, 0x6f, 0x73, 0x74,
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63,
0x68, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68,
0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x54,
0x6f, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x6f, 0x41,
0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70,
0x6c, 0x79, 0x54, 0x6f, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x78, 0x0a, 0x17, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x41, 0x67, 0x65, 0x6e,
0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65,
0x76, 0x69, 0x65, 0x77, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76,
0x69, 0x65, 0x77, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e,
0x4b, 0x69, 0x63, 0x6b, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x24,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4b,
0x69, 0x63, 0x6b, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65,
0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x69, 0x63, 0x6b, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48,
0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x49,
0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x22, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x69,
0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x42, 0x44,
0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x51, 0x0a, 0x0a, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x12,
0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
0x31, 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65,
0x61, 0x64, 0x65, 0x72, 0x42, 0x44, 0x73, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x6e, 0x63, 0x79, 0x22, 0xfc, 0x01, 0x0a, 0x1a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x67,
0x65, 0x6e, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65,
0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49,
0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73,
0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65,
0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61,
0x67, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08,
0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01,
0x01, 0x12, 0x1b, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28,
0x09, 0x48, 0x01, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x88, 0x01, 0x01, 0x42, 0x07,
0x0a, 0x05, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x61, 0x76, 0x61, 0x74,
0x61, 0x72, 0x22, 0x4c, 0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e,
0x63, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
0x31, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x63, 0x79,
0x32, 0xc3, 0x13, 0x0a, 0x0f, 0x55, 0x73, 0x65, 0x72, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x41, 0x67,
0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75,
0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x41, 0x67, 0x65,
0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x61,
0x72, 0x63, 0x68, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x6f, 0x41, 0x67,
0x65, 0x6e, 0x63, 0x79, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65,
0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x6f, 0x41, 0x67, 0x65, 0x6e,
0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x54,
0x6f, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x78, 0x0a, 0x17, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41,
0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x65,
0x77, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77,
0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x4b, 0x69, 0x63,
0x6b, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x24, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x69, 0x63, 0x6b,
0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
0x31, 0x2e, 0x4b, 0x69, 0x63, 0x6b, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x49, 0x6e, 0x76, 0x69,
0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x41,
0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x76,
0x69, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x4b, 0x0a, 0x08, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x42, 0x44, 0x12, 0x1e, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e,
0x76, 0x69, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e,
0x76, 0x69, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51,
0x0a, 0x0a, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x20, 0x2e, 0x68,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x76,
0x69, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49,
0x6e, 0x76, 0x69, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x60, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65,
0x72, 0x42, 0x44, 0x73, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65,
0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65,
0x72, 0x42, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61,
0x64, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x2a, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65,
0x61, 0x64, 0x65, 0x72, 0x42, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c,
0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x44, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44,
0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x2a,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c,
0x69, 0x73, 0x74, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x63,
0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42,
0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x42,
0x44, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44,
0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f,
0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x29, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c,
0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49,
0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c,
0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15,
0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73,
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x6c,
0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x61, 0x64, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x41, 0x67,
0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75,
0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x44, 0x41, 0x67, 0x65,
0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73,
0x74, 0x42, 0x44, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49,
0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52,
0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73,
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e,
0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x66, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69,
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75,
0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e,
0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x50, 0x72, 0x6f,
0x63, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e,
0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x5d, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69,
0x6c, 0x65, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74,
0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x6f, 0x0a, 0x14, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50,
0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74,
0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74,
0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x57, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65,
0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
0x2e, 0x47, 0x65, 0x74, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65,
0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c,
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x47, 0x65, 0x74,
0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c,
0x65, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50,
0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e,
0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a,
0x0e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12,
0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73,
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f,
0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14,
0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66,
0x69, 0x6c, 0x65, 0x73, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65,
0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73,
0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x6f,
0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a,
0x0c, 0x47, 0x65, 0x74, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x22, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65,
0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69,
0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x90, 0x01, 0x0a, 0x1f, 0x4c,
0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c,
0x6c, 0x65, 0x72, 0x73, 0x49, 0x6e, 0x4d, 0x79, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x35,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c,
0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c,
0x6c, 0x65, 0x72, 0x73, 0x49, 0x6e, 0x4d, 0x79, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73,
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65,
0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x49, 0x6e, 0x4d, 0x79, 0x52,
0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a,
0x12, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x6d,
0x61, 0x72, 0x79, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x53,
0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65,
0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x41,
0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73,
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75,
0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, 0x17, 0x43, 0x68, 0x65, 0x63,
0x6b, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c,
0x69, 0x74, 0x79, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73,
0x73, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73,
0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x63, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d,
0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75,
0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79,
0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27,
0x74, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x44, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x69,
0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x2a,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47,
0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x41, 0x67,
0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73,
0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e,
0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66,
0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f,
0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x90, 0x01, 0x0a, 0x1f, 0x4c, 0x69, 0x73, 0x74,
0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72,
0x73, 0x49, 0x6e, 0x4d, 0x79, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72,
0x73, 0x49, 0x6e, 0x4d, 0x79, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x69,
0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x49, 0x6e, 0x4d, 0x79, 0x52, 0x65, 0x67, 0x69,
0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x47, 0x65,
0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79,
0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x6d,
0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73,
0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e,
0x63, 0x79, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, 0x17, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x75,
0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79,
0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x61,
0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x61, 0x70,
0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x63, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62,
0x65, 0x72, 0x73, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d,
0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41,
0x67, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63,
0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2b, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65,
0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xb1, 0x08, 0x0a, 0x14,
0x55, 0x73, 0x65, 0x72, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44,
0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75,
0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x4c,
0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65,
0x61, 0x74, 0x65, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x12,
0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x54, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x42, 0x44, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
0x53, 0x65, 0x74, 0x42, 0x44, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x42, 0x44, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61,
0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67,
0x65, 0x6e, 0x63, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12,
0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x66,
0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xb1, 0x08, 0x0a, 0x14, 0x55, 0x73, 0x65, 0x72, 0x48,
0x6f, 0x73, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
0x5d, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65,
0x72, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44,
0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b,
0x0a, 0x08, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x44, 0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74,
0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c,
0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x53,
0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74,
0x75, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e,
0x65, 0x42, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74,
0x65, 0x42, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x53,
0x65, 0x74, 0x42, 0x44, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x42, 0x44,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65,
0x74, 0x42, 0x44, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x63, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53,
0x65, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73,
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x69, 0x6e,
0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72,
0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x69,
0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65,
0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x43, 0x72, 0x65,
0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72,
0x65, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x41, 0x64, 0x64, 0x41, 0x67,
0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x41, 0x64,
0x64, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x41, 0x64, 0x64, 0x41, 0x67, 0x65, 0x6e, 0x63,
0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a,
0x0b, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x21, 0x2e, 0x68,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x6f,
0x73, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
0x43, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x67, 0x65,
0x6e, 0x63, 0x79, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x67,
0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0f,
0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75,
0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f,
0x0a, 0x14, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45,
0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75,
0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79,
0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x6f, 0x69, 0x6e,
0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42,
0x26, 0x5a, 0x24, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61,
0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31,
0x3b, 0x75, 0x73, 0x65, 0x72, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x69, 0x6e,
0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x67,
0x65, 0x6e, 0x63, 0x79, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65,
0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63,
0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41,
0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a,
0x12, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x41, 0x64, 0x64, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48,
0x6f, 0x73, 0x74, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x41, 0x64, 0x64, 0x41, 0x67, 0x65, 0x6e,
0x63, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64,
0x6d, 0x69, 0x6e, 0x41, 0x64, 0x64, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x73, 0x74,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x43, 0x6c, 0x6f, 0x73,
0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x67, 0x65,
0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65,
0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57,
0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x22,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44,
0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x41, 0x67,
0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x67,
0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x53, 0x65, 0x74,
0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65,
0x64, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45,
0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65,
0x74, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c,
0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72,
0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -6362,7 +6520,7 @@ func file_proto_user_v1_host_proto_rawDescGZIP() []byte {
return file_proto_user_v1_host_proto_rawDescData
}
var file_proto_user_v1_host_proto_msgTypes = make([]protoimpl.MessageInfo, 78)
var file_proto_user_v1_host_proto_msgTypes = make([]protoimpl.MessageInfo, 80)
var file_proto_user_v1_host_proto_goTypes = []any{
(*HostProfile)(nil), // 0: hyapp.user.v1.HostProfile
(*Agency)(nil), // 1: hyapp.user.v1.Agency
@ -6441,162 +6599,168 @@ var file_proto_user_v1_host_proto_goTypes = []any{
(*SetAgencyStatusResponse)(nil), // 74: hyapp.user.v1.SetAgencyStatusResponse
(*SetAgencyJoinEnabledRequest)(nil), // 75: hyapp.user.v1.SetAgencyJoinEnabledRequest
(*SetAgencyJoinEnabledResponse)(nil), // 76: hyapp.user.v1.SetAgencyJoinEnabledResponse
nil, // 77: hyapp.user.v1.BatchGetHostProfilesResponse.HostProfilesEntry
(*RequestMeta)(nil), // 78: hyapp.user.v1.RequestMeta
(*UpdateAgencyProfileRequest)(nil), // 77: hyapp.user.v1.UpdateAgencyProfileRequest
(*UpdateAgencyProfileResponse)(nil), // 78: hyapp.user.v1.UpdateAgencyProfileResponse
nil, // 79: hyapp.user.v1.BatchGetHostProfilesResponse.HostProfilesEntry
(*RequestMeta)(nil), // 80: hyapp.user.v1.RequestMeta
}
var file_proto_user_v1_host_proto_depIdxs = []int32{
78, // 0: hyapp.user.v1.SearchAgenciesRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 0: hyapp.user.v1.SearchAgenciesRequest.meta:type_name -> hyapp.user.v1.RequestMeta
1, // 1: hyapp.user.v1.SearchAgenciesResponse.agencies:type_name -> hyapp.user.v1.Agency
78, // 2: hyapp.user.v1.ApplyToAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 2: hyapp.user.v1.ApplyToAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta
7, // 3: hyapp.user.v1.ApplyToAgencyResponse.application:type_name -> hyapp.user.v1.AgencyApplication
78, // 4: hyapp.user.v1.ReviewAgencyApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 4: hyapp.user.v1.ReviewAgencyApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta
7, // 5: hyapp.user.v1.ReviewAgencyApplicationResponse.application:type_name -> hyapp.user.v1.AgencyApplication
0, // 6: hyapp.user.v1.ReviewAgencyApplicationResponse.host_profile:type_name -> hyapp.user.v1.HostProfile
6, // 7: hyapp.user.v1.ReviewAgencyApplicationResponse.membership:type_name -> hyapp.user.v1.AgencyMembership
78, // 8: hyapp.user.v1.KickAgencyHostRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 8: hyapp.user.v1.KickAgencyHostRequest.meta:type_name -> hyapp.user.v1.RequestMeta
6, // 9: hyapp.user.v1.KickAgencyHostResponse.membership:type_name -> hyapp.user.v1.AgencyMembership
0, // 10: hyapp.user.v1.KickAgencyHostResponse.host_profile:type_name -> hyapp.user.v1.HostProfile
78, // 11: hyapp.user.v1.InviteAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 11: hyapp.user.v1.InviteAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta
8, // 12: hyapp.user.v1.InviteAgencyResponse.invitation:type_name -> hyapp.user.v1.RoleInvitation
78, // 13: hyapp.user.v1.InviteBDRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 13: hyapp.user.v1.InviteBDRequest.meta:type_name -> hyapp.user.v1.RequestMeta
8, // 14: hyapp.user.v1.InviteBDResponse.invitation:type_name -> hyapp.user.v1.RoleInvitation
78, // 15: hyapp.user.v1.InviteHostRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 15: hyapp.user.v1.InviteHostRequest.meta:type_name -> hyapp.user.v1.RequestMeta
8, // 16: hyapp.user.v1.InviteHostResponse.invitation:type_name -> hyapp.user.v1.RoleInvitation
78, // 17: hyapp.user.v1.ListBDLeaderBDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 17: hyapp.user.v1.ListBDLeaderBDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta
2, // 18: hyapp.user.v1.ListBDLeaderBDsResponse.bd_profiles:type_name -> hyapp.user.v1.BDProfile
78, // 19: hyapp.user.v1.ListBDLeaderAgenciesRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 19: hyapp.user.v1.ListBDLeaderAgenciesRequest.meta:type_name -> hyapp.user.v1.RequestMeta
1, // 20: hyapp.user.v1.ListBDLeaderAgenciesResponse.agencies:type_name -> hyapp.user.v1.Agency
78, // 21: hyapp.user.v1.ListBDAgenciesRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 21: hyapp.user.v1.ListBDAgenciesRequest.meta:type_name -> hyapp.user.v1.RequestMeta
1, // 22: hyapp.user.v1.ListBDAgenciesResponse.agencies:type_name -> hyapp.user.v1.Agency
78, // 23: hyapp.user.v1.ListRoleInvitationsRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 23: hyapp.user.v1.ListRoleInvitationsRequest.meta:type_name -> hyapp.user.v1.RequestMeta
8, // 24: hyapp.user.v1.ListRoleInvitationsResponse.invitations:type_name -> hyapp.user.v1.RoleInvitation
78, // 25: hyapp.user.v1.GetRoleInvitationRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 25: hyapp.user.v1.GetRoleInvitationRequest.meta:type_name -> hyapp.user.v1.RequestMeta
8, // 26: hyapp.user.v1.GetRoleInvitationResponse.invitation:type_name -> hyapp.user.v1.RoleInvitation
78, // 27: hyapp.user.v1.ProcessRoleInvitationRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 27: hyapp.user.v1.ProcessRoleInvitationRequest.meta:type_name -> hyapp.user.v1.RequestMeta
8, // 28: hyapp.user.v1.ProcessRoleInvitationResponse.invitation:type_name -> hyapp.user.v1.RoleInvitation
0, // 29: hyapp.user.v1.ProcessRoleInvitationResponse.host_profile:type_name -> hyapp.user.v1.HostProfile
1, // 30: hyapp.user.v1.ProcessRoleInvitationResponse.agency:type_name -> hyapp.user.v1.Agency
6, // 31: hyapp.user.v1.ProcessRoleInvitationResponse.membership:type_name -> hyapp.user.v1.AgencyMembership
2, // 32: hyapp.user.v1.ProcessRoleInvitationResponse.bd_profile:type_name -> hyapp.user.v1.BDProfile
78, // 33: hyapp.user.v1.GetHostProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 33: hyapp.user.v1.GetHostProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta
0, // 34: hyapp.user.v1.GetHostProfileResponse.host_profile:type_name -> hyapp.user.v1.HostProfile
78, // 35: hyapp.user.v1.BatchGetHostProfilesRequest.meta:type_name -> hyapp.user.v1.RequestMeta
77, // 36: hyapp.user.v1.BatchGetHostProfilesResponse.host_profiles:type_name -> hyapp.user.v1.BatchGetHostProfilesResponse.HostProfilesEntry
78, // 37: hyapp.user.v1.GetBDProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 35: hyapp.user.v1.BatchGetHostProfilesRequest.meta:type_name -> hyapp.user.v1.RequestMeta
79, // 36: hyapp.user.v1.BatchGetHostProfilesResponse.host_profiles:type_name -> hyapp.user.v1.BatchGetHostProfilesResponse.HostProfilesEntry
80, // 37: hyapp.user.v1.GetBDProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta
2, // 38: hyapp.user.v1.GetBDProfileResponse.bd_profile:type_name -> hyapp.user.v1.BDProfile
78, // 39: hyapp.user.v1.GetCoinSellerProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 39: hyapp.user.v1.GetCoinSellerProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta
3, // 40: hyapp.user.v1.GetCoinSellerProfileResponse.coin_seller_profile:type_name -> hyapp.user.v1.CoinSellerProfile
78, // 41: hyapp.user.v1.ListActiveCoinSellersInMyRegionRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 41: hyapp.user.v1.ListActiveCoinSellersInMyRegionRequest.meta:type_name -> hyapp.user.v1.RequestMeta
4, // 42: hyapp.user.v1.ListActiveCoinSellersInMyRegionResponse.coin_sellers:type_name -> hyapp.user.v1.CoinSellerListItem
78, // 43: hyapp.user.v1.GetUserRoleSummaryRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 43: hyapp.user.v1.GetUserRoleSummaryRequest.meta:type_name -> hyapp.user.v1.RequestMeta
5, // 44: hyapp.user.v1.GetUserRoleSummaryResponse.summary:type_name -> hyapp.user.v1.UserRoleSummary
78, // 45: hyapp.user.v1.GetAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 45: hyapp.user.v1.GetAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta
1, // 46: hyapp.user.v1.GetAgencyResponse.agency:type_name -> hyapp.user.v1.Agency
78, // 47: hyapp.user.v1.CheckBusinessCapabilityRequest.meta:type_name -> hyapp.user.v1.RequestMeta
78, // 48: hyapp.user.v1.GetAgencyMembersRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 47: hyapp.user.v1.CheckBusinessCapabilityRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 48: hyapp.user.v1.GetAgencyMembersRequest.meta:type_name -> hyapp.user.v1.RequestMeta
6, // 49: hyapp.user.v1.GetAgencyMembersResponse.memberships:type_name -> hyapp.user.v1.AgencyMembership
78, // 50: hyapp.user.v1.GetAgencyApplicationsRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 50: hyapp.user.v1.GetAgencyApplicationsRequest.meta:type_name -> hyapp.user.v1.RequestMeta
7, // 51: hyapp.user.v1.GetAgencyApplicationsResponse.applications:type_name -> hyapp.user.v1.AgencyApplication
78, // 52: hyapp.user.v1.CreateBDLeaderRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 52: hyapp.user.v1.CreateBDLeaderRequest.meta:type_name -> hyapp.user.v1.RequestMeta
2, // 53: hyapp.user.v1.CreateBDLeaderResponse.bd_profile:type_name -> hyapp.user.v1.BDProfile
78, // 54: hyapp.user.v1.CreateBDRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 54: hyapp.user.v1.CreateBDRequest.meta:type_name -> hyapp.user.v1.RequestMeta
2, // 55: hyapp.user.v1.CreateBDResponse.bd_profile:type_name -> hyapp.user.v1.BDProfile
78, // 56: hyapp.user.v1.SetBDStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 56: hyapp.user.v1.SetBDStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta
2, // 57: hyapp.user.v1.SetBDStatusResponse.bd_profile:type_name -> hyapp.user.v1.BDProfile
78, // 58: hyapp.user.v1.CreateCoinSellerRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 58: hyapp.user.v1.CreateCoinSellerRequest.meta:type_name -> hyapp.user.v1.RequestMeta
3, // 59: hyapp.user.v1.CreateCoinSellerResponse.coin_seller_profile:type_name -> hyapp.user.v1.CoinSellerProfile
78, // 60: hyapp.user.v1.SetCoinSellerStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 60: hyapp.user.v1.SetCoinSellerStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta
3, // 61: hyapp.user.v1.SetCoinSellerStatusResponse.coin_seller_profile:type_name -> hyapp.user.v1.CoinSellerProfile
78, // 62: hyapp.user.v1.CreateAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 62: hyapp.user.v1.CreateAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta
1, // 63: hyapp.user.v1.CreateAgencyResponse.agency:type_name -> hyapp.user.v1.Agency
0, // 64: hyapp.user.v1.CreateAgencyResponse.host_profile:type_name -> hyapp.user.v1.HostProfile
6, // 65: hyapp.user.v1.CreateAgencyResponse.membership:type_name -> hyapp.user.v1.AgencyMembership
78, // 66: hyapp.user.v1.AdminAddAgencyHostRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 66: hyapp.user.v1.AdminAddAgencyHostRequest.meta:type_name -> hyapp.user.v1.RequestMeta
1, // 67: hyapp.user.v1.AdminAddAgencyHostResponse.agency:type_name -> hyapp.user.v1.Agency
0, // 68: hyapp.user.v1.AdminAddAgencyHostResponse.host_profile:type_name -> hyapp.user.v1.HostProfile
6, // 69: hyapp.user.v1.AdminAddAgencyHostResponse.membership:type_name -> hyapp.user.v1.AgencyMembership
78, // 70: hyapp.user.v1.CloseAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 70: hyapp.user.v1.CloseAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta
1, // 71: hyapp.user.v1.CloseAgencyResponse.agency:type_name -> hyapp.user.v1.Agency
78, // 72: hyapp.user.v1.DeleteAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 72: hyapp.user.v1.DeleteAgencyRequest.meta:type_name -> hyapp.user.v1.RequestMeta
1, // 73: hyapp.user.v1.DeleteAgencyResponse.agency:type_name -> hyapp.user.v1.Agency
78, // 74: hyapp.user.v1.SetAgencyStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 74: hyapp.user.v1.SetAgencyStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta
1, // 75: hyapp.user.v1.SetAgencyStatusResponse.agency:type_name -> hyapp.user.v1.Agency
78, // 76: hyapp.user.v1.SetAgencyJoinEnabledRequest.meta:type_name -> hyapp.user.v1.RequestMeta
80, // 76: hyapp.user.v1.SetAgencyJoinEnabledRequest.meta:type_name -> hyapp.user.v1.RequestMeta
1, // 77: hyapp.user.v1.SetAgencyJoinEnabledResponse.agency:type_name -> hyapp.user.v1.Agency
0, // 78: hyapp.user.v1.BatchGetHostProfilesResponse.HostProfilesEntry.value:type_name -> hyapp.user.v1.HostProfile
9, // 79: hyapp.user.v1.UserHostService.SearchAgencies:input_type -> hyapp.user.v1.SearchAgenciesRequest
11, // 80: hyapp.user.v1.UserHostService.ApplyToAgency:input_type -> hyapp.user.v1.ApplyToAgencyRequest
13, // 81: hyapp.user.v1.UserHostService.ReviewAgencyApplication:input_type -> hyapp.user.v1.ReviewAgencyApplicationRequest
15, // 82: hyapp.user.v1.UserHostService.KickAgencyHost:input_type -> hyapp.user.v1.KickAgencyHostRequest
17, // 83: hyapp.user.v1.UserHostService.InviteAgency:input_type -> hyapp.user.v1.InviteAgencyRequest
19, // 84: hyapp.user.v1.UserHostService.InviteBD:input_type -> hyapp.user.v1.InviteBDRequest
21, // 85: hyapp.user.v1.UserHostService.InviteHost:input_type -> hyapp.user.v1.InviteHostRequest
23, // 86: hyapp.user.v1.UserHostService.ListBDLeaderBDs:input_type -> hyapp.user.v1.ListBDLeaderBDsRequest
25, // 87: hyapp.user.v1.UserHostService.ListBDLeaderAgencies:input_type -> hyapp.user.v1.ListBDLeaderAgenciesRequest
27, // 88: hyapp.user.v1.UserHostService.ListBDAgencies:input_type -> hyapp.user.v1.ListBDAgenciesRequest
29, // 89: hyapp.user.v1.UserHostService.ListRoleInvitations:input_type -> hyapp.user.v1.ListRoleInvitationsRequest
31, // 90: hyapp.user.v1.UserHostService.GetRoleInvitation:input_type -> hyapp.user.v1.GetRoleInvitationRequest
33, // 91: hyapp.user.v1.UserHostService.ProcessRoleInvitation:input_type -> hyapp.user.v1.ProcessRoleInvitationRequest
35, // 92: hyapp.user.v1.UserHostService.GetHostProfile:input_type -> hyapp.user.v1.GetHostProfileRequest
37, // 93: hyapp.user.v1.UserHostService.BatchGetHostProfiles:input_type -> hyapp.user.v1.BatchGetHostProfilesRequest
39, // 94: hyapp.user.v1.UserHostService.GetBDProfile:input_type -> hyapp.user.v1.GetBDProfileRequest
41, // 95: hyapp.user.v1.UserHostService.GetCoinSellerProfile:input_type -> hyapp.user.v1.GetCoinSellerProfileRequest
43, // 96: hyapp.user.v1.UserHostService.ListActiveCoinSellersInMyRegion:input_type -> hyapp.user.v1.ListActiveCoinSellersInMyRegionRequest
45, // 97: hyapp.user.v1.UserHostService.GetUserRoleSummary:input_type -> hyapp.user.v1.GetUserRoleSummaryRequest
47, // 98: hyapp.user.v1.UserHostService.GetAgency:input_type -> hyapp.user.v1.GetAgencyRequest
49, // 99: hyapp.user.v1.UserHostService.CheckBusinessCapability:input_type -> hyapp.user.v1.CheckBusinessCapabilityRequest
51, // 100: hyapp.user.v1.UserHostService.GetAgencyMembers:input_type -> hyapp.user.v1.GetAgencyMembersRequest
53, // 101: hyapp.user.v1.UserHostService.GetAgencyApplications:input_type -> hyapp.user.v1.GetAgencyApplicationsRequest
55, // 102: hyapp.user.v1.UserHostAdminService.CreateBDLeader:input_type -> hyapp.user.v1.CreateBDLeaderRequest
57, // 103: hyapp.user.v1.UserHostAdminService.CreateBD:input_type -> hyapp.user.v1.CreateBDRequest
59, // 104: hyapp.user.v1.UserHostAdminService.SetBDStatus:input_type -> hyapp.user.v1.SetBDStatusRequest
61, // 105: hyapp.user.v1.UserHostAdminService.CreateCoinSeller:input_type -> hyapp.user.v1.CreateCoinSellerRequest
63, // 106: hyapp.user.v1.UserHostAdminService.SetCoinSellerStatus:input_type -> hyapp.user.v1.SetCoinSellerStatusRequest
65, // 107: hyapp.user.v1.UserHostAdminService.CreateAgency:input_type -> hyapp.user.v1.CreateAgencyRequest
67, // 108: hyapp.user.v1.UserHostAdminService.AdminAddAgencyHost:input_type -> hyapp.user.v1.AdminAddAgencyHostRequest
69, // 109: hyapp.user.v1.UserHostAdminService.CloseAgency:input_type -> hyapp.user.v1.CloseAgencyRequest
71, // 110: hyapp.user.v1.UserHostAdminService.DeleteAgency:input_type -> hyapp.user.v1.DeleteAgencyRequest
73, // 111: hyapp.user.v1.UserHostAdminService.SetAgencyStatus:input_type -> hyapp.user.v1.SetAgencyStatusRequest
75, // 112: hyapp.user.v1.UserHostAdminService.SetAgencyJoinEnabled:input_type -> hyapp.user.v1.SetAgencyJoinEnabledRequest
10, // 113: hyapp.user.v1.UserHostService.SearchAgencies:output_type -> hyapp.user.v1.SearchAgenciesResponse
12, // 114: hyapp.user.v1.UserHostService.ApplyToAgency:output_type -> hyapp.user.v1.ApplyToAgencyResponse
14, // 115: hyapp.user.v1.UserHostService.ReviewAgencyApplication:output_type -> hyapp.user.v1.ReviewAgencyApplicationResponse
16, // 116: hyapp.user.v1.UserHostService.KickAgencyHost:output_type -> hyapp.user.v1.KickAgencyHostResponse
18, // 117: hyapp.user.v1.UserHostService.InviteAgency:output_type -> hyapp.user.v1.InviteAgencyResponse
20, // 118: hyapp.user.v1.UserHostService.InviteBD:output_type -> hyapp.user.v1.InviteBDResponse
22, // 119: hyapp.user.v1.UserHostService.InviteHost:output_type -> hyapp.user.v1.InviteHostResponse
24, // 120: hyapp.user.v1.UserHostService.ListBDLeaderBDs:output_type -> hyapp.user.v1.ListBDLeaderBDsResponse
26, // 121: hyapp.user.v1.UserHostService.ListBDLeaderAgencies:output_type -> hyapp.user.v1.ListBDLeaderAgenciesResponse
28, // 122: hyapp.user.v1.UserHostService.ListBDAgencies:output_type -> hyapp.user.v1.ListBDAgenciesResponse
30, // 123: hyapp.user.v1.UserHostService.ListRoleInvitations:output_type -> hyapp.user.v1.ListRoleInvitationsResponse
32, // 124: hyapp.user.v1.UserHostService.GetRoleInvitation:output_type -> hyapp.user.v1.GetRoleInvitationResponse
34, // 125: hyapp.user.v1.UserHostService.ProcessRoleInvitation:output_type -> hyapp.user.v1.ProcessRoleInvitationResponse
36, // 126: hyapp.user.v1.UserHostService.GetHostProfile:output_type -> hyapp.user.v1.GetHostProfileResponse
38, // 127: hyapp.user.v1.UserHostService.BatchGetHostProfiles:output_type -> hyapp.user.v1.BatchGetHostProfilesResponse
40, // 128: hyapp.user.v1.UserHostService.GetBDProfile:output_type -> hyapp.user.v1.GetBDProfileResponse
42, // 129: hyapp.user.v1.UserHostService.GetCoinSellerProfile:output_type -> hyapp.user.v1.GetCoinSellerProfileResponse
44, // 130: hyapp.user.v1.UserHostService.ListActiveCoinSellersInMyRegion:output_type -> hyapp.user.v1.ListActiveCoinSellersInMyRegionResponse
46, // 131: hyapp.user.v1.UserHostService.GetUserRoleSummary:output_type -> hyapp.user.v1.GetUserRoleSummaryResponse
48, // 132: hyapp.user.v1.UserHostService.GetAgency:output_type -> hyapp.user.v1.GetAgencyResponse
50, // 133: hyapp.user.v1.UserHostService.CheckBusinessCapability:output_type -> hyapp.user.v1.CheckBusinessCapabilityResponse
52, // 134: hyapp.user.v1.UserHostService.GetAgencyMembers:output_type -> hyapp.user.v1.GetAgencyMembersResponse
54, // 135: hyapp.user.v1.UserHostService.GetAgencyApplications:output_type -> hyapp.user.v1.GetAgencyApplicationsResponse
56, // 136: hyapp.user.v1.UserHostAdminService.CreateBDLeader:output_type -> hyapp.user.v1.CreateBDLeaderResponse
58, // 137: hyapp.user.v1.UserHostAdminService.CreateBD:output_type -> hyapp.user.v1.CreateBDResponse
60, // 138: hyapp.user.v1.UserHostAdminService.SetBDStatus:output_type -> hyapp.user.v1.SetBDStatusResponse
62, // 139: hyapp.user.v1.UserHostAdminService.CreateCoinSeller:output_type -> hyapp.user.v1.CreateCoinSellerResponse
64, // 140: hyapp.user.v1.UserHostAdminService.SetCoinSellerStatus:output_type -> hyapp.user.v1.SetCoinSellerStatusResponse
66, // 141: hyapp.user.v1.UserHostAdminService.CreateAgency:output_type -> hyapp.user.v1.CreateAgencyResponse
68, // 142: hyapp.user.v1.UserHostAdminService.AdminAddAgencyHost:output_type -> hyapp.user.v1.AdminAddAgencyHostResponse
70, // 143: hyapp.user.v1.UserHostAdminService.CloseAgency:output_type -> hyapp.user.v1.CloseAgencyResponse
72, // 144: hyapp.user.v1.UserHostAdminService.DeleteAgency:output_type -> hyapp.user.v1.DeleteAgencyResponse
74, // 145: hyapp.user.v1.UserHostAdminService.SetAgencyStatus:output_type -> hyapp.user.v1.SetAgencyStatusResponse
76, // 146: hyapp.user.v1.UserHostAdminService.SetAgencyJoinEnabled:output_type -> hyapp.user.v1.SetAgencyJoinEnabledResponse
113, // [113:147] is the sub-list for method output_type
79, // [79:113] is the sub-list for method input_type
79, // [79:79] is the sub-list for extension type_name
79, // [79:79] is the sub-list for extension extendee
0, // [0:79] is the sub-list for field type_name
80, // 78: hyapp.user.v1.UpdateAgencyProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta
1, // 79: hyapp.user.v1.UpdateAgencyProfileResponse.agency:type_name -> hyapp.user.v1.Agency
0, // 80: hyapp.user.v1.BatchGetHostProfilesResponse.HostProfilesEntry.value:type_name -> hyapp.user.v1.HostProfile
9, // 81: hyapp.user.v1.UserHostService.SearchAgencies:input_type -> hyapp.user.v1.SearchAgenciesRequest
11, // 82: hyapp.user.v1.UserHostService.ApplyToAgency:input_type -> hyapp.user.v1.ApplyToAgencyRequest
13, // 83: hyapp.user.v1.UserHostService.ReviewAgencyApplication:input_type -> hyapp.user.v1.ReviewAgencyApplicationRequest
15, // 84: hyapp.user.v1.UserHostService.KickAgencyHost:input_type -> hyapp.user.v1.KickAgencyHostRequest
17, // 85: hyapp.user.v1.UserHostService.InviteAgency:input_type -> hyapp.user.v1.InviteAgencyRequest
19, // 86: hyapp.user.v1.UserHostService.InviteBD:input_type -> hyapp.user.v1.InviteBDRequest
21, // 87: hyapp.user.v1.UserHostService.InviteHost:input_type -> hyapp.user.v1.InviteHostRequest
23, // 88: hyapp.user.v1.UserHostService.ListBDLeaderBDs:input_type -> hyapp.user.v1.ListBDLeaderBDsRequest
25, // 89: hyapp.user.v1.UserHostService.ListBDLeaderAgencies:input_type -> hyapp.user.v1.ListBDLeaderAgenciesRequest
27, // 90: hyapp.user.v1.UserHostService.ListBDAgencies:input_type -> hyapp.user.v1.ListBDAgenciesRequest
29, // 91: hyapp.user.v1.UserHostService.ListRoleInvitations:input_type -> hyapp.user.v1.ListRoleInvitationsRequest
31, // 92: hyapp.user.v1.UserHostService.GetRoleInvitation:input_type -> hyapp.user.v1.GetRoleInvitationRequest
33, // 93: hyapp.user.v1.UserHostService.ProcessRoleInvitation:input_type -> hyapp.user.v1.ProcessRoleInvitationRequest
35, // 94: hyapp.user.v1.UserHostService.GetHostProfile:input_type -> hyapp.user.v1.GetHostProfileRequest
37, // 95: hyapp.user.v1.UserHostService.BatchGetHostProfiles:input_type -> hyapp.user.v1.BatchGetHostProfilesRequest
39, // 96: hyapp.user.v1.UserHostService.GetBDProfile:input_type -> hyapp.user.v1.GetBDProfileRequest
41, // 97: hyapp.user.v1.UserHostService.GetCoinSellerProfile:input_type -> hyapp.user.v1.GetCoinSellerProfileRequest
43, // 98: hyapp.user.v1.UserHostService.ListActiveCoinSellersInMyRegion:input_type -> hyapp.user.v1.ListActiveCoinSellersInMyRegionRequest
45, // 99: hyapp.user.v1.UserHostService.GetUserRoleSummary:input_type -> hyapp.user.v1.GetUserRoleSummaryRequest
47, // 100: hyapp.user.v1.UserHostService.GetAgency:input_type -> hyapp.user.v1.GetAgencyRequest
49, // 101: hyapp.user.v1.UserHostService.CheckBusinessCapability:input_type -> hyapp.user.v1.CheckBusinessCapabilityRequest
51, // 102: hyapp.user.v1.UserHostService.GetAgencyMembers:input_type -> hyapp.user.v1.GetAgencyMembersRequest
53, // 103: hyapp.user.v1.UserHostService.GetAgencyApplications:input_type -> hyapp.user.v1.GetAgencyApplicationsRequest
77, // 104: hyapp.user.v1.UserHostService.UpdateAgencyProfile:input_type -> hyapp.user.v1.UpdateAgencyProfileRequest
55, // 105: hyapp.user.v1.UserHostAdminService.CreateBDLeader:input_type -> hyapp.user.v1.CreateBDLeaderRequest
57, // 106: hyapp.user.v1.UserHostAdminService.CreateBD:input_type -> hyapp.user.v1.CreateBDRequest
59, // 107: hyapp.user.v1.UserHostAdminService.SetBDStatus:input_type -> hyapp.user.v1.SetBDStatusRequest
61, // 108: hyapp.user.v1.UserHostAdminService.CreateCoinSeller:input_type -> hyapp.user.v1.CreateCoinSellerRequest
63, // 109: hyapp.user.v1.UserHostAdminService.SetCoinSellerStatus:input_type -> hyapp.user.v1.SetCoinSellerStatusRequest
65, // 110: hyapp.user.v1.UserHostAdminService.CreateAgency:input_type -> hyapp.user.v1.CreateAgencyRequest
67, // 111: hyapp.user.v1.UserHostAdminService.AdminAddAgencyHost:input_type -> hyapp.user.v1.AdminAddAgencyHostRequest
69, // 112: hyapp.user.v1.UserHostAdminService.CloseAgency:input_type -> hyapp.user.v1.CloseAgencyRequest
71, // 113: hyapp.user.v1.UserHostAdminService.DeleteAgency:input_type -> hyapp.user.v1.DeleteAgencyRequest
73, // 114: hyapp.user.v1.UserHostAdminService.SetAgencyStatus:input_type -> hyapp.user.v1.SetAgencyStatusRequest
75, // 115: hyapp.user.v1.UserHostAdminService.SetAgencyJoinEnabled:input_type -> hyapp.user.v1.SetAgencyJoinEnabledRequest
10, // 116: hyapp.user.v1.UserHostService.SearchAgencies:output_type -> hyapp.user.v1.SearchAgenciesResponse
12, // 117: hyapp.user.v1.UserHostService.ApplyToAgency:output_type -> hyapp.user.v1.ApplyToAgencyResponse
14, // 118: hyapp.user.v1.UserHostService.ReviewAgencyApplication:output_type -> hyapp.user.v1.ReviewAgencyApplicationResponse
16, // 119: hyapp.user.v1.UserHostService.KickAgencyHost:output_type -> hyapp.user.v1.KickAgencyHostResponse
18, // 120: hyapp.user.v1.UserHostService.InviteAgency:output_type -> hyapp.user.v1.InviteAgencyResponse
20, // 121: hyapp.user.v1.UserHostService.InviteBD:output_type -> hyapp.user.v1.InviteBDResponse
22, // 122: hyapp.user.v1.UserHostService.InviteHost:output_type -> hyapp.user.v1.InviteHostResponse
24, // 123: hyapp.user.v1.UserHostService.ListBDLeaderBDs:output_type -> hyapp.user.v1.ListBDLeaderBDsResponse
26, // 124: hyapp.user.v1.UserHostService.ListBDLeaderAgencies:output_type -> hyapp.user.v1.ListBDLeaderAgenciesResponse
28, // 125: hyapp.user.v1.UserHostService.ListBDAgencies:output_type -> hyapp.user.v1.ListBDAgenciesResponse
30, // 126: hyapp.user.v1.UserHostService.ListRoleInvitations:output_type -> hyapp.user.v1.ListRoleInvitationsResponse
32, // 127: hyapp.user.v1.UserHostService.GetRoleInvitation:output_type -> hyapp.user.v1.GetRoleInvitationResponse
34, // 128: hyapp.user.v1.UserHostService.ProcessRoleInvitation:output_type -> hyapp.user.v1.ProcessRoleInvitationResponse
36, // 129: hyapp.user.v1.UserHostService.GetHostProfile:output_type -> hyapp.user.v1.GetHostProfileResponse
38, // 130: hyapp.user.v1.UserHostService.BatchGetHostProfiles:output_type -> hyapp.user.v1.BatchGetHostProfilesResponse
40, // 131: hyapp.user.v1.UserHostService.GetBDProfile:output_type -> hyapp.user.v1.GetBDProfileResponse
42, // 132: hyapp.user.v1.UserHostService.GetCoinSellerProfile:output_type -> hyapp.user.v1.GetCoinSellerProfileResponse
44, // 133: hyapp.user.v1.UserHostService.ListActiveCoinSellersInMyRegion:output_type -> hyapp.user.v1.ListActiveCoinSellersInMyRegionResponse
46, // 134: hyapp.user.v1.UserHostService.GetUserRoleSummary:output_type -> hyapp.user.v1.GetUserRoleSummaryResponse
48, // 135: hyapp.user.v1.UserHostService.GetAgency:output_type -> hyapp.user.v1.GetAgencyResponse
50, // 136: hyapp.user.v1.UserHostService.CheckBusinessCapability:output_type -> hyapp.user.v1.CheckBusinessCapabilityResponse
52, // 137: hyapp.user.v1.UserHostService.GetAgencyMembers:output_type -> hyapp.user.v1.GetAgencyMembersResponse
54, // 138: hyapp.user.v1.UserHostService.GetAgencyApplications:output_type -> hyapp.user.v1.GetAgencyApplicationsResponse
78, // 139: hyapp.user.v1.UserHostService.UpdateAgencyProfile:output_type -> hyapp.user.v1.UpdateAgencyProfileResponse
56, // 140: hyapp.user.v1.UserHostAdminService.CreateBDLeader:output_type -> hyapp.user.v1.CreateBDLeaderResponse
58, // 141: hyapp.user.v1.UserHostAdminService.CreateBD:output_type -> hyapp.user.v1.CreateBDResponse
60, // 142: hyapp.user.v1.UserHostAdminService.SetBDStatus:output_type -> hyapp.user.v1.SetBDStatusResponse
62, // 143: hyapp.user.v1.UserHostAdminService.CreateCoinSeller:output_type -> hyapp.user.v1.CreateCoinSellerResponse
64, // 144: hyapp.user.v1.UserHostAdminService.SetCoinSellerStatus:output_type -> hyapp.user.v1.SetCoinSellerStatusResponse
66, // 145: hyapp.user.v1.UserHostAdminService.CreateAgency:output_type -> hyapp.user.v1.CreateAgencyResponse
68, // 146: hyapp.user.v1.UserHostAdminService.AdminAddAgencyHost:output_type -> hyapp.user.v1.AdminAddAgencyHostResponse
70, // 147: hyapp.user.v1.UserHostAdminService.CloseAgency:output_type -> hyapp.user.v1.CloseAgencyResponse
72, // 148: hyapp.user.v1.UserHostAdminService.DeleteAgency:output_type -> hyapp.user.v1.DeleteAgencyResponse
74, // 149: hyapp.user.v1.UserHostAdminService.SetAgencyStatus:output_type -> hyapp.user.v1.SetAgencyStatusResponse
76, // 150: hyapp.user.v1.UserHostAdminService.SetAgencyJoinEnabled:output_type -> hyapp.user.v1.SetAgencyJoinEnabledResponse
116, // [116:151] is the sub-list for method output_type
81, // [81:116] is the sub-list for method input_type
81, // [81:81] is the sub-list for extension type_name
81, // [81:81] is the sub-list for extension extendee
0, // [0:81] is the sub-list for field type_name
}
func init() { file_proto_user_v1_host_proto_init() }
@ -6605,13 +6769,14 @@ func file_proto_user_v1_host_proto_init() {
return
}
file_proto_user_v1_user_proto_init()
file_proto_user_v1_host_proto_msgTypes[77].OneofWrappers = []any{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proto_user_v1_host_proto_rawDesc,
NumEnums: 0,
NumMessages: 78,
NumMessages: 80,
NumExtensions: 0,
NumServices: 2,
},

View File

@ -548,6 +548,19 @@ message SetAgencyJoinEnabledResponse {
Agency agency = 1;
}
message UpdateAgencyProfileRequest {
RequestMeta meta = 1;
string command_id = 2;
int64 operator_user_id = 3;
int64 agency_id = 4;
optional string name = 5;
optional string avatar = 6;
}
message UpdateAgencyProfileResponse {
Agency agency = 1;
}
// UserHostService user-service host domain gRPC
service UserHostService {
rpc SearchAgencies(SearchAgenciesRequest) returns (SearchAgenciesResponse);
@ -573,6 +586,7 @@ service UserHostService {
rpc CheckBusinessCapability(CheckBusinessCapabilityRequest) returns (CheckBusinessCapabilityResponse);
rpc GetAgencyMembers(GetAgencyMembersRequest) returns (GetAgencyMembersResponse);
rpc GetAgencyApplications(GetAgencyApplicationsRequest) returns (GetAgencyApplicationsResponse);
rpc UpdateAgencyProfile(UpdateAgencyProfileRequest) returns (UpdateAgencyProfileResponse);
}
// UserHostAdminService admin admin-server

View File

@ -42,6 +42,7 @@ const (
UserHostService_CheckBusinessCapability_FullMethodName = "/hyapp.user.v1.UserHostService/CheckBusinessCapability"
UserHostService_GetAgencyMembers_FullMethodName = "/hyapp.user.v1.UserHostService/GetAgencyMembers"
UserHostService_GetAgencyApplications_FullMethodName = "/hyapp.user.v1.UserHostService/GetAgencyApplications"
UserHostService_UpdateAgencyProfile_FullMethodName = "/hyapp.user.v1.UserHostService/UpdateAgencyProfile"
)
// UserHostServiceClient is the client API for UserHostService service.
@ -73,6 +74,7 @@ type UserHostServiceClient interface {
CheckBusinessCapability(ctx context.Context, in *CheckBusinessCapabilityRequest, opts ...grpc.CallOption) (*CheckBusinessCapabilityResponse, error)
GetAgencyMembers(ctx context.Context, in *GetAgencyMembersRequest, opts ...grpc.CallOption) (*GetAgencyMembersResponse, error)
GetAgencyApplications(ctx context.Context, in *GetAgencyApplicationsRequest, opts ...grpc.CallOption) (*GetAgencyApplicationsResponse, error)
UpdateAgencyProfile(ctx context.Context, in *UpdateAgencyProfileRequest, opts ...grpc.CallOption) (*UpdateAgencyProfileResponse, error)
}
type userHostServiceClient struct {
@ -313,6 +315,16 @@ func (c *userHostServiceClient) GetAgencyApplications(ctx context.Context, in *G
return out, nil
}
func (c *userHostServiceClient) UpdateAgencyProfile(ctx context.Context, in *UpdateAgencyProfileRequest, opts ...grpc.CallOption) (*UpdateAgencyProfileResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(UpdateAgencyProfileResponse)
err := c.cc.Invoke(ctx, UserHostService_UpdateAgencyProfile_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// UserHostServiceServer is the server API for UserHostService service.
// All implementations must embed UnimplementedUserHostServiceServer
// for forward compatibility.
@ -342,6 +354,7 @@ type UserHostServiceServer interface {
CheckBusinessCapability(context.Context, *CheckBusinessCapabilityRequest) (*CheckBusinessCapabilityResponse, error)
GetAgencyMembers(context.Context, *GetAgencyMembersRequest) (*GetAgencyMembersResponse, error)
GetAgencyApplications(context.Context, *GetAgencyApplicationsRequest) (*GetAgencyApplicationsResponse, error)
UpdateAgencyProfile(context.Context, *UpdateAgencyProfileRequest) (*UpdateAgencyProfileResponse, error)
mustEmbedUnimplementedUserHostServiceServer()
}
@ -421,6 +434,9 @@ func (UnimplementedUserHostServiceServer) GetAgencyMembers(context.Context, *Get
func (UnimplementedUserHostServiceServer) GetAgencyApplications(context.Context, *GetAgencyApplicationsRequest) (*GetAgencyApplicationsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetAgencyApplications not implemented")
}
func (UnimplementedUserHostServiceServer) UpdateAgencyProfile(context.Context, *UpdateAgencyProfileRequest) (*UpdateAgencyProfileResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateAgencyProfile not implemented")
}
func (UnimplementedUserHostServiceServer) mustEmbedUnimplementedUserHostServiceServer() {}
func (UnimplementedUserHostServiceServer) testEmbeddedByValue() {}
@ -856,6 +872,24 @@ func _UserHostService_GetAgencyApplications_Handler(srv interface{}, ctx context
return interceptor(ctx, in, info, handler)
}
func _UserHostService_UpdateAgencyProfile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateAgencyProfileRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserHostServiceServer).UpdateAgencyProfile(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserHostService_UpdateAgencyProfile_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserHostServiceServer).UpdateAgencyProfile(ctx, req.(*UpdateAgencyProfileRequest))
}
return interceptor(ctx, in, info, handler)
}
// UserHostService_ServiceDesc is the grpc.ServiceDesc for UserHostService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -955,6 +989,10 @@ var UserHostService_ServiceDesc = grpc.ServiceDesc{
MethodName: "GetAgencyApplications",
Handler: _UserHostService_GetAgencyApplications_Handler,
},
{
MethodName: "UpdateAgencyProfile",
Handler: _UserHostService_UpdateAgencyProfile_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "proto/user/v1/host.proto",

File diff suppressed because it is too large Load Diff

View File

@ -691,6 +691,25 @@ message BatchGetUsersResponse {
map<int64, User> users = 1;
}
// RoomBasicUser
message RoomBasicUser {
int64 user_id = 1;
string username = 2;
string avatar = 3;
string display_user_id = 4;
}
// BatchGetRoomBasicUsersRequest lite
message BatchGetRoomBasicUsersRequest {
RequestMeta meta = 1;
repeated int64 user_ids = 2;
}
// BatchGetRoomBasicUsersResponse user_id
message BatchGetRoomBasicUsersResponse {
map<int64, RoomBasicUser> users = 1;
}
// ListUserIDsRequest fanout/ user_id
message ListUserIDsRequest {
RequestMeta meta = 1;
@ -1324,6 +1343,7 @@ service UserService {
rpc BusinessUserLookup(BusinessUserLookupRequest) returns (BusinessUserLookupResponse);
rpc GetMyProfileStats(GetMyProfileStatsRequest) returns (GetMyProfileStatsResponse);
rpc BatchGetUsers(BatchGetUsersRequest) returns (BatchGetUsersResponse);
rpc BatchGetRoomBasicUsers(BatchGetRoomBasicUsersRequest) returns (BatchGetRoomBasicUsersResponse);
rpc ListUserIDs(ListUserIDsRequest) returns (ListUserIDsResponse);
rpc GetUserMicLifetimeStats(GetUserMicLifetimeStatsRequest) returns (GetUserMicLifetimeStatsResponse);
rpc UpdateUserProfile(UpdateUserProfileRequest) returns (UpdateUserProfileResponse);

View File

@ -24,6 +24,7 @@ const (
UserService_BusinessUserLookup_FullMethodName = "/hyapp.user.v1.UserService/BusinessUserLookup"
UserService_GetMyProfileStats_FullMethodName = "/hyapp.user.v1.UserService/GetMyProfileStats"
UserService_BatchGetUsers_FullMethodName = "/hyapp.user.v1.UserService/BatchGetUsers"
UserService_BatchGetRoomBasicUsers_FullMethodName = "/hyapp.user.v1.UserService/BatchGetRoomBasicUsers"
UserService_ListUserIDs_FullMethodName = "/hyapp.user.v1.UserService/ListUserIDs"
UserService_GetUserMicLifetimeStats_FullMethodName = "/hyapp.user.v1.UserService/GetUserMicLifetimeStats"
UserService_UpdateUserProfile_FullMethodName = "/hyapp.user.v1.UserService/UpdateUserProfile"
@ -52,6 +53,7 @@ type UserServiceClient interface {
BusinessUserLookup(ctx context.Context, in *BusinessUserLookupRequest, opts ...grpc.CallOption) (*BusinessUserLookupResponse, error)
GetMyProfileStats(ctx context.Context, in *GetMyProfileStatsRequest, opts ...grpc.CallOption) (*GetMyProfileStatsResponse, error)
BatchGetUsers(ctx context.Context, in *BatchGetUsersRequest, opts ...grpc.CallOption) (*BatchGetUsersResponse, error)
BatchGetRoomBasicUsers(ctx context.Context, in *BatchGetRoomBasicUsersRequest, opts ...grpc.CallOption) (*BatchGetRoomBasicUsersResponse, error)
ListUserIDs(ctx context.Context, in *ListUserIDsRequest, opts ...grpc.CallOption) (*ListUserIDsResponse, error)
GetUserMicLifetimeStats(ctx context.Context, in *GetUserMicLifetimeStatsRequest, opts ...grpc.CallOption) (*GetUserMicLifetimeStatsResponse, error)
UpdateUserProfile(ctx context.Context, in *UpdateUserProfileRequest, opts ...grpc.CallOption) (*UpdateUserProfileResponse, error)
@ -127,6 +129,16 @@ func (c *userServiceClient) BatchGetUsers(ctx context.Context, in *BatchGetUsers
return out, nil
}
func (c *userServiceClient) BatchGetRoomBasicUsers(ctx context.Context, in *BatchGetRoomBasicUsersRequest, opts ...grpc.CallOption) (*BatchGetRoomBasicUsersResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(BatchGetRoomBasicUsersResponse)
err := c.cc.Invoke(ctx, UserService_BatchGetRoomBasicUsers_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userServiceClient) ListUserIDs(ctx context.Context, in *ListUserIDsRequest, opts ...grpc.CallOption) (*ListUserIDsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListUserIDsResponse)
@ -288,6 +300,7 @@ type UserServiceServer interface {
BusinessUserLookup(context.Context, *BusinessUserLookupRequest) (*BusinessUserLookupResponse, error)
GetMyProfileStats(context.Context, *GetMyProfileStatsRequest) (*GetMyProfileStatsResponse, error)
BatchGetUsers(context.Context, *BatchGetUsersRequest) (*BatchGetUsersResponse, error)
BatchGetRoomBasicUsers(context.Context, *BatchGetRoomBasicUsersRequest) (*BatchGetRoomBasicUsersResponse, error)
ListUserIDs(context.Context, *ListUserIDsRequest) (*ListUserIDsResponse, error)
GetUserMicLifetimeStats(context.Context, *GetUserMicLifetimeStatsRequest) (*GetUserMicLifetimeStatsResponse, error)
UpdateUserProfile(context.Context, *UpdateUserProfileRequest) (*UpdateUserProfileResponse, error)
@ -328,6 +341,9 @@ func (UnimplementedUserServiceServer) GetMyProfileStats(context.Context, *GetMyP
func (UnimplementedUserServiceServer) BatchGetUsers(context.Context, *BatchGetUsersRequest) (*BatchGetUsersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchGetUsers not implemented")
}
func (UnimplementedUserServiceServer) BatchGetRoomBasicUsers(context.Context, *BatchGetRoomBasicUsersRequest) (*BatchGetRoomBasicUsersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchGetRoomBasicUsers not implemented")
}
func (UnimplementedUserServiceServer) ListUserIDs(context.Context, *ListUserIDsRequest) (*ListUserIDsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListUserIDs not implemented")
}
@ -484,6 +500,24 @@ func _UserService_BatchGetUsers_Handler(srv interface{}, ctx context.Context, de
return interceptor(ctx, in, info, handler)
}
func _UserService_BatchGetRoomBasicUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BatchGetRoomBasicUsersRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).BatchGetRoomBasicUsers(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_BatchGetRoomBasicUsers_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).BatchGetRoomBasicUsers(ctx, req.(*BatchGetRoomBasicUsersRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserService_ListUserIDs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListUserIDsRequest)
if err := dec(in); err != nil {
@ -781,6 +815,10 @@ var UserService_ServiceDesc = grpc.ServiceDesc{
MethodName: "BatchGetUsers",
Handler: _UserService_BatchGetUsers_Handler,
},
{
MethodName: "BatchGetRoomBasicUsers",
Handler: _UserService_BatchGetRoomBasicUsers_Handler,
},
{
MethodName: "ListUserIDs",
Handler: _UserService_ListUserIDs_Handler,

View File

@ -0,0 +1,296 @@
# Agency 资料与房间 Flutter 对接
## 基础约定
本地开发地址:
```text
http://127.0.0.1:13000
```
所有接口都是登录接口,需要带登录 token。
请求头:
| Header | 必填 | 说明 |
| --- | --- | --- |
| `Authorization` | 是 | `Bearer <access_token>`。 |
| `X-App-Code` | 否 | App 编码,默认 `lalu`;登录态接口最终以 token 内 app_code 为准。 |
成功和失败都使用统一 envelope
```json
{
"code": "OK",
"message": "ok",
"request_id": "req_xxx",
"data": {}
}
```
客户端分支只判断 `code`,排查问题记录 `request_id`
## 1. 查询用户所属 Agency 资料
地址:
```http
GET /api/v1/agencies/profile
```
参数:
| 参数 | 位置 | 必填 | 类型 | 说明 |
| --- | --- | --- | --- | --- |
| `user_id` | query | 是 | string/int64 | 被查看的 host 用户 ID。 |
示例:
```http
GET /api/v1/agencies/profile?user_id=10001
Authorization: Bearer <access_token>
X-App-Code: lalu
```
返回值:
```json
{
"code": "OK",
"message": "ok",
"request_id": "req_abc",
"data": {
"agency": {
"agency_id": "7001",
"owner_user_id": "99",
"region_id": 30,
"parent_bd_user_id": "501",
"name": "Yumi Star Agency",
"avatar": "https://cdn.example/agency.png",
"host_count": 20,
"status": "active",
"join_enabled": true,
"created_by_user_id": "1",
"created_at_ms": 1710000000000,
"updated_at_ms": 1710000001000
}
}
}
```
没有 active host 身份,或没有 active agency 归属时:
```json
{
"code": "OK",
"message": "ok",
"request_id": "req_abc",
"data": {
"agency": null
}
}
```
字段说明:
| 字段 | 说明 |
| --- | --- |
| `agency_id` | Agency ID字符串。 |
| `owner_user_id` | Agency 会长用户 ID字符串。 |
| `name` | Agency 自己的名称,不再读 owner 用户昵称。 |
| `avatar` | Agency 自己的头像,不再读 owner 用户头像。 |
| `host_count` | 当前 active host 数量。 |
| `status` | 当前只展示 `active`。 |
| `join_enabled` | 是否允许用户申请加入。 |
| `created_at_ms` / `updated_at_ms` | Unix epoch milliseconds。 |
## 2. 查询 Agency 下所有 host 的房间
地址:
```http
GET /api/v1/agencies/rooms
```
参数:
| 参数 | 位置 | 必填 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- | --- | --- |
| `user_id` | query | 是 | string/int64 | 无 | 被查看的 host 用户 ID。 |
| `limit` | query | 否 | int | 20 | 每页数量,最大 50。 |
| `cursor` | query | 否 | string | 空 | 下一页 cursor。 |
| `tab` | query | 否 | string | `hot` | `hot``new`。 |
| `query` | query | 否 | string | 空 | 搜索房间标题、房间 ID 或短号。 |
示例:
```http
GET /api/v1/agencies/rooms?user_id=10001&tab=hot&limit=20
Authorization: Bearer <access_token>
X-App-Code: lalu
```
返回值:
```json
{
"code": "OK",
"message": "ok",
"request_id": "req_abc",
"data": {
"agency_id": "7001",
"rooms": [
{
"room_id": "room_101",
"im_group_id": "room_101",
"owner_user_id": "101",
"title": "Agency Room",
"cover_url": "https://cdn.example/room.png",
"mode": "voice",
"status": "active",
"locked": false,
"heat": 88,
"online_count": 12,
"seat_count": 8,
"occupied_seat_count": 3,
"visible_region_id": 30,
"app_code": "lalu",
"room_short_id": "M20",
"country_code": "AE",
"country_flag": "🇦🇪"
}
],
"next_cursor": "cursor_xxx"
}
}
```
没有 active host 身份,或没有 active agency 归属时:
```json
{
"code": "OK",
"message": "ok",
"request_id": "req_abc",
"data": {
"agency_id": "",
"rooms": [],
"next_cursor": ""
}
}
```
字段说明:
| 字段 | 说明 |
| --- | --- |
| `agency_id` | 当前查询到的 Agency ID无归属时为空字符串。 |
| `rooms` | Agency 下所有 active host 的 active 房间。 |
| `next_cursor` | 下一页 cursor为空表示没有下一页。 |
| `room_id` | 房间 ID。 |
| `im_group_id` | 腾讯云 IM 房间群 ID进群使用这个字段。 |
| `owner_user_id` | 房主用户 ID字符串。 |
| `title` | 房间名。 |
| `cover_url` | 房间封面。 |
| `locked` | 是否锁房。 |
| `heat` | 房间热度。 |
| `online_count` | 在线人数。 |
| `seat_count` | 麦位数量。 |
| `occupied_seat_count` | 已占用麦位数量。 |
| `room_short_id` | 房间短号。 |
| `country_code` / `country_flag` | 房主国家展示字段。 |
## 3. 修改 Agency 资料
地址:
```http
POST /api/v1/agencies/profile/update
```
权限:
只有当前登录用户是 active Agency owner 时可以修改。普通 host、Agency 成员、BD、非 active owner 都不能修改。
参数:
| 参数 | 位置 | 必填 | 类型 | 说明 |
| --- | --- | --- | --- | --- |
| `command_id` | body | 是 | string | 客户端生成的幂等 ID。 |
| `name` | body | 否 | string | 新 Agency 名称。 |
| `avatar` | body | 否 | string | 新 Agency 头像 URL。 |
`name``avatar` 至少传一个;传了就不能是空字符串。修改 Agency 资料不会修改 owner 用户昵称和头像。
示例:
```http
POST /api/v1/agencies/profile/update
Authorization: Bearer <access_token>
X-App-Code: lalu
Content-Type: application/json
{
"command_id": "cmd_agency_profile_001",
"name": "New Agency Name",
"avatar": "https://cdn.example/new-agency.png"
}
```
返回值:
```json
{
"code": "OK",
"message": "ok",
"request_id": "req_abc",
"data": {
"agency": {
"agency_id": "7001",
"owner_user_id": "42",
"region_id": 30,
"name": "New Agency Name",
"avatar": "https://cdn.example/new-agency.png",
"host_count": 20,
"status": "active",
"join_enabled": true,
"created_at_ms": 1710000000000,
"updated_at_ms": 1710000005000
}
}
}
```
## 错误处理
失败返回:
```json
{
"code": "INVALID_ARGUMENT",
"message": "invalid argument",
"request_id": "req_abc"
}
```
| HTTP 状态码 | `code` | 处理方式 |
| --- | --- | --- |
| `400` | `INVALID_ARGUMENT` | 参数错误,例如 `user_id` 非法、`tab` 非法、`limit` 非法、缺少 `command_id``name/avatar` 都没传或传空。 |
| `401` | `UNAUTHORIZED` | token 缺失、无效或过期,重新登录。 |
| `403` | `PROFILE_REQUIRED` | 用户资料未完成,跳转资料补全。 |
| `403` | `PERMISSION_DENIED` | 修改接口中当前用户不是 active Agency owner不允许修改。 |
| `404` | `NOT_FOUND` | 修改接口中 Agency 事实不存在;刷新当前用户身份后再进入页面。 |
| `409` | `CONFLICT` | `command_id` 与已有不同命令冲突;重新生成新的 `command_id` 再提交。 |
| `502` | `UPSTREAM_ERROR` | 服务暂不可用,可以提示稍后重试。 |
客户端处理规则:
1. 查询资料时,`agency == null` 是正常空状态,不弹错误。
2. 查询房间时,`rooms == []` 是正常空列表,不弹错误。
3. 修改资料成功后,用返回的 `data.agency` 刷新页面,不要用 owner 用户资料覆盖。
4. 修改失败时保留本地未保存状态,让用户可以重试。
5. 所有失败日志记录 `request_id`
## 相关 IM
无。Agency 资料修改和 Agency 房间列表不会下发新的 IM房间卡片里的 `im_group_id` 只用于进入房间时加入腾讯云 IM 房间群。

View File

@ -0,0 +1,282 @@
# 进房接口 Flutter 对接
## 接口地址
```http
POST /api/v1/rooms/join
Authorization: Bearer <access_token>
Content-Type: application/json
```
新版本进房默认使用 `response_mode=lite``full` 仍保留给旧客户端兼容,不建议新 Flutter 首屏使用。
## 参数
Body
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `room_id` | string | 是 | 房间 ID。 |
| `command_id` | string | 是 | 本次进房命令 ID。同一次点击超时重试复用同一个值下一次重新进房换新值。 |
| `role` | string | 否 | 不传默认 `audience`。房主身份由服务端按房间 owner 判断,客户端不要靠传 `owner` 获取权限。 |
| `password` | string | 否 | 密码房传房间密码,非密码房不传或传空。 |
| `response_mode` | string | 是 | 新版固定传 `lite`。可选值:`lite` / `full`。 |
请求示例:
```json
{
"room_id": "lalu_abc123",
"command_id": "cmd_join_room_lalu_abc123_10001_1778000001000",
"role": "audience",
"password": "",
"response_mode": "lite"
}
```
也支持 query 参数:
```http
POST /api/v1/rooms/join?response_mode=lite
```
Flutter 建议放在 body 里,避免调用层漏拼 query。
## 返回值
统一外壳:
```json
{
"code": "OK",
"message": "ok",
"request_id": "req_xxx",
"data": {}
}
```
成功返回示例:
```json
{
"code": "OK",
"message": "ok",
"request_id": "req_xxx",
"data": {
"result": {
"applied": true,
"room_version": 8,
"server_time_ms": 1778000001123
},
"room": {
"room_id": "lalu_abc123",
"im_group_id": "lalu_abc123",
"room_short_id": "10001",
"title": "Live Room",
"cover_url": "https://cdn.example/room.png",
"background_url": "https://cdn.example/bg.png",
"description": "welcome",
"owner_user_id": "10001",
"mode": "voice",
"status": "active",
"chat_enabled": true,
"locked": false,
"heat": 88,
"online_count": 200,
"seat_count": 10,
"occupied_seat_count": 2,
"visible_region_id": 1001,
"version": 8
},
"viewer": {
"user_id": "10001",
"role": "audience",
"joined_at_ms": 1778000001000,
"last_seen_at_ms": 1778000001100
},
"seats": [
{
"seat_no": 1,
"user_id": "10002",
"locked": false,
"publish_state": "publishing",
"mic_session_id": "mic_10002",
"publish_deadline_ms": 1778000030000,
"mic_muted": false,
"seat_status": "publishing",
"mic_heartbeat_at_ms": 1778000000000,
"gift_value": 0
},
{
"seat_no": 2,
"locked": false,
"mic_muted": false,
"gift_value": 0
}
],
"profiles": [
{
"user_id": "10001",
"username": "host",
"avatar": "https://cdn.example/avatar.png",
"display_user_id": "10001"
},
{
"user_id": "10002",
"username": "speaker",
"avatar": "https://cdn.example/avatar2.png",
"display_user_id": "10002"
}
],
"im": {
"group_id": "lalu_abc123",
"need_join_group": true
},
"rtc": {
"need_token": true,
"available": true,
"token": {
"sdk_app_id": 1400000000,
"user_id": "10001",
"rtc_user_id": "10001",
"user_sig": "xxx",
"expire_at_ms": 1778007200000,
"room_id": "lalu_abc123",
"rtc_room_id": "lalu_abc123",
"rtc_room_id_type": "string",
"str_room_id": "lalu_abc123",
"app_scene": "voice_chat_room",
"role": "user"
}
},
"server_time_ms": 1778000001123
}
}
```
字段说明:
| 字段 | 说明 |
| --- | --- |
| `data.result.applied` | 本次进房是否真正写入。重复 `command_id` 重试可能为 `false`。 |
| `data.result.room_version` | 房间版本,用于客户端状态更新判断。 |
| `data.room.room_id` | 房间 ID。 |
| `data.room.im_group_id` | 腾讯 IM 房间群 ID。当前通常等于 `room_id`,客户端仍以返回值为准。 |
| `data.room.online_count` | 房间在线人数。`lite` 不返回在线用户明细,只返回人数。 |
| `data.room.seat_count` | 麦位总数。 |
| `data.room.occupied_seat_count` | 已占用麦位数。 |
| `data.viewer` | 当前登录用户在房间内的 presence。 |
| `data.viewer.role` | 当前用户房间角色,房主会由服务端返回 `owner`。 |
| `data.seats` | 首屏麦位状态。 |
| `data.seats[].user_id` | 麦上用户 ID空麦不返回该字段。 |
| `data.seats[].publish_state` | 麦上发流状态,常见值:`pending_publish` / `publishing`。 |
| `data.seats[].mic_session_id` | 麦位发流会话 ID。麦上心跳、发流确认需要使用它。 |
| `data.seats[].seat_status` | 麦位展示状态,常见值:`empty` / `locked` / `occupied` / `publishing` / `muted`。 |
| `data.profiles` | 首屏基础资料,只包含房主、当前用户、麦上用户。 |
| `data.profiles[].username` | 昵称。 |
| `data.profiles[].avatar` | 头像。 |
| `data.profiles[].display_user_id` | 展示 ID。 |
| `data.im.group_id` | Flutter 调腾讯 IM `joinGroup` 使用的群 ID。 |
| `data.im.need_join_group` | 为 `true` 时需要加入腾讯 IM 群。 |
| `data.rtc.available` | 是否拿到 RTC 票据。 |
| `data.rtc.token` | RTC 进房票据。`available=false` 时可能为空。 |
| `data.rtc.reason` | RTC 不可用原因。业务进房成功时RTC 不可用不代表进房失败。 |
| `data.server_time_ms` | 服务端时间UTC epoch milliseconds。 |
`lite` 不返回这些内容:
| 字段 | 说明 |
| --- | --- |
| `contribution_rank` | 贡献榜不在进房首屏主包返回。 |
| `profiles[].avatar_frame` | 头像框后置拉。 |
| `profiles[].profile_card` | 资料卡后置拉。 |
| `profiles[].vehicle` | 座驾后置拉。 |
| `profiles[].mic_seat_animation` | 麦位动画后置拉。 |
| `profiles[].badges` | 等级/成就徽章后置拉。 |
| `profiles[].vip` | VIP 展示后置拉。 |
## 相关 IM
进房成功后 Flutter 执行:
```text
1. POST /api/v1/rooms/joinbody 传 response_mode=lite
2. 如果 data.im.need_join_group=true调用腾讯 IM joinGroup(data.im.group_id)
3. joinGroup 成功或已在群内,都按进房 IM 成功处理
4. 页面开始房间 heartbeat
5. 如果 data.rtc.available=true用 data.rtc.token 进入 RTC
```
说明:
| 项 | 说明 |
| --- | --- |
| IM 登录 | 进房接口不返回 IM UserSig。Flutter 仍然通过 `GET /api/v1/im/usersig` 获取并登录腾讯 IM。 |
| IM 入群 | 必须先 `JoinRoom` 成功,再调腾讯 IM `joinGroup`。不要先入 IM 群。 |
| 入房广播 | 服务端会发 `RoomUserJoined` 房间事件。Flutter 按现有房间 IM 事件逻辑展示入房飘窗。 |
| 重复进房 | 用户重新打开房间页或重连时服务端仍会给展示用入房事件Flutter 正常展示即可。 |
| 入场座驾 | `lite` 进房不携带座驾快照。座驾、头像框、资料卡、麦位动画等展示资料在进房 UI 出来后异步补齐。 |
## 后置资料接口
进房首屏出来后,再按需要异步拉展示资料:
```http
GET /api/v1/users/room-display-profiles:batch?user_ids=10001,10002
Authorization: Bearer <access_token>
```
参数:
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `user_ids` | string | 是 | 逗号分隔的用户 ID。也支持多个 `user_id=10001&user_id=10002`。 |
返回值:
```json
{
"code": "OK",
"message": "ok",
"request_id": "req_xxx",
"data": {
"profiles": [
{
"user_id": "10001",
"username": "host",
"avatar": "https://cdn.example/avatar.png",
"display_user_id": "10001",
"avatar_frame": {},
"profile_card": {},
"vehicle": {},
"mic_seat_animation": {},
"badges": [],
"vip": {},
"level": {},
"level_badges": {},
"charm": 0
}
]
}
}
```
当前用户自己的装扮也可以后置拉:
```http
GET /api/v1/appearance
GET /api/v1/users/me/appearance
```
## 常见错误
| code | HTTP | 说明 |
| --- | --- | --- |
| `INVALID_JSON` | 400 | 请求体不是合法 JSON。 |
| `INVALID_ARGUMENT` | 400 | 参数错误,例如 `response_mode` 不是 `lite``full`。 |
| `UNAUTHORIZED` | 401 | 未登录或 token 无效。 |
| `PROFILE_REQUIRED` | 403 | 用户资料未补全。 |
| `PERMISSION_DENIED` | 403 | 密码错误、被踢/被禁等权限问题。 |
| `ROOM_CLOSED` | 409 | 房间已关闭;非房主不能进入。 |
| `NOT_FOUND` | 404 | 房间不存在。 |
| `UPSTREAM_ERROR` | 502 | 上游服务异常。 |

View File

@ -634,6 +634,7 @@ type Agency struct {
RegionID int64 `json:"regionId"`
ParentBDUserID int64 `json:"parentBdUserId,string"`
Name string `json:"name"`
Avatar string `json:"avatar"`
Status string `json:"status"`
JoinEnabled bool `json:"joinEnabled"`
MaxHosts int32 `json:"maxHosts"`
@ -960,6 +961,7 @@ func fromProtoAgency(agency *userv1.Agency) *Agency {
RegionID: agency.GetRegionId(),
ParentBDUserID: agency.GetParentBdUserId(),
Name: agency.GetName(),
Avatar: agency.GetAvatar(),
Status: agency.GetStatus(),
JoinEnabled: agency.GetJoinEnabled(),
MaxHosts: agency.GetMaxHosts(),

View File

@ -213,7 +213,7 @@ type createAgencyRequest struct {
CommandID string `json:"commandId" binding:"required"`
OwnerUserID flexibleInt64 `json:"ownerUserId" binding:"required"`
ParentBDUserID flexibleInt64 `json:"parentBdUserId"`
Name string `json:"name" binding:"required"`
Name string `json:"name"`
JoinEnabled *bool `json:"joinEnabled" binding:"required"`
Reason string `json:"reason"`
}

View File

@ -301,6 +301,10 @@ func (s *Service) LaunchGame(ctx context.Context, command LaunchCommand) (Launch
}
token = "gt_" + randomHex(24)
}
if strings.EqualFold(game.AdapterType, gamedomain.AdapterZGameV1) {
// ZGame 文档要求 js_code 是客户服务器派发的短期凭证;不能把 App access token 泄漏到三方 H5 URL。
token = "gt_" + randomHex(24)
}
sessionTTL := s.config.LaunchSessionTTL
// 三方游戏会在 H5 运行期间持续回调 token厂商适配器可以把默认 15 分钟启动 TTL 拉长。
if strings.EqualFold(game.AdapterType, gamedomain.AdapterYomiV4) {
@ -995,16 +999,15 @@ func buildLaunchURL(game gamedomain.LaunchableGame, session gamedomain.LaunchSes
return parsed.String(), nil
}
if strings.EqualFold(game.AdapterType, gamedomain.AdapterZGameV1) {
// ZGame 客户端通过 ThirdPlatformBridge.login 拿 js_code为避免 App 发版,远程桥接脚本直接从启动配置读取同一个短期 token。
// laluparty 是厂商文档约定通道H5 通过 App 注入的 ThirdPlatformBridge 取用户信息和 login js_code。
// 启动 URL 仍带 open_id/js_code供 App Bridge 复用同一份短期凭证,避免客户端再发起一次派码请求。
config := zgameConfigFromPlatform(game)
openID := externalUserID(session, config.UIDMode)
query.Set("openId", openID)
query.Set("channelName", "laluparty")
query.Set("open_id", openID)
query.Set("userId", openID)
query.Set("uid", openID)
query.Set("code", token)
query.Set("openId", openID)
query.Set("js_code", token)
query.Set("session", token)
query.Set("jscode", token)
query.Set("gameId", strings.TrimSpace(session.ProviderGameID))
query.Set("game_id", strings.TrimSpace(session.ProviderGameID))
callbackBase := config.CallbackBaseValue()

View File

@ -630,14 +630,19 @@ func TestLaunchGameBuildsZGameURL(t *testing.T) {
if parsed.Scheme+"://"+parsed.Host+parsed.Path != "https://zgame.example/h5/1001" {
t.Fatalf("zgame launch url must use per-game configured url: %s", result.LaunchURL)
}
jsCode := parsed.Query().Get("js_code")
if jsCode == "" || !strings.HasPrefix(jsCode, "gt_") || jsCode == token {
t.Fatalf("zgame launch must expose a scoped js_code instead of app token: %q", jsCode)
}
if repo.session.LaunchTokenHash != stableHash(jsCode) {
t.Fatalf("zgame launch session must be bound to scoped js_code")
}
expected := map[string]string{
"channelName": "laluparty",
"openId": "420001",
"open_id": "420001",
"userId": "420001",
"uid": "420001",
"code": token,
"js_code": token,
"session": token,
"js_code": jsCode,
"jscode": jsCode,
"gameId": "1001",
"game_id": "1001",
"callbackBase": "https://api.example.test/api/v1/zgame/callback",
@ -657,6 +662,11 @@ func TestLaunchGameBuildsZGameURL(t *testing.T) {
t.Fatalf("zgame launch query %s mismatch: got %q want %q url=%s", key, got, want, result.LaunchURL)
}
}
for _, key := range []string{"token", "code", "session", "cv", "channel_name"} {
if got := parsed.Query().Get(key); got != "" {
t.Fatalf("zgame launch query must not expose %s=%q url=%s", key, got, result.LaunchURL)
}
}
}
func TestLaunchGameRequiresBaishunLaunchURL(t *testing.T) {

View File

@ -59,6 +59,10 @@ func zgameConfigFromPlatform(value any) zgameAdapterConfig {
var config zgameAdapterConfig
_ = json.Unmarshal([]byte(strings.TrimSpace(raw)), &config)
config.UIDMode = strings.ToLower(strings.TrimSpace(config.UIDMode))
if config.UIDMode == "" {
// laluparty 游戏侧展示和签名都用短号作为 open_id历史配置未写 uid_mode 时按文档默认走 display_user_id。
config.UIDMode = "display_user_id"
}
config.DefaultLang = strings.TrimSpace(config.DefaultLang)
config.LaunchURLTemplate = strings.TrimSpace(config.LaunchURLTemplate)
config.CallbackBase = strings.TrimSpace(firstNonEmpty(config.CallbackBase, config.CallbackBaseAlias))
@ -159,7 +163,7 @@ func (s *Service) handleZGameLogin(ctx context.Context, app string, config zgame
if err != nil {
return zgameAdapterError(zgameCodeFromError(err), zgameMessageFromError(err), true, "")
}
raw, contentType := zgameJSON(zgameCodeOK, "success", zgameLoginData(session, config, jsCode, snapshot, s.now()))
raw, contentType := zgameJSON(zgameCodeOK, "success", zgameLoginData(session, config, openID, jsCode, snapshot, s.now()))
return raw, contentType, strconv.Itoa(zgameCodeOK), true, "", nil
}
@ -339,7 +343,7 @@ func (s *Service) validZGameSession(ctx context.Context, app string, platformCod
if token == "" {
return gamedomain.LaunchSession{}, xerr.New(xerr.Unauthorized, "session invalid")
}
// 当前 ZGame 不要求 App 原生生成一次性 jscode优先消费 App access token远程桥接脚本即可免发版接入
// 新版启动链路使用服务端短期 js_code保留 JWT 兼容旧测试链接,避免已打开的 H5 立即失效
if strings.Count(token, ".") == 2 {
if session, err := s.appSessionFromAccessToken(app, token); err == nil {
return session, nil
@ -377,8 +381,11 @@ func zgameSignatureValid(openID string, session string, signature string, secret
return subtle.ConstantTimeCompare([]byte(strings.ToLower(signature)), []byte(expected)) == 1
}
func zgameLoginData(session gamedomain.LaunchSession, config zgameAdapterConfig, token string, snapshot yomiUserSnapshot, now time.Time) map[string]any {
openID := externalUserID(session, config.UIDMode)
func zgameLoginData(session gamedomain.LaunchSession, config zgameAdapterConfig, requestedOpenID string, token string, snapshot yomiUserSnapshot, now time.Time) map[string]any {
openID := strings.TrimSpace(requestedOpenID)
if openID == "" {
openID = externalUserID(session, config.UIDMode)
}
return map[string]any{
"session": token,
"session_expired": zgameSessionTTLMS(session, now),

View File

@ -45,6 +45,7 @@ type RoomGuardClient interface {
// RoomQueryClient 抽象 gateway 对 room-service 读模型查询的依赖。
type RoomQueryClient interface {
ListRooms(ctx context.Context, req *roomv1.ListRoomsRequest) (*roomv1.ListRoomsResponse, error)
ListRoomsByOwners(ctx context.Context, req *roomv1.ListRoomsByOwnersRequest) (*roomv1.ListRoomsResponse, error)
ListRoomFeeds(ctx context.Context, req *roomv1.ListRoomFeedsRequest) (*roomv1.ListRoomsResponse, error)
ListRoomGiftLeaderboard(ctx context.Context, req *roomv1.ListRoomGiftLeaderboardRequest) (*roomv1.ListRoomGiftLeaderboardResponse, error)
GetMyRoom(ctx context.Context, req *roomv1.GetMyRoomRequest) (*roomv1.GetMyRoomResponse, error)
@ -197,6 +198,10 @@ func (c *grpcRoomQueryClient) ListRooms(ctx context.Context, req *roomv1.ListRoo
return c.client.ListRooms(ctx, req)
}
func (c *grpcRoomQueryClient) ListRoomsByOwners(ctx context.Context, req *roomv1.ListRoomsByOwnersRequest) (*roomv1.ListRoomsResponse, error) {
return c.client.ListRoomsByOwners(ctx, req)
}
func (c *grpcRoomQueryClient) ListRoomFeeds(ctx context.Context, req *roomv1.ListRoomFeedsRequest) (*roomv1.ListRoomsResponse, error) {
return c.client.ListRoomFeeds(ctx, req)
}

View File

@ -39,6 +39,7 @@ type UserProfileClient interface {
BusinessUserLookup(ctx context.Context, req *userv1.BusinessUserLookupRequest) (*userv1.BusinessUserLookupResponse, error)
GetMyProfileStats(ctx context.Context, req *userv1.GetMyProfileStatsRequest) (*userv1.GetMyProfileStatsResponse, error)
BatchGetUsers(ctx context.Context, req *userv1.BatchGetUsersRequest) (*userv1.BatchGetUsersResponse, error)
BatchGetRoomBasicUsers(ctx context.Context, req *userv1.BatchGetRoomBasicUsersRequest) (*userv1.BatchGetRoomBasicUsersResponse, error)
CompleteOnboarding(ctx context.Context, req *userv1.CompleteOnboardingRequest) (*userv1.CompleteOnboardingResponse, error)
UpdateUserProfile(ctx context.Context, req *userv1.UpdateUserProfileRequest) (*userv1.UpdateUserProfileResponse, error)
UpdateUserProfileBackground(ctx context.Context, req *userv1.UpdateUserProfileBackgroundRequest) (*userv1.UpdateUserProfileBackgroundResponse, error)
@ -121,6 +122,7 @@ type UserHostClient interface {
ListActiveCoinSellersInMyRegion(ctx context.Context, req *userv1.ListActiveCoinSellersInMyRegionRequest) (*userv1.ListActiveCoinSellersInMyRegionResponse, error)
GetUserRoleSummary(ctx context.Context, req *userv1.GetUserRoleSummaryRequest) (*userv1.GetUserRoleSummaryResponse, error)
GetAgency(ctx context.Context, req *userv1.GetAgencyRequest) (*userv1.GetAgencyResponse, error)
UpdateAgencyProfile(ctx context.Context, req *userv1.UpdateAgencyProfileRequest) (*userv1.UpdateAgencyProfileResponse, error)
CheckBusinessCapability(ctx context.Context, req *userv1.CheckBusinessCapabilityRequest) (*userv1.CheckBusinessCapabilityResponse, error)
GetAgencyMembers(ctx context.Context, req *userv1.GetAgencyMembersRequest) (*userv1.GetAgencyMembersResponse, error)
GetAgencyApplications(ctx context.Context, req *userv1.GetAgencyApplicationsRequest) (*userv1.GetAgencyApplicationsResponse, error)
@ -354,6 +356,10 @@ func (c *grpcUserProfileClient) BatchGetUsers(ctx context.Context, req *userv1.B
return c.client.BatchGetUsers(ctx, req)
}
func (c *grpcUserProfileClient) BatchGetRoomBasicUsers(ctx context.Context, req *userv1.BatchGetRoomBasicUsersRequest) (*userv1.BatchGetRoomBasicUsersResponse, error) {
return c.client.BatchGetRoomBasicUsers(ctx, req)
}
func (c *grpcUserProfileClient) CompleteOnboarding(ctx context.Context, req *userv1.CompleteOnboardingRequest) (*userv1.CompleteOnboardingResponse, error) {
return c.client.CompleteOnboarding(ctx, req)
}
@ -570,6 +576,10 @@ func (c *grpcUserHostClient) GetAgency(ctx context.Context, req *userv1.GetAgenc
return c.client.GetAgency(ctx, req)
}
func (c *grpcUserHostClient) UpdateAgencyProfile(ctx context.Context, req *userv1.UpdateAgencyProfileRequest) (*userv1.UpdateAgencyProfileResponse, error) {
return c.client.UpdateAgencyProfile(ctx, req)
}
func (c *grpcUserHostClient) CheckBusinessCapability(ctx context.Context, req *userv1.CheckBusinessCapabilityRequest) (*userv1.CheckBusinessCapabilityResponse, error) {
return c.client.CheckBusinessCapability(ctx, req)
}

View File

@ -113,6 +113,10 @@ func (f *fakeInviteActivityUserProfileClient) BatchGetUsers(_ context.Context, r
return &userv1.BatchGetUsersResponse{Users: f.users}, nil
}
func (f *fakeInviteActivityUserProfileClient) BatchGetRoomBasicUsers(context.Context, *userv1.BatchGetRoomBasicUsersRequest) (*userv1.BatchGetRoomBasicUsersResponse, error) {
return &userv1.BatchGetRoomBasicUsersResponse{}, nil
}
func (f *fakeInviteActivityUserProfileClient) GetUser(context.Context, *userv1.GetUserRequest) (*userv1.GetUserResponse, error) {
return &userv1.GetUserResponse{}, nil
}

View File

@ -183,6 +183,10 @@ func (f *fakeUserLeaderboardRoomQueryClient) ListRooms(context.Context, *roomv1.
return &roomv1.ListRoomsResponse{}, nil
}
func (f *fakeUserLeaderboardRoomQueryClient) ListRoomsByOwners(context.Context, *roomv1.ListRoomsByOwnersRequest) (*roomv1.ListRoomsResponse, error) {
return &roomv1.ListRoomsResponse{}, nil
}
func (f *fakeUserLeaderboardRoomQueryClient) ListRoomFeeds(context.Context, *roomv1.ListRoomFeedsRequest) (*roomv1.ListRoomsResponse, error) {
return &roomv1.ListRoomsResponse{}, nil
}

View File

@ -83,6 +83,9 @@ type UserHandlers struct {
GetMyRoleSummary http.HandlerFunc
SearchHostAgencies http.HandlerFunc
ApplyToHostAgency http.HandlerFunc
GetAgencyProfile http.HandlerFunc
ListAgencyRooms http.HandlerFunc
UpdateAgencyProfile http.HandlerFunc
GetHostCenterAgency http.HandlerFunc
GetHostCenterPlatformPolicy http.HandlerFunc
GetAgencyCenterOverview http.HandlerFunc
@ -436,6 +439,9 @@ func (r routes) registerUserRoutes() {
r.profile("/users/me/role-summary", "", h.GetMyRoleSummary)
r.profile("/host/agencies/search", http.MethodGet, h.SearchHostAgencies)
r.profile("/host/agency-applications", http.MethodPost, h.ApplyToHostAgency)
r.profile("/agencies/profile", http.MethodGet, h.GetAgencyProfile)
r.profile("/agencies/rooms", http.MethodGet, h.ListAgencyRooms)
r.profile("/agencies/profile/update", http.MethodPost, h.UpdateAgencyProfile)
r.profile("/host-center/agency", http.MethodGet, h.GetHostCenterAgency)
r.profile("/host-center/platform-policy", http.MethodGet, h.GetHostCenterPlatformPolicy)
r.profile("/agency-center/overview", http.MethodGet, h.GetAgencyCenterOverview)

View File

@ -319,6 +319,8 @@ type fakeUserProfileClient struct {
lastStats *userv1.GetMyProfileStatsRequest
lastBatch *userv1.BatchGetUsersRequest
batchRequests []*userv1.BatchGetUsersRequest
lastRoomBasicBatch *userv1.BatchGetRoomBasicUsersRequest
roomBasicRequests []*userv1.BatchGetRoomBasicUsersRequest
getRequests []*userv1.GetUserRequest
lastComplete *userv1.CompleteOnboardingRequest
lastUpdate *userv1.UpdateUserProfileRequest
@ -379,6 +381,7 @@ type fakeUserIdentityClient struct {
type fakeRoomQueryClient struct {
lastList *roomv1.ListRoomsRequest
lastListOwners *roomv1.ListRoomsByOwnersRequest
lastFeeds *roomv1.ListRoomFeedsRequest
lastMyRoom *roomv1.GetMyRoomRequest
lastCurrent *roomv1.GetCurrentRoomRequest
@ -388,6 +391,7 @@ type fakeRoomQueryClient struct {
lastOnline *roomv1.ListRoomOnlineUsersRequest
lastBannedUsers *roomv1.ListRoomBannedUsersRequest
resp *roomv1.ListRoomsResponse
ownersResp *roomv1.ListRoomsResponse
feedsResp *roomv1.ListRoomsResponse
myRoomResp *roomv1.GetMyRoomResponse
currentResp *roomv1.GetCurrentRoomResponse
@ -397,6 +401,7 @@ type fakeRoomQueryClient struct {
onlineResp *roomv1.ListRoomOnlineUsersResponse
bannedUsersResp *roomv1.ListRoomBannedUsersResponse
err error
ownersErr error
feedsErr error
myRoomErr error
currentErr error
@ -507,6 +512,9 @@ type fakeUserHostClient struct {
lastGetAgency *userv1.GetAgencyRequest
getAgencyResp *userv1.GetAgencyResponse
getAgencyErr error
lastUpdateAgency *userv1.UpdateAgencyProfileRequest
updateAgencyResp *userv1.UpdateAgencyProfileResponse
updateAgencyErr error
lastMembers *userv1.GetAgencyMembersRequest
membersResp *userv1.GetAgencyMembersResponse
membersErr error
@ -977,6 +985,32 @@ func (f *fakeUserProfileClient) BatchGetUsers(_ context.Context, req *userv1.Bat
return &userv1.BatchGetUsersResponse{Users: users}, nil
}
func (f *fakeUserProfileClient) BatchGetRoomBasicUsers(_ context.Context, req *userv1.BatchGetRoomBasicUsersRequest) (*userv1.BatchGetRoomBasicUsersResponse, error) {
f.lastRoomBasicBatch = req
f.roomBasicRequests = append(f.roomBasicRequests, req)
users := make(map[int64]*userv1.RoomBasicUser, len(req.GetUserIds()))
for _, userID := range req.GetUserIds() {
if f.usersByID != nil {
if user, ok := f.usersByID[userID]; ok {
users[userID] = &userv1.RoomBasicUser{
UserId: user.GetUserId(),
Username: user.GetUsername(),
Avatar: user.GetAvatar(),
DisplayUserId: user.GetDisplayUserId(),
}
continue
}
}
users[userID] = &userv1.RoomBasicUser{
UserId: userID,
Username: "user-" + strconv.FormatInt(userID, 10),
Avatar: "https://cdn.example/avatar.png",
DisplayUserId: strconv.FormatInt(100000+userID, 10),
}
}
return &userv1.BatchGetRoomBasicUsersResponse{Users: users}, nil
}
func (f *fakeUserProfileClient) CompleteOnboarding(_ context.Context, req *userv1.CompleteOnboardingRequest) (*userv1.CompleteOnboardingResponse, error) {
f.lastComplete = req
if f.completeErr != nil {
@ -1205,6 +1239,17 @@ func (f *fakeRoomQueryClient) ListRooms(_ context.Context, req *roomv1.ListRooms
return &roomv1.ListRoomsResponse{}, nil
}
func (f *fakeRoomQueryClient) ListRoomsByOwners(_ context.Context, req *roomv1.ListRoomsByOwnersRequest) (*roomv1.ListRoomsResponse, error) {
f.lastListOwners = req
if f.ownersErr != nil {
return nil, f.ownersErr
}
if f.ownersResp != nil {
return f.ownersResp, nil
}
return &roomv1.ListRoomsResponse{}, nil
}
func (f *fakeRoomQueryClient) ListRoomFeeds(_ context.Context, req *roomv1.ListRoomFeedsRequest) (*roomv1.ListRoomsResponse, error) {
f.lastFeeds = req
if f.feedsErr != nil {
@ -1751,6 +1796,23 @@ func (f *fakeUserHostClient) GetAgency(_ context.Context, req *userv1.GetAgencyR
return &userv1.GetAgencyResponse{}, nil
}
func (f *fakeUserHostClient) UpdateAgencyProfile(_ context.Context, req *userv1.UpdateAgencyProfileRequest) (*userv1.UpdateAgencyProfileResponse, error) {
f.lastUpdateAgency = req
if f.updateAgencyErr != nil {
return nil, f.updateAgencyErr
}
if f.updateAgencyResp != nil {
return f.updateAgencyResp, nil
}
return &userv1.UpdateAgencyProfileResponse{Agency: &userv1.Agency{
AgencyId: req.GetAgencyId(),
OwnerUserId: req.GetOperatorUserId(),
Name: req.GetName(),
Avatar: req.GetAvatar(),
Status: "active",
}}, nil
}
func (f *fakeUserHostClient) CheckBusinessCapability(_ context.Context, req *userv1.CheckBusinessCapabilityRequest) (*userv1.CheckBusinessCapabilityResponse, error) {
f.lastCapability = req
f.capabilities = append(f.capabilities, req.GetCapability())
@ -3281,6 +3343,96 @@ func TestJoinRoomReturnsInitialDataWithIMGroupRTCTokenAndProfiles(t *testing.T)
}
}
func TestJoinRoomLiteReturnsFirstScreenFieldsWithoutDisplayHydration(t *testing.T) {
roomClient := &fakeRoomClient{joinResp: &roomv1.JoinRoomResponse{
Result: &roomv1.CommandResult{Applied: true, RoomVersion: 8, ServerTimeMs: 1_700_000_010_123},
User: &roomv1.RoomUser{UserId: 42, Role: "audience", JoinedAtMs: 1_700_000_010_000, LastSeenAtMs: 1_700_000_010_100},
Room: &roomv1.RoomSnapshot{
RoomId: "room_lite",
OwnerUserId: 101,
Mode: "voice",
Status: "active",
ChatEnabled: true,
MicSeats: []*roomv1.SeatState{{SeatNo: 1, UserId: 102, PublishState: "publishing"}, {SeatNo: 2, Locked: true}},
GiftRank: []*roomv1.RankItem{{UserId: 301, Score: 900, GiftValue: 900}},
RoomExt: map[string]string{"title": "Lite Room", "cover_url": "https://cdn.example/lite.png"},
Heat: 88,
Version: 8,
RoomShortId: "100102",
OnlineCount: 200,
Locked: true,
},
}}
walletClient := &fakeWalletClient{batchEquippedResp: &walletv1.BatchGetUserEquippedResourcesResponse{}}
profileClient := &fakeUserProfileClient{}
handler := NewHandlerWithConfig(roomClient, nil, nil, nil, TencentIMConfig{}, profileClient)
handler.SetWalletClient(walletClient)
handler.SetTencentRTC(TencentRTCConfig{
Enabled: true,
SDKAppID: 1400000000,
SecretKey: "secret",
UserSigTTL: 2 * time.Hour,
RoomIDType: "string",
AppScene: "voice_chat_room",
})
router := handler.Routes(auth.NewVerifier("secret"))
request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/join", bytes.NewReader([]byte(`{"room_id":"room_lite","command_id":"cmd-join-lite","role":"audience","response_mode":"lite"}`)))
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
request.Header.Set("X-Request-ID", "req-join-lite")
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 roomClient.lastJoin == nil || roomClient.lastJoin.GetResponseMode() != "lite" || roomClient.lastJoin.GetEntryVehicle() != nil {
t.Fatalf("lite join must pass response_mode and skip entry vehicle: %+v", roomClient.lastJoin)
}
if len(walletClient.batchEquippedRequests) != 0 {
t.Fatalf("lite join must not query wallet appearance or vehicle resources: %+v", walletClient.batchEquippedRequests)
}
if len(profileClient.batchRequests) != 0 {
t.Fatalf("lite join must not call full BatchGetUsers: %+v", profileClient.batchRequests)
}
if profileClient.lastRoomBasicBatch == nil || strings.Join(int64SliceToStrings(profileClient.lastRoomBasicBatch.GetUserIds()), ",") != "101,42,102" {
t.Fatalf("lite join must batch only owner/viewer/mic basic users: %+v", profileClient.lastRoomBasicBatch)
}
var response httpkit.ResponseEnvelope
if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
t.Fatalf("decode lite join response failed: %v", err)
}
data := response.Data.(map[string]any)
if _, exists := data["contribution_rank"]; exists {
t.Fatalf("lite join must not include contribution rank: %+v", data["contribution_rank"])
}
room := data["room"].(map[string]any)
if room["room_id"] != "room_lite" || room["online_count"].(float64) != 200 {
t.Fatalf("lite join must keep core room fields and online_count: %+v", room)
}
profiles := data["profiles"].([]any)
if len(profiles) != 3 {
t.Fatalf("lite join must include basic profiles for owner/viewer/mic only: %+v", profiles)
}
viewerProfile := map[string]any{}
for _, item := range profiles {
profile := item.(map[string]any)
if profile["user_id"] == "42" {
viewerProfile = profile
}
if profile["user_id"] == "301" {
t.Fatalf("lite join must not hydrate contribution-rank users: %+v", profiles)
}
}
if viewerProfile["username"] != "user-42" || viewerProfile["avatar"] == "" || viewerProfile["display_user_id"] == "" {
t.Fatalf("lite profile must carry only basic display fields: %+v", viewerProfile)
}
if _, exists := viewerProfile["avatar_frame"]; exists {
t.Fatalf("lite profile must not carry appearance fields: %+v", viewerProfile)
}
}
func TestListRoomsUsesUserRegionFromUserService(t *testing.T) {
profileClient := &fakeUserProfileClient{regionID: 1001}
regionClient := &fakeUserRegionClient{resp: &userv1.RegionResponse{Region: &userv1.Region{
@ -5474,6 +5626,7 @@ func TestHostCenterAgencyUsesCurrentHostAgencyAndOwnerProfile(t *testing.T) {
OwnerUserId: 99,
RegionId: 30,
Name: "Yumi Star Agency",
Avatar: "https://cdn.example/agency.png",
Status: "active",
}},
}
@ -5508,11 +5661,198 @@ func TestHostCenterAgencyUsesCurrentHostAgencyAndOwnerProfile(t *testing.T) {
}
data := response.Data.(map[string]any)
agency := data["agency"].(map[string]any)
if agency["agency_id"] != "7001" || agency["short_id"] != "163003" || agency["avatar"] != "https://cdn.example/agency-owner.png" || agency["name"] != "Yumi Star Agency" {
if agency["agency_id"] != "7001" || agency["short_id"] != "163003" || agency["avatar"] != "https://cdn.example/agency.png" || agency["name"] != "Yumi Star Agency" {
t.Fatalf("host center agency response mismatch: %+v", agency)
}
}
func TestGetAgencyProfileReturnsIndependentAgencyAvatar(t *testing.T) {
hostClient := &fakeUserHostClient{
hostProfile: &userv1.HostProfile{UserId: 88, Status: "active", RegionId: 30, CurrentAgencyId: 7001},
getAgencyResp: &userv1.GetAgencyResponse{Agency: &userv1.Agency{
AgencyId: 7001,
OwnerUserId: 99,
RegionId: 30,
Name: "Independent Agency",
Avatar: "https://cdn.example/agency-avatar.png",
HostCount: 3,
Status: "active",
JoinEnabled: true,
}},
}
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{})
handler.SetUserHostClient(hostClient)
router := handler.Routes(auth.NewVerifier("secret"))
request := httptest.NewRequest(http.MethodGet, "/api/v1/agencies/profile?user_id=88", nil)
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
request.Header.Set("X-Request-ID", "req-agency-profile")
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 hostClient.lastHost == nil || hostClient.lastHost.GetUserId() != 88 || hostClient.lastGetAgency == nil || hostClient.lastGetAgency.GetAgencyId() != 7001 {
t.Fatalf("agency profile upstream requests mismatch: host=%+v agency=%+v", hostClient.lastHost, hostClient.lastGetAgency)
}
var response httpkit.ResponseEnvelope
if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
t.Fatalf("decode response failed: %v", err)
}
data := response.Data.(map[string]any)
agency := data["agency"].(map[string]any)
if agency["agency_id"] != "7001" || agency["owner_user_id"] != "99" || agency["name"] != "Independent Agency" || agency["avatar"] != "https://cdn.example/agency-avatar.png" || agency["host_count"] != float64(3) {
t.Fatalf("agency profile response mismatch: %+v", agency)
}
}
func TestGetAgencyProfileReturnsNullWhenTargetNotActiveHost(t *testing.T) {
hostClient := &fakeUserHostClient{hostProfile: &userv1.HostProfile{UserId: 88, Status: "inactive", RegionId: 30, CurrentAgencyId: 7001}}
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{})
handler.SetUserHostClient(hostClient)
router := handler.Routes(auth.NewVerifier("secret"))
request := httptest.NewRequest(http.MethodGet, "/api/v1/agencies/profile?user_id=88", nil)
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
request.Header.Set("X-Request-ID", "req-agency-profile-empty")
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 hostClient.lastGetAgency != nil {
t.Fatalf("inactive host must not fetch agency: %+v", hostClient.lastGetAgency)
}
var response httpkit.ResponseEnvelope
if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
t.Fatalf("decode response failed: %v", err)
}
data := response.Data.(map[string]any)
if data["agency"] != nil {
t.Fatalf("inactive host must return null agency: %+v", data)
}
}
func TestListAgencyRoomsUsesActiveAgencyHosts(t *testing.T) {
hostClient := &fakeUserHostClient{
hostProfile: &userv1.HostProfile{UserId: 88, Status: "active", RegionId: 30, CurrentAgencyId: 7001},
getAgencyResp: &userv1.GetAgencyResponse{Agency: &userv1.Agency{
AgencyId: 7001,
OwnerUserId: 99,
RegionId: 30,
Name: "Independent Agency",
Avatar: "https://cdn.example/agency-avatar.png",
Status: "active",
}},
membersResp: &userv1.GetAgencyMembersResponse{Memberships: []*userv1.AgencyMembership{
{MembershipId: 1, AgencyId: 7001, HostUserId: 101, Status: "active"},
{MembershipId: 2, AgencyId: 7001, HostUserId: 102, Status: "inactive"},
{MembershipId: 3, AgencyId: 7001, HostUserId: 99, Status: "active"},
}},
}
roomClient := &fakeRoomQueryClient{ownersResp: &roomv1.ListRoomsResponse{
Rooms: []*roomv1.RoomListItem{{
RoomId: "room-101",
OwnerUserId: 101,
Title: "Agency Room",
CoverUrl: "https://cdn.example/room.png",
Mode: "voice",
Status: "active",
Heat: 88,
OnlineCount: 12,
SeatCount: 8,
OccupiedSeatCount: 3,
RoomShortId: "M20",
CountryCode: "AE",
}},
NextCursor: "next-owner-room",
}}
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{})
handler.SetUserHostClient(hostClient)
handler.SetRoomQueryClient(roomClient)
router := handler.Routes(auth.NewVerifier("secret"))
request := httptest.NewRequest(http.MethodGet, "/api/v1/agencies/rooms?user_id=88&tab=new&limit=120&cursor=cursor-1&query=karaoke", nil)
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
request.Header.Set("X-Request-ID", "req-agency-rooms")
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 hostClient.lastMembers == nil || hostClient.lastMembers.GetAgencyId() != 7001 || hostClient.lastMembers.GetStatus() != "active" {
t.Fatalf("agency members request mismatch: %+v", hostClient.lastMembers)
}
if roomClient.lastListOwners == nil {
t.Fatal("agency rooms must call ListRoomsByOwners")
}
ownerIDs := roomClient.lastListOwners.GetOwnerUserIds()
if len(ownerIDs) != 2 || ownerIDs[0] != 99 || ownerIDs[1] != 101 {
t.Fatalf("owner ids mismatch: got %v request=%+v", roomClient.lastListOwners.GetOwnerUserIds(), roomClient.lastListOwners)
}
if roomClient.lastListOwners.GetViewerUserId() != 42 || roomClient.lastListOwners.GetTab() != "new" || roomClient.lastListOwners.GetLimit() != 50 || roomClient.lastListOwners.GetCursor() != "cursor-1" || roomClient.lastListOwners.GetQuery() != "karaoke" {
t.Fatalf("agency rooms request mismatch: %+v", roomClient.lastListOwners)
}
var response httpkit.ResponseEnvelope
if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
t.Fatalf("decode response failed: %v", err)
}
data := response.Data.(map[string]any)
rooms := data["rooms"].([]any)
first := rooms[0].(map[string]any)
if data["agency_id"] != "7001" || data["next_cursor"] != "next-owner-room" || first["room_id"] != "room-101" || first["owner_user_id"] != "101" || first["country_flag"] != "🇦🇪" {
t.Fatalf("agency rooms response mismatch: %+v", response)
}
}
func TestUpdateAgencyProfileForwardsOwnerCommand(t *testing.T) {
hostClient := &fakeUserHostClient{
roleSummary: &userv1.UserRoleSummary{UserId: 42, IsAgency: true, AgencyId: 7001},
getAgencyResp: &userv1.GetAgencyResponse{Agency: &userv1.Agency{AgencyId: 7001, OwnerUserId: 42, RegionId: 30, Name: "Old Agency", Avatar: "https://cdn.example/old.png", Status: "active"}},
updateAgencyResp: &userv1.UpdateAgencyProfileResponse{Agency: &userv1.Agency{
AgencyId: 7001,
OwnerUserId: 42,
RegionId: 30,
Name: "New Agency",
Avatar: "https://cdn.example/new.png",
Status: "active",
}},
}
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{})
handler.SetUserHostClient(hostClient)
router := handler.Routes(auth.NewVerifier("secret"))
request := httptest.NewRequest(http.MethodPost, "/api/v1/agencies/profile/update", bytes.NewReader([]byte(`{"command_id":"cmd-agency-profile","name":" New Agency ","avatar":" https://cdn.example/new.png "}`)))
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
request.Header.Set("X-Request-ID", "req-agency-profile-update")
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 hostClient.lastUpdateAgency == nil ||
hostClient.lastUpdateAgency.GetCommandId() != "cmd-agency-profile" ||
hostClient.lastUpdateAgency.GetOperatorUserId() != 42 ||
hostClient.lastUpdateAgency.GetAgencyId() != 7001 ||
hostClient.lastUpdateAgency.GetName() != "New Agency" ||
hostClient.lastUpdateAgency.GetAvatar() != "https://cdn.example/new.png" {
t.Fatalf("update agency profile request mismatch: %+v", hostClient.lastUpdateAgency)
}
var response httpkit.ResponseEnvelope
if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
t.Fatalf("decode response failed: %v", err)
}
data := response.Data.(map[string]any)
agency := data["agency"].(map[string]any)
if agency["agency_id"] != "7001" || agency["name"] != "New Agency" || agency["avatar"] != "https://cdn.example/new.png" {
t.Fatalf("update agency profile response mismatch: %+v", agency)
}
}
func TestHostCenterPlatformPolicyUsesCurrentHostRegion(t *testing.T) {
hostClient := &fakeUserHostClient{hostProfile: &userv1.HostProfile{
UserId: 42,

View File

@ -37,6 +37,10 @@ const (
// 机器人房标记来自 room-service RoomExtgateway 只用它决定首屏资料是否补机器人头像昵称。
roomExtRobotRoomKey = "robot_room"
roomExtRobotUserIDsKey = "robot_user_ids"
// JoinRoom 默认保持历史完整包lite 只保留进房首屏必要字段,把装扮/榜单等增强展示后移。
joinRoomResponseModeFull = "full"
joinRoomResponseModeLite = "lite"
)
type flexibleInt64 int64
@ -1092,15 +1096,21 @@ func trimOptionalString(value *string) {
// joinRoom 把进房 HTTP JSON 请求转换为 room-service JoinRoom 命令,并组装房间首屏初始化包。
func (h *Handler) joinRoom(writer http.ResponseWriter, request *http.Request) {
var body struct {
RoomID string `json:"room_id"`
CommandID string `json:"command_id"`
Role string `json:"role"`
Password string `json:"password"`
RoomID string `json:"room_id"`
CommandID string `json:"command_id"`
Role string `json:"role"`
Password string `json:"password"`
ResponseMode string `json:"response_mode"`
}
if !httpkit.Decode(writer, request, &body) {
return
}
responseMode, ok := joinRoomResponseMode(request, body.ResponseMode)
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid response_mode")
return
}
actorSnapshot, err := h.resolveRoomActorJoinSnapshot(request)
if err != nil {
httpkit.WriteRPCError(writer, request, err)
@ -1115,16 +1125,42 @@ func (h *Handler) joinRoom(writer http.ResponseWriter, request *http.Request) {
ActorRegionId: actorSnapshot.regionID,
ActorDisplayProfile: actorSnapshot.displayProfile,
// 座驾快照在进房命令提交前生成room-service 只保存快照,不回查 wallet。
EntryVehicle: h.joinRoomEntryVehicleSnapshot(request),
EntryVehicle: h.joinRoomEntryVehicleSnapshot(request, responseMode),
ResponseMode: responseMode,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
if responseMode == joinRoomResponseModeLite {
httpkit.WriteOK(writer, request, h.joinRoomInitialLiteData(request, body.RoomID, resp))
return
}
httpkit.WriteOK(writer, request, h.joinRoomInitialData(request, body.RoomID, resp))
}
func (h *Handler) joinRoomEntryVehicleSnapshot(request *http.Request) *roomv1.RoomEntryVehicleSnapshot {
func joinRoomResponseMode(request *http.Request, bodyMode string) (string, bool) {
mode := strings.TrimSpace(bodyMode)
if mode == "" {
mode = strings.TrimSpace(request.URL.Query().Get("response_mode"))
}
if mode == "" {
return joinRoomResponseModeFull, true
}
mode = strings.ToLower(mode)
switch mode {
case joinRoomResponseModeFull, joinRoomResponseModeLite:
return mode, true
default:
return "", false
}
}
func (h *Handler) joinRoomEntryVehicleSnapshot(request *http.Request, responseMode string) *roomv1.RoomEntryVehicleSnapshot {
if responseMode == joinRoomResponseModeLite {
// lite 进房只提交 presence 必需事实;座驾属于入场展示增强,后续由资料/装扮接口补齐。
return nil
}
if h.walletClient == nil {
// 座驾只影响进房展示,不应该因为 wallet 暂不可用阻断 JoinRoom。
return nil
@ -1198,6 +1234,30 @@ func (h *Handler) joinRoomInitialData(request *http.Request, requestedRoomID str
}
}
func (h *Handler) joinRoomInitialLiteData(request *http.Request, requestedRoomID string, resp *roomv1.JoinRoomResponse) joinRoomLiteData {
if resp == nil {
return joinRoomLiteData{}
}
viewerUserID := auth.UserIDFromContext(request.Context())
snapshot := resp.GetRoom()
roomID := snapshot.GetRoomId()
if roomID == "" {
// lite 和完整包共用同一兜底,避免异常上游只回 result 时客户端拿不到 IM/RTC 房间 ID。
roomID = strings.TrimSpace(requestedRoomID)
}
return joinRoomLiteData{
Result: commandResultDataFromProto(resp.GetResult()),
Room: roomInitialRoomDataFromSnapshot(snapshot, roomID),
Viewer: roomViewerDataFromProto(resp.GetUser(), viewerUserID),
Seats: roomSeatDataFromSnapshot(snapshot),
Profiles: h.roomInitialBasicProfiles(request, snapshot, viewerUserID),
IM: roomIMData{GroupID: roomIMGroupID(roomID), NeedJoinGroup: true},
RTC: h.joinRoomRTCData(roomID, viewerUserID),
ServerTimeMS: resp.GetResult().GetServerTimeMs(),
}
}
func (h *Handler) joinRoomRTCData(roomID string, viewerUserID int64) roomRTCData {
rtc := roomRTCData{NeedToken: true}
token, err := h.generateTencentRTCToken(viewerUserID, roomID)
@ -1219,6 +1279,50 @@ func (h *Handler) roomInitialProfiles(request *http.Request, snapshot *roomv1.Ro
return h.roomDisplayProfiles(request, userIDs)
}
func (h *Handler) roomInitialBasicProfiles(request *http.Request, snapshot *roomv1.RoomSnapshot, viewerUserID int64) []roomBasicProfileData {
userIDs := roomInitialBasicProfileUserIDs(snapshot, viewerUserID)
profileMap := h.roomBasicProfileMap(request, userIDs)
profiles := make([]roomBasicProfileData, 0, len(userIDs))
for _, userID := range userIDs {
if profile, ok := profileMap[userID]; ok {
profiles = append(profiles, profile)
}
}
return profiles
}
func (h *Handler) roomBasicProfileMap(request *http.Request, userIDs []int64) map[int64]roomBasicProfileData {
if h.userProfileClient == nil {
return nil
}
userIDs = uniquePositiveUserIDs(userIDs)
if len(userIDs) == 0 {
return nil
}
resp, err := h.userProfileClient.BatchGetRoomBasicUsers(request.Context(), &userv1.BatchGetRoomBasicUsersRequest{
Meta: httpkit.UserMeta(request, ""),
UserIds: userIDs,
})
if err != nil {
// lite 首屏资料是展示兜底,失败不能回滚已经成功的进房 presence。
return nil
}
profiles := make(map[int64]roomBasicProfileData, len(resp.GetUsers()))
for _, userID := range userIDs {
user := resp.GetUsers()[userID]
if user == nil {
continue
}
profiles[userID] = roomBasicProfileData{
UserID: formatOptionalUserID(user.GetUserId()),
Username: strings.TrimSpace(user.GetUsername()),
Avatar: strings.TrimSpace(user.GetAvatar()),
DisplayUserID: strings.TrimSpace(user.GetDisplayUserId()),
}
}
return profiles
}
// roomHeartbeat 把客户端显式心跳转换为 room-service presence refresh 命令。
func (h *Handler) roomHeartbeat(writer http.ResponseWriter, request *http.Request) {
var body struct {

View File

@ -273,6 +273,10 @@ func (c *shareRoomQueryClient) ListRooms(context.Context, *roomv1.ListRoomsReque
return &roomv1.ListRoomsResponse{}, nil
}
func (c *shareRoomQueryClient) ListRoomsByOwners(context.Context, *roomv1.ListRoomsByOwnersRequest) (*roomv1.ListRoomsResponse, error) {
return &roomv1.ListRoomsResponse{}, nil
}
func (c *shareRoomQueryClient) ListRoomFeeds(context.Context, *roomv1.ListRoomFeedsRequest) (*roomv1.ListRoomsResponse, error) {
return &roomv1.ListRoomsResponse{}, nil
}

View File

@ -73,6 +73,17 @@ type joinRoomData struct {
ServerTimeMS int64 `json:"server_time_ms"`
}
type joinRoomLiteData struct {
Result roomCommandResultData `json:"result"`
Room roomInitialData `json:"room"`
Viewer roomViewerData `json:"viewer"`
Seats []roomSeatData `json:"seats"`
Profiles []roomBasicProfileData `json:"profiles"`
IM roomIMData `json:"im"`
RTC roomRTCData `json:"rtc"`
ServerTimeMS int64 `json:"server_time_ms"`
}
type roomDetailData struct {
Room roomInitialData `json:"room"`
Viewer roomViewerData `json:"viewer"`
@ -176,6 +187,13 @@ type roomDisplayProfileData struct {
Charm int64 `json:"charm"`
}
type roomBasicProfileData struct {
UserID string `json:"user_id"`
Username string `json:"username"`
Avatar string `json:"avatar"`
DisplayUserID string `json:"display_user_id"`
}
type roomGiftPanelData struct {
CoinBalance int64 `json:"coin_balance"`
Recipients []roomGiftRecipientData `json:"recipients"`
@ -797,6 +815,11 @@ func roomInitialRoomDataFromSnapshot(snapshot *roomv1.RoomSnapshot, roomID strin
roomShortID = ext["room_short_id"]
}
seatCount, occupiedSeatCount := roomSeatCounts(snapshot)
onlineCount := snapshot.GetOnlineCount()
if onlineCount <= 0 && len(snapshot.GetOnlineUsers()) > 0 {
// 老快照或非 lite 上游可能还没有 online_count 字段;此时用明细长度保持兼容。
onlineCount = int32(len(snapshot.GetOnlineUsers()))
}
return roomInitialData{
RoomID: roomID,
IMGroupID: roomIMGroupID(roomID),
@ -811,7 +834,7 @@ func roomInitialRoomDataFromSnapshot(snapshot *roomv1.RoomSnapshot, roomID strin
ChatEnabled: snapshot.GetChatEnabled(),
Locked: snapshot.GetLocked(),
Heat: snapshot.GetHeat(),
OnlineCount: int32(len(snapshot.GetOnlineUsers())),
OnlineCount: onlineCount,
SeatCount: seatCount,
OccupiedSeatCount: occupiedSeatCount,
VisibleRegionID: snapshot.GetVisibleRegionId(),
@ -967,6 +990,25 @@ func roomInitialProfileUserIDs(snapshot *roomv1.RoomSnapshot, viewerUserID int64
return userIDs
}
func roomInitialBasicProfileUserIDs(snapshot *roomv1.RoomSnapshot, viewerUserID int64) []int64 {
seen := map[int64]bool{}
userIDs := make([]int64, 0, len(snapshot.GetMicSeats())+2)
add := func(userID int64) {
if userID <= 0 || seen[userID] {
return
}
seen[userID] = true
userIDs = append(userIDs, userID)
}
add(snapshot.GetOwnerUserId())
add(viewerUserID)
for _, seat := range snapshot.GetMicSeats() {
add(seat.GetUserId())
}
return userIDs
}
func roomOnlineUserDataFromProto(resp *roomv1.ListRoomOnlineUsersResponse, profiles map[int64]roomDisplayProfileData) roomOnlineUsersData {
if resp == nil {
return roomOnlineUsersData{}

View File

@ -66,6 +66,7 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler {
UserCPClient: h.userCPClient,
UserCountryClient: h.userCountryClient,
UserHostClient: h.userHostClient,
RoomQueryClient: h.roomQueryClient,
WalletClient: h.walletClient,
GrowthLevelClient: h.growthLevelClient,
AchievementClient: h.achievementClient,

View File

@ -0,0 +1,348 @@
package userapi
import (
"net/http"
"strconv"
"strings"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"hyapp/pkg/imgroup"
"hyapp/services/gateway-service/internal/auth"
"hyapp/services/gateway-service/internal/transport/http/httpkit"
roomv1 "hyapp.local/api/proto/room/v1"
userv1 "hyapp.local/api/proto/user/v1"
)
const maxAgencyRoomListLimit = 50
type agencyRoomsData struct {
AgencyID string `json:"agency_id"`
Rooms []agencyRoomItemData `json:"rooms"`
NextCursor string `json:"next_cursor"`
}
type agencyRoomItemData struct {
RoomID string `json:"room_id"`
IMGroupID string `json:"im_group_id"`
OwnerUserID string `json:"owner_user_id,omitempty"`
Title string `json:"title,omitempty"`
CoverURL string `json:"cover_url,omitempty"`
Mode string `json:"mode,omitempty"`
Status string `json:"status,omitempty"`
Locked bool `json:"locked"`
Heat int64 `json:"heat"`
OnlineCount int32 `json:"online_count"`
SeatCount int32 `json:"seat_count"`
OccupiedSeatCount int32 `json:"occupied_seat_count"`
VisibleRegionID int64 `json:"visible_region_id,omitempty"`
AppCode string `json:"app_code,omitempty"`
RoomShortID string `json:"room_short_id,omitempty"`
CountryCode string `json:"country_code,omitempty"`
CountryFlag string `json:"country_flag,omitempty"`
}
func (h *Handler) getAgencyProfile(writer http.ResponseWriter, request *http.Request) {
targetUserID, ok := positiveInt64Query(request, "user_id")
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
agency, found, err := h.resolveAgencyForTargetHost(request, targetUserID)
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
if !found {
httpkit.WriteOK(writer, request, map[string]any{"agency": nil})
return
}
httpkit.WriteOK(writer, request, map[string]any{"agency": hostAgencyFromProto(agency)})
}
func (h *Handler) listAgencyRooms(writer http.ResponseWriter, request *http.Request) {
targetUserID, ok := positiveInt64Query(request, "user_id")
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
limit, ok := parseAgencyRoomListLimit(request.URL.Query().Get("limit"))
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
tab, ok := parseAgencyRoomListTab(request.URL.Query().Get("tab"))
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
if h.roomQueryClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
agency, found, err := h.resolveAgencyForTargetHost(request, targetUserID)
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
if !found {
httpkit.WriteOK(writer, request, agencyRoomsData{Rooms: []agencyRoomItemData{}})
return
}
ownerUserIDs, err := h.activeAgencyHostUserIDs(request, agency)
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
if len(ownerUserIDs) == 0 {
httpkit.WriteOK(writer, request, agencyRoomsData{AgencyID: int64String(agency.GetAgencyId()), Rooms: []agencyRoomItemData{}})
return
}
resp, err := h.roomQueryClient.ListRoomsByOwners(request.Context(), &roomv1.ListRoomsByOwnersRequest{
Meta: httpkit.RoomMeta(request, "", ""),
ViewerUserId: auth.UserIDFromContext(request.Context()),
OwnerUserIds: ownerUserIDs,
Tab: tab,
Cursor: strings.TrimSpace(request.URL.Query().Get("cursor")),
Limit: limit,
Query: agencyRoomListQuery(request),
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, agencyRoomsData{
AgencyID: int64String(agency.GetAgencyId()),
Rooms: agencyRoomItemsFromProto(resp.GetRooms()),
NextCursor: resp.GetNextCursor(),
})
}
func (h *Handler) updateAgencyProfile(writer http.ResponseWriter, request *http.Request) {
agency, ok := h.resolveCurrentOwnerAgency(writer, request)
if !ok {
return
}
var body struct {
CommandID string `json:"command_id"`
CommandIDAlt string `json:"commandId"`
Name *string `json:"name"`
Avatar *string `json:"avatar"`
}
if !httpkit.Decode(writer, request, &body) {
return
}
commandID := strings.TrimSpace(body.CommandID)
if commandID == "" {
commandID = strings.TrimSpace(body.CommandIDAlt)
}
name := trimmedOptionalString(body.Name)
avatar := trimmedOptionalString(body.Avatar)
if commandID == "" || (name == nil && avatar == nil) || optionalStringIsEmpty(name) || optionalStringIsEmpty(avatar) {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
resp, err := h.userHostClient.UpdateAgencyProfile(request.Context(), &userv1.UpdateAgencyProfileRequest{
Meta: httpkit.UserMeta(request, ""),
CommandId: commandID,
OperatorUserId: auth.UserIDFromContext(request.Context()),
AgencyId: agency.GetAgencyId(),
Name: name,
Avatar: avatar,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, map[string]any{"agency": hostAgencyFromProto(resp.GetAgency())})
}
func (h *Handler) resolveAgencyForTargetHost(request *http.Request, targetUserID int64) (*userv1.Agency, bool, error) {
if h.userHostClient == nil {
return nil, false, status.Error(codes.Unavailable, "user host client unavailable")
}
profileResp, err := h.userHostClient.GetHostProfile(request.Context(), &userv1.GetHostProfileRequest{
Meta: httpkit.UserMeta(request, ""),
UserId: targetUserID,
})
if status.Code(err) == codes.NotFound {
return nil, false, nil
}
if err != nil {
return nil, false, err
}
profile := profileResp.GetHostProfile()
// 这两个公开查询看的是“被查询用户作为 active host 当前所属 agency”。非 host、停用 host、
// 或没有 active agency 归属都按空结果处理,避免向客户端暴露中间态/历史关系。
if profile == nil || profile.GetStatus() != agencyStatusActive || profile.GetCurrentAgencyId() <= 0 {
return nil, false, nil
}
agencyResp, err := h.userHostClient.GetAgency(request.Context(), &userv1.GetAgencyRequest{
Meta: httpkit.UserMeta(request, ""),
AgencyId: profile.GetCurrentAgencyId(),
})
if status.Code(err) == codes.NotFound {
return nil, false, nil
}
if err != nil {
return nil, false, err
}
agency := agencyResp.GetAgency()
if agency == nil || agency.GetAgencyId() != profile.GetCurrentAgencyId() || agency.GetStatus() != agencyStatusActive {
return nil, false, nil
}
return agency, true, nil
}
func (h *Handler) activeAgencyHostUserIDs(request *http.Request, agency *userv1.Agency) ([]int64, error) {
resp, err := h.userHostClient.GetAgencyMembers(request.Context(), &userv1.GetAgencyMembersRequest{
Meta: httpkit.UserMeta(request, ""),
AgencyId: agency.GetAgencyId(),
Status: membershipStatusActive,
})
if err != nil {
return nil, err
}
seen := make(map[int64]struct{}, len(resp.GetMemberships())+1)
ownerUserIDs := make([]int64, 0, len(resp.GetMemberships())+1)
appendOwner := func(userID int64) {
if userID <= 0 {
return
}
if _, exists := seen[userID]; exists {
return
}
seen[userID] = struct{}{}
ownerUserIDs = append(ownerUserIDs, userID)
}
// Agency owner 自动拥有 owner membership即使历史数据缺 owner membership也要把 owner 的 active 房间纳入组织房间列表。
appendOwner(agency.GetOwnerUserId())
for _, membership := range resp.GetMemberships() {
if membership.GetStatus() == membershipStatusActive {
appendOwner(membership.GetHostUserId())
}
}
return ownerUserIDs, nil
}
func agencyRoomItemsFromProto(rooms []*roomv1.RoomListItem) []agencyRoomItemData {
items := make([]agencyRoomItemData, 0, len(rooms))
for _, room := range rooms {
items = append(items, agencyRoomItemFromProto(room))
}
return items
}
func agencyRoomItemFromProto(room *roomv1.RoomListItem) agencyRoomItemData {
if room == nil {
return agencyRoomItemData{}
}
roomID := room.GetRoomId()
return agencyRoomItemData{
RoomID: roomID,
IMGroupID: agencyRoomIMGroupID(roomID),
OwnerUserID: int64String(room.GetOwnerUserId()),
Title: room.GetTitle(),
CoverURL: room.GetCoverUrl(),
Mode: room.GetMode(),
Status: room.GetStatus(),
Locked: room.GetLocked(),
Heat: room.GetHeat(),
OnlineCount: room.GetOnlineCount(),
SeatCount: room.GetSeatCount(),
OccupiedSeatCount: room.GetOccupiedSeatCount(),
VisibleRegionID: room.GetVisibleRegionId(),
AppCode: room.GetAppCode(),
RoomShortID: room.GetRoomShortId(),
CountryCode: room.GetCountryCode(),
CountryFlag: agencyRoomCountryFlag(room.GetCountryCode()),
}
}
func positiveInt64Query(request *http.Request, name string) (int64, bool) {
raw := strings.TrimSpace(request.URL.Query().Get(name))
value, err := strconv.ParseInt(raw, 10, 64)
return value, err == nil && value > 0
}
func parseAgencyRoomListLimit(raw string) (int32, bool) {
raw = strings.TrimSpace(raw)
if raw == "" {
return 0, true
}
value, err := strconv.ParseInt(raw, 10, 32)
if err != nil || value <= 0 {
return 0, false
}
if value > maxAgencyRoomListLimit {
value = maxAgencyRoomListLimit
}
return int32(value), true
}
func parseAgencyRoomListTab(raw string) (string, bool) {
switch strings.ToLower(strings.TrimSpace(raw)) {
case "", "hot":
return "hot", true
case "new":
return "new", true
default:
return "", false
}
}
func agencyRoomListQuery(request *http.Request) string {
if query := strings.TrimSpace(request.URL.Query().Get("query")); query != "" {
return query
}
return strings.TrimSpace(request.URL.Query().Get("q"))
}
func trimmedOptionalString(value *string) *string {
if value == nil {
return nil
}
trimmed := strings.TrimSpace(*value)
return &trimmed
}
func optionalStringIsEmpty(value *string) bool {
return value != nil && *value == ""
}
func agencyRoomIMGroupID(roomID string) string {
groupID, err := imgroup.RoomGroupID(roomID)
if err != nil {
return roomID
}
return groupID
}
func agencyRoomCountryFlag(country string) string {
country = strings.ToUpper(strings.TrimSpace(country))
if len(country) != 2 {
return ""
}
runes := []rune(country)
for _, value := range runes {
if value < 'A' || value > 'Z' {
return ""
}
}
const regionalIndicatorA = 0x1F1E6
return string([]rune{
regionalIndicatorA + (runes[0] - 'A'),
regionalIndicatorA + (runes[1] - 'A'),
})
}

View File

@ -17,6 +17,7 @@ type Handler struct {
userCPClient client.UserCPClient
userCountryClient client.UserCountryQueryClient
userHostClient client.UserHostClient
roomQueryClient client.RoomQueryClient
walletClient client.WalletClient
growthLevelClient client.GrowthLevelClient
achievementClient client.AchievementClient
@ -31,6 +32,7 @@ type Config struct {
UserCPClient client.UserCPClient
UserCountryClient client.UserCountryQueryClient
UserHostClient client.UserHostClient
RoomQueryClient client.RoomQueryClient
WalletClient client.WalletClient
GrowthLevelClient client.GrowthLevelClient
AchievementClient client.AchievementClient
@ -46,6 +48,7 @@ func New(config Config) *Handler {
userCPClient: config.UserCPClient,
userCountryClient: config.UserCountryClient,
userHostClient: config.UserHostClient,
roomQueryClient: config.RoomQueryClient,
walletClient: config.WalletClient,
growthLevelClient: config.GrowthLevelClient,
achievementClient: config.AchievementClient,
@ -66,6 +69,9 @@ func (h *Handler) UserHandlers() httproutes.UserHandlers {
GetMyRoleSummary: h.getMyRoleSummary,
SearchHostAgencies: h.searchHostAgencies,
ApplyToHostAgency: h.applyToHostAgency,
GetAgencyProfile: h.getAgencyProfile,
ListAgencyRooms: h.listAgencyRooms,
UpdateAgencyProfile: h.updateAgencyProfile,
GetHostCenterAgency: h.getHostCenterAgency,
GetHostCenterPlatformPolicy: h.getHostCenterPlatformPolicy,
GetAgencyCenterOverview: h.getAgencyCenterOverview,

View File

@ -59,9 +59,6 @@ func (h *Handler) getHostCenterAgency(writer http.ResponseWriter, request *http.
if owner.DisplayUserID != "" {
data.ShortID = owner.DisplayUserID
}
if owner.Avatar != "" {
data.Avatar = owner.Avatar
}
}
}

View File

@ -9,6 +9,7 @@ import (
"hyapp/pkg/appcode"
"hyapp/pkg/logx"
"hyapp/pkg/xerr"
"hyapp/services/room-service/internal/room/command"
"hyapp/services/room-service/internal/room/state"
)
@ -109,6 +110,49 @@ func (s *Service) projectRoomPresenceBestEffort(ctx context.Context, snapshot *r
}
}
// projectRoomPresenceForCommandBestEffort 按命令类型选择最小投影Join/Leave 热路径不能再重写整房间在线集合。
func (s *Service) projectRoomPresenceForCommandBestEffort(ctx context.Context, cmd command.Command, snapshot *roomv1.RoomSnapshot, joinedUser *roomv1.RoomUser) {
switch cmd.Type() {
case (command.JoinRoom{}).Type():
s.upsertJoinedRoomPresenceBestEffort(ctx, snapshot, cmd.ActorUserID(), joinedUser)
case (command.LeaveRoom{}).Type():
s.markLeftRoomPresenceBestEffort(ctx, snapshot, cmd.ActorUserID())
default:
// Kick/Close/SystemEvict/Mic 变更仍先保留全量投影,确保复杂状态收敛不因本次 Join/Leave 优化改变语义。
s.projectRoomPresenceBestEffort(ctx, snapshot)
}
}
func (s *Service) upsertJoinedRoomPresenceBestEffort(ctx context.Context, snapshot *roomv1.RoomSnapshot, userID int64, joinedUser *roomv1.RoomUser) {
projection := roomPresenceProjectionForUser(snapshot, userID, joinedUser, s.clock.Now().UnixMilli())
if projection.RoomID == "" || projection.UserID <= 0 {
return
}
if err := s.repository.UpsertRoomUserPresence(ctx, projection); err != nil {
logx.Error(ctx, "room_presence_join_project_failed", err,
slog.String("component", "room_presence_projector"),
slog.String("room_id", projection.RoomID),
slog.Int64("user_id", projection.UserID),
slog.Int64("room_version", projection.RoomVersion),
)
}
}
func (s *Service) markLeftRoomPresenceBestEffort(ctx context.Context, snapshot *roomv1.RoomSnapshot, userID int64) {
projection := roomPresenceLeaveProjection(snapshot, userID, s.clock.Now().UnixMilli())
if projection.RoomID == "" || projection.UserID <= 0 {
return
}
if err := s.repository.MarkRoomUserPresenceLeft(ctx, projection); err != nil {
logx.Error(ctx, "room_presence_leave_project_failed", err,
slog.String("component", "room_presence_projector"),
slog.String("room_id", projection.RoomID),
slog.Int64("user_id", projection.UserID),
slog.Int64("room_version", projection.RoomVersion),
)
}
}
// roomPresenceProjectionFromSnapshot 把 Room Cell 快照压缩成用户当前房间读模型。
// 读模型只保存恢复入口需要的字段,不复制完整 RoomState也不作为权限权威来源。
func roomPresenceProjectionFromSnapshot(snapshot *roomv1.RoomSnapshot, updatedAtMS int64) RoomPresenceSnapshot {
@ -161,6 +205,53 @@ func roomPresenceProjectionFromSnapshot(snapshot *roomv1.RoomSnapshot, updatedAt
}
}
func roomPresenceProjectionForUser(snapshot *roomv1.RoomSnapshot, userID int64, user *roomv1.RoomUser, updatedAtMS int64) RoomPresence {
if snapshot == nil || snapshot.GetRoomId() == "" || snapshot.GetStatus() != state.RoomStatusActive || userID <= 0 {
return RoomPresence{}
}
if user == nil || user.GetUserId() != userID {
user = findProtoUser(snapshot, userID)
}
if user == nil || user.GetUserId() <= 0 {
return RoomPresence{}
}
entry := RoomPresence{
AppCode: snapshot.GetAppCode(),
UserID: user.GetUserId(),
RoomID: snapshot.GetRoomId(),
Role: user.GetRole(),
RoomVersion: snapshot.GetVersion(),
Status: roomPresenceStatusActive,
JoinedAtMS: user.GetJoinedAtMs(),
LastSeenAtMS: user.GetLastSeenAtMs(),
UpdatedAtMS: updatedAtMS,
}
for _, seat := range snapshot.GetMicSeats() {
if seat.GetUserId() != user.GetUserId() {
continue
}
// JoinRoom 可能是重连到已在麦用户;单用户投影仍要保留麦上发流状态。
entry.PublishState = seat.GetPublishState()
entry.MicSessionID = seat.GetMicSessionId()
break
}
return entry
}
func roomPresenceLeaveProjection(snapshot *roomv1.RoomSnapshot, userID int64, updatedAtMS int64) RoomPresence {
if snapshot == nil || snapshot.GetRoomId() == "" || userID <= 0 {
return RoomPresence{}
}
return RoomPresence{
AppCode: snapshot.GetAppCode(),
UserID: userID,
RoomID: snapshot.GetRoomId(),
RoomVersion: snapshot.GetVersion(),
Status: roomPresenceStatusLeft,
UpdatedAtMS: updatedAtMS,
}
}
// containsUserID 是当前房间恢复入口的本地集合查询,独立保留便于后续替换为二分查找。
func containsUserID(values []int64, target int64) bool {
return slices.Contains(values, target)

View File

@ -24,6 +24,7 @@ const (
defaultRoomListLimit = 20
maxRoomListLimit = 50
maxRoomListQueryRunes = 64
maxRoomOwnerIDFilters = 500
roomHotHeatWeight = 650000
roomHotOnlineWeight = 300000
roomHotMicWeight = 50000
@ -102,6 +103,45 @@ func (s *Service) ListRooms(ctx context.Context, req *roomv1.ListRoomsRequest) (
return roomListResponseFromEntries(tab, query, countryCode, viewerCountryCode, allVisibleRegions, entries, limit), nil
}
func (s *Service) ListRoomsByOwners(ctx context.Context, req *roomv1.ListRoomsByOwnersRequest) (*roomv1.ListRoomsResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
if req.GetViewerUserId() <= 0 {
// owner 集合必须由 gateway 在鉴权后根据业务归属注入room-service 不接受匿名组织页查询。
return nil, xerr.New(xerr.InvalidArgument, "viewer_user_id is required")
}
tab := normalizeRoomListTab(req.GetTab())
if tab == "" {
return nil, xerr.New(xerr.InvalidArgument, "tab is invalid")
}
ownerUserIDs := normalizeRoomOwnerFilters(req.GetOwnerUserIds())
if len(ownerUserIDs) == 0 {
return roomListResponseFromEntries(tab, "", "", "", false, nil, normalizeRoomListLimit(req.GetLimit())), nil
}
limit := normalizeRoomListLimit(req.GetLimit())
query, err := normalizeRoomListQuery(req.GetQuery())
if err != nil {
return nil, err
}
cursor, err := decodeRoomListCursor(tab, query, "", "", false, req.GetCursor())
if err != nil {
return nil, err
}
entries, err := s.repository.ListRoomListEntries(ctx, RoomListQuery{
AppCode: appcode.FromContext(ctx),
Tab: tab,
OwnerUserIDs: ownerUserIDs,
Query: query,
Limit: limit + 1,
CursorSortScore: cursor.SortScore,
CursorCreatedAtMS: cursor.CreatedAtMS,
CursorRoomID: cursor.RoomID,
})
if err != nil {
return nil, err
}
return roomListResponseFromEntries(tab, query, "", "", false, entries, limit), nil
}
func (s *Service) listRoomListEntries(ctx context.Context, query RoomListQuery) ([]RoomListEntry, error) {
if s != nil && s.roomListCache != nil && roomListCacheEligible(query) {
entries, ok, err := s.roomListCache.ListRoomListEntries(ctx, query)
@ -118,7 +158,7 @@ func (s *Service) listRoomListEntries(ctx context.Context, query RoomListQuery)
func roomListCacheEligible(query RoomListQuery) bool {
// 搜索、全区白名单和内部定向查询仍走 MySQL避免为了低频条件维护过宽的 Redis 索引。
if strings.TrimSpace(query.Query) != "" || query.AllVisibleRegions || query.OwnerUserID > 0 {
if strings.TrimSpace(query.Query) != "" || query.AllVisibleRegions || query.OwnerUserID > 0 || len(query.OwnerUserIDs) > 0 {
return false
}
return query.Tab == roomListTabHot || query.Tab == roomListTabNew
@ -439,6 +479,25 @@ func normalizeRoomListLimit(limit int32) int {
return int(limit)
}
func normalizeRoomOwnerFilters(ownerUserIDs []int64) []int64 {
seen := make(map[int64]struct{}, len(ownerUserIDs))
ids := make([]int64, 0, len(ownerUserIDs))
for _, userID := range ownerUserIDs {
if userID <= 0 {
continue
}
if _, exists := seen[userID]; exists {
continue
}
seen[userID] = struct{}{}
ids = append(ids, userID)
if len(ids) >= maxRoomOwnerIDFilters {
break
}
}
return ids
}
// normalizeVisibleRegionID 把负数区域收敛到 GLOBAL 桶,避免非法区域影响 SQL 查询边界。
func normalizeVisibleRegionID(regionID int64) int64 {
if regionID < 0 {

View File

@ -84,6 +84,48 @@ func TestListRoomsFallsBackToMySQLWhenCacheMisses(t *testing.T) {
}
}
func TestListRoomsByOwnersReturnsActiveRoomsForAgencyHosts(t *testing.T) {
ctx := context.Background()
repository := mysqltest.NewRepository(t)
svc := roomservice.New(roomservice.Config{
NodeID: "node-list-by-owners-test",
LeaseTTL: 10 * time.Second,
RankLimit: 20,
SnapshotEveryN: 1,
}, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())
createPinnedListRoom(t, ctx, svc, "room-agency-owner", "agency-owner", 6201, 7101, "US")
createPinnedListRoom(t, ctx, svc, "room-agency-member", "agency-member", 6202, 7202, "AE")
createPinnedListRoom(t, ctx, svc, "room-outside-agency", "outside-agency", 6203, 7101, "US")
createPinnedListRoom(t, ctx, svc, "room-agency-closed", "agency-closed", 6202, 7202, "AE")
if _, err := svc.CloseRoom(ctx, &roomv1.CloseRoomRequest{
Meta: roomservice.NewRequestMeta("room-agency-closed", 6202),
Reason: "closed fixture",
}); err != nil {
t.Fatalf("close agency room fixture failed: %v", err)
}
resp, err := svc.ListRoomsByOwners(ctx, &roomv1.ListRoomsByOwnersRequest{
Meta: &roomv1.RequestMeta{AppCode: appcode.Default},
ViewerUserId: 4001,
OwnerUserIds: []int64{6201, 6202},
Tab: "new",
Limit: 10,
})
if err != nil {
t.Fatalf("list rooms by owners failed: %v", err)
}
got := roomIDs(resp.GetRooms())
want := map[string]bool{"room-agency-owner": true, "room-agency-member": true}
if len(got) != len(want) {
t.Fatalf("owner-filtered room count mismatch: got=%v", got)
}
for _, roomID := range got {
if !want[roomID] {
t.Fatalf("owner-filtered rooms must include only active agency host rooms, got=%v", got)
}
}
}
type fakeRoomListCache struct {
entries []roomservice.RoomListEntry
ok bool

View File

@ -94,6 +94,95 @@ func TestListRoomOnlineUsersReturnsOwnerAdminRoleAndGiftValue(t *testing.T) {
t.Logf("online users response items: %s", payload)
}
func TestJoinLeaveRoomUpdatesPresenceIncrementally(t *testing.T) {
ctx := context.Background()
repository := mysqltest.NewRepository(t)
now := &fixedRoomRocketClock{now: time.Date(2026, 5, 27, 13, 0, 0, 0, time.UTC)}
svc := newRocketTestService(t, repository, &rocketTestWallet{}, now)
roomID := "room-presence-incremental"
ownerID := int64(1101)
viewerID := int64(1102)
createRocketRoom(t, ctx, svc, roomID, ownerID, 9001)
joinRocketRoom(t, ctx, svc, roomID, viewerID)
onlineResp, err := svc.ListRoomOnlineUsers(ctx, &roomv1.ListRoomOnlineUsersRequest{
Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID},
RoomId: roomID,
ViewerUserId: ownerID,
Page: 1,
PageSize: 10,
})
if err != nil {
t.Fatalf("list online users after join failed: %v", err)
}
if item := onlineUserItemByID(onlineResp.GetItems(), viewerID); item == nil || item.GetRole() != "audience" {
t.Fatalf("join must upsert viewer active presence: %+v", onlineResp.GetItems())
}
if _, err := svc.LeaveRoom(ctx, &roomv1.LeaveRoomRequest{Meta: rocketMeta(roomID, viewerID, "presence-incremental-leave")}); err != nil {
t.Fatalf("leave room failed: %v", err)
}
onlineResp, err = svc.ListRoomOnlineUsers(ctx, &roomv1.ListRoomOnlineUsersRequest{
Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID},
RoomId: roomID,
ViewerUserId: ownerID,
Page: 1,
PageSize: 10,
})
if err != nil {
t.Fatalf("list online users after leave failed: %v", err)
}
if item := onlineUserItemByID(onlineResp.GetItems(), viewerID); item != nil {
t.Fatalf("leave must mark only viewer presence left: %+v", onlineResp.GetItems())
}
}
func TestMarkRoomUserPresenceLeftDoesNotClearAnotherRoom(t *testing.T) {
ctx := appcode.WithContext(context.Background(), appcode.Default)
repository := mysqltest.NewRepository(t)
userID := int64(1201)
if err := repository.UpsertRoomUserPresence(ctx, roomservice.RoomPresence{
AppCode: appcode.Default,
UserID: userID,
RoomID: "room-current-active",
Role: "audience",
RoomVersion: 2,
JoinedAtMS: 1000,
LastSeenAtMS: 1000,
UpdatedAtMS: 1000,
}); err != nil {
t.Fatalf("upsert current room presence failed: %v", err)
}
if err := repository.MarkRoomUserPresenceLeft(ctx, roomservice.RoomPresence{
AppCode: appcode.Default,
UserID: userID,
RoomID: "room-old-left",
RoomVersion: 3,
UpdatedAtMS: 2000,
}); err != nil {
t.Fatalf("mark old room presence left failed: %v", err)
}
presence, exists, err := repository.GetCurrentRoomPresence(ctx, userID)
if err != nil {
t.Fatalf("get current room presence failed: %v", err)
}
if !exists || presence.RoomID != "room-current-active" || presence.Status != "active" {
t.Fatalf("old room leave projection must not clear newer room presence: exists=%v presence=%+v", exists, presence)
}
}
func onlineUserItemByID(items []*roomv1.RoomOnlineUser, userID int64) *roomv1.RoomOnlineUser {
for _, item := range items {
if item.GetUserId() == userID {
return item
}
}
return nil
}
func TestSendGiftPassesHostPeriodScopeToWallet(t *testing.T) {
ctx := context.Background()
repository := mysqltest.NewRepository(t)

View File

@ -178,7 +178,7 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl
// 列表读模型最终一致,失败只记录日志,不回滚已经提交的 Room Cell 命令。
s.projectRoomListBestEffort(ctx, result.snapshot)
// 当前房间恢复读模型同样最终一致;权威校验仍以 Room Cell 快照为准。
s.projectRoomPresenceBestEffort(ctx, result.snapshot)
s.projectRoomPresenceForCommandBestEffort(ctx, cmd, result.snapshot, result.user)
// 跨房间贡献榜是 Redis 轻量读模型,不能影响已提交的房间状态和账务事实。
s.recordRoomGiftLeaderboardBestEffort(ctx, result.roomGiftLeaderboard)
projectionMS = elapsedMS(projectionStartedAt)

View File

@ -19,6 +19,8 @@ import (
"hyapp/services/room-service/internal/room/state"
)
const joinRoomResponseModeLite = "lite"
// JoinRoom 把用户业务态接入房间。
func (s *Service) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*roomv1.JoinRoomResponse, error) {
ctx = contextFromMeta(ctx, req.GetMeta())
@ -133,10 +135,38 @@ func (s *Service) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*r
return &roomv1.JoinRoomResponse{
Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
User: result.user,
Room: result.snapshot,
Room: joinRoomResponseSnapshot(result.snapshot, req.GetResponseMode()),
}, nil
}
func joinRoomResponseSnapshot(snapshot *roomv1.RoomSnapshot, responseMode string) *roomv1.RoomSnapshot {
if strings.ToLower(strings.TrimSpace(responseMode)) != joinRoomResponseModeLite || snapshot == nil {
return snapshot
}
onlineCount := snapshot.GetOnlineCount()
if onlineCount <= 0 && len(snapshot.GetOnlineUsers()) > 0 {
onlineCount = int32(len(snapshot.GetOnlineUsers()))
}
// lite 只裁剪返回给 gateway 的快照Room Cell 提交、持久化、projection、outbox 都已经使用完整 snapshot。
return &roomv1.RoomSnapshot{
RoomId: snapshot.GetRoomId(),
OwnerUserId: snapshot.GetOwnerUserId(),
Mode: snapshot.GetMode(),
Status: snapshot.GetStatus(),
ChatEnabled: snapshot.GetChatEnabled(),
MicSeats: snapshot.GetMicSeats(),
RoomExt: snapshot.GetRoomExt(),
Heat: snapshot.GetHeat(),
Version: snapshot.GetVersion(),
AppCode: snapshot.GetAppCode(),
RoomShortId: snapshot.GetRoomShortId(),
VisibleRegionId: snapshot.GetVisibleRegionId(),
Locked: snapshot.GetLocked(),
Rocket: snapshot.GetRocket(),
OnlineCount: onlineCount,
}
}
func userDisplayProfileFromProto(item *roomv1.RoomUserDisplayProfile) command.UserDisplayProfile {
if item == nil || item.GetUserId() <= 0 {
return command.UserDisplayProfile{}

View File

@ -497,6 +497,8 @@ type RoomListQuery struct {
Tab string
// OwnerUserID 保留给内部定向读模型;公开 Mine 顶部卡片走 GetMyRoom。
OwnerUserID int64
// OwnerUserIDs 是业务方预先解析出的房主集合,用于 Agency 等组织页批量取房间卡片。
OwnerUserIDs []int64
// Query 非空时按 room_id 或 title 做包含匹配。
Query string
// CountryCode 非空时只返回房主国家命中的房间gateway 必须先校验它属于当前用户区域。
@ -782,6 +784,10 @@ type RoomListStore interface {
type PresenceStore interface {
// ProjectRoomPresence 用最新快照刷新用户当前房间读模型;失败不应破坏 Room Cell 已提交状态。
ProjectRoomPresence(ctx context.Context, snapshot RoomPresenceSnapshot) error
// UpsertRoomUserPresence 只刷新单个用户 active presence服务 JoinRoom/Heartbeat 等单用户热路径。
UpsertRoomUserPresence(ctx context.Context, presence RoomPresence) error
// MarkRoomUserPresenceLeft 只把指定用户在指定房间标记为 left避免 LeaveRoom 重新写整房间在线用户。
MarkRoomUserPresenceLeft(ctx context.Context, presence RoomPresence) error
// GetCurrentRoomPresence 查询用户当前 active 房间 presence不扫描房间列表或快照。
GetCurrentRoomPresence(ctx context.Context, userID int64) (RoomPresence, bool, error)
// ListRoomOnlineUsers 查询房间 active 在线用户分页,不读取完整 RoomSnapshot。

View File

@ -151,6 +151,42 @@ func TestRepeatJoinRoomPublishesEntryBroadcastDirectIMOnly(t *testing.T) {
}
}
func TestJoinRoomLiteTrimsResponseSnapshotOnly(t *testing.T) {
ctx := context.Background()
repository := mysqltest.NewRepository(t)
now := &fixedRoomRocketClock{now: time.Date(2026, 6, 27, 12, 0, 0, 0, time.UTC)}
svc := newRocketTestServiceWithDirectIM(t, repository, &rocketTestWallet{}, now, nil, newRecordingRoomDirectIMPublisher())
roomID := "room-lite-response-snapshot"
createRocketRoom(t, ctx, svc, roomID, 1001, 9001)
joinRocketRoom(t, ctx, svc, roomID, 1002)
joinRocketRoom(t, ctx, svc, roomID, 1003)
resp, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{
Meta: roomservice.NewRequestMeta(roomID, 1004),
Role: "audience",
ResponseMode: "lite",
})
if err != nil {
t.Fatalf("lite join room failed: %v", err)
}
if len(resp.GetRoom().GetOnlineUsers()) != 0 || len(resp.GetRoom().GetGiftRank()) != 0 || len(resp.GetRoom().GetAdminUserIds()) != 0 {
t.Fatalf("lite response must trim heavy room arrays: %+v", resp.GetRoom())
}
snapshot, err := svc.GetRoomSnapshot(ctx, &roomv1.GetRoomSnapshotRequest{
Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID},
RoomId: roomID,
ViewerUserId: 1004,
})
if err != nil {
t.Fatalf("snapshot after lite join failed: %v", err)
}
if got, want := resp.GetRoom().GetOnlineCount(), int32(len(snapshot.GetRoom().GetOnlineUsers())); got != want || want == 0 {
t.Fatalf("lite response must keep accurate online_count without mutating stored snapshot: got=%d want=%d snapshot=%+v", got, want, snapshot.GetRoom())
}
}
func (w *rocketTestWallet) DebitGift(_ context.Context, req *walletv1.DebitGiftRequest) (*walletv1.DebitGiftResponse, error) {
w.lastDebit = req
w.debitRequests = append(w.debitRequests, req)

View File

@ -465,6 +465,7 @@ func (s *RoomState) ToProto() *roomv1.RoomSnapshot {
Locked: s.RoomPasswordHash != "",
Rocket: rocketStateToProto(s.Rocket),
BanStates: sortedModerationStates(s.BanUsers),
OnlineCount: int32(len(users)),
}
}

View File

@ -77,6 +77,62 @@ func (r *Repository) ProjectRoomPresence(ctx context.Context, snapshot roomservi
return tx.Commit()
}
// UpsertRoomUserPresence 只写入单个 active 用户,避免 JoinRoom 为整房间在线用户产生 O(N) 写放大。
func (r *Repository) UpsertRoomUserPresence(ctx context.Context, presence roomservice.RoomPresence) error {
appCode := normalizedRecordAppCode(ctx, presence.AppCode)
if presence.UserID <= 0 || strings.TrimSpace(presence.RoomID) == "" {
return nil
}
_, err := r.db.ExecContext(ctx,
`INSERT INTO room_user_presence (
app_code, user_id, room_id, role, publish_state, mic_session_id, room_version, status, joined_at_ms, last_seen_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
room_id = VALUES(room_id),
role = VALUES(role),
publish_state = VALUES(publish_state),
mic_session_id = VALUES(mic_session_id),
room_version = VALUES(room_version),
status = VALUES(status),
joined_at_ms = VALUES(joined_at_ms),
last_seen_at_ms = VALUES(last_seen_at_ms),
updated_at_ms = VALUES(updated_at_ms)`,
appCode,
presence.UserID,
presence.RoomID,
presence.Role,
presence.PublishState,
presence.MicSessionID,
presence.RoomVersion,
roomPresenceStatusActiveSQL,
presence.JoinedAtMS,
presence.LastSeenAtMS,
presence.UpdatedAtMS,
)
return err
}
// MarkRoomUserPresenceLeft 只关闭指定用户在指定房间的 active presence避免旧离房投影误伤新房间。
func (r *Repository) MarkRoomUserPresenceLeft(ctx context.Context, presence roomservice.RoomPresence) error {
appCode := normalizedRecordAppCode(ctx, presence.AppCode)
if presence.UserID <= 0 || strings.TrimSpace(presence.RoomID) == "" {
return nil
}
_, err := r.db.ExecContext(ctx,
`UPDATE room_user_presence
SET status = ?, publish_state = '', mic_session_id = '', room_version = ?, updated_at_ms = ?
WHERE app_code = ? AND user_id = ? AND room_id = ? AND status = ?`,
roomPresenceStatusLeftSQL,
presence.RoomVersion,
presence.UpdatedAtMS,
appCode,
presence.UserID,
presence.RoomID,
roomPresenceStatusActiveSQL,
)
return err
}
// GetCurrentRoomPresence 查询用户当前 active 房间,不扫描发现页列表。
func (r *Repository) GetCurrentRoomPresence(ctx context.Context, userID int64) (roomservice.RoomPresence, bool, error) {
row := r.db.QueryRowContext(ctx,

View File

@ -572,6 +572,9 @@ const roomFollowSelectColumns = `
JOIN room_list_entries r ON r.app_code = f.app_code AND r.room_id = f.room_id`
func buildRoomListQuerySQL(query roomservice.RoomListQuery) (string, []any) {
if len(query.OwnerUserIDs) > 0 {
return buildRoomListByOwnersQuerySQL(query)
}
regionID := query.VisibleRegionID
if regionID < 0 {
regionID = 0
@ -674,6 +677,40 @@ func buildRoomListQuerySQL(query roomservice.RoomListQuery) (string, []any) {
return selectSQL + "\n\tWHERE " + strings.Join(where, " AND ") + "\n\tORDER BY " + pinRankExpr + " ASC, " + pinWeightExpr + " DESC, " + pinExpiresExpr + " DESC, r.sort_score DESC, r.room_id ASC\n\tLIMIT ?", args
}
func buildRoomListByOwnersQuerySQL(query roomservice.RoomListQuery) (string, []any) {
where := []string{"app_code = ?", "status = ?"}
args := []any{appcode.Normalize(query.AppCode), "active"}
placeholders := make([]string, 0, len(query.OwnerUserIDs))
for _, ownerUserID := range query.OwnerUserIDs {
placeholders = append(placeholders, "?")
args = append(args, ownerUserID)
}
where = append(where, "owner_user_id IN ("+strings.Join(placeholders, ",")+")")
if strings.TrimSpace(query.Query) != "" {
where = append(where, "(room_id LIKE ? ESCAPE '\\\\' OR room_short_id LIKE ? ESCAPE '\\\\' OR title LIKE ? ESCAPE '\\\\')")
like := "%" + escapeRoomListLike(query.Query) + "%"
args = append(args, like, like, like)
}
if query.Tab == "new" {
if query.CursorRoomID != "" {
where = append(where, "(created_at_ms < ? OR (created_at_ms = ? AND room_id > ?))")
args = append(args, query.CursorCreatedAtMS, query.CursorCreatedAtMS, query.CursorRoomID)
}
args = append(args, query.Limit)
return roomListSelectColumns + "\n\tWHERE " + strings.Join(where, " AND ") + "\n\tORDER BY created_at_ms DESC, room_id ASC\n\tLIMIT ?", args
}
if query.CursorRoomID != "" {
where = append(where, "(sort_score < ? OR (sort_score = ? AND room_id > ?))")
args = append(args, query.CursorSortScore, query.CursorSortScore, query.CursorRoomID)
}
args = append(args, query.Limit)
return roomListSelectColumns + "\n\tWHERE " + strings.Join(where, " AND ") + "\n\tORDER BY sort_score DESC, room_id ASC\n\tLIMIT ?", args
}
func normalizeRoomListSQLCountryCode(countryCode string) string {
countryCode = strings.ToUpper(strings.TrimSpace(countryCode))
if len(countryCode) < 2 || len(countryCode) > 3 {

View File

@ -485,6 +485,13 @@ func (s *Server) ListRooms(ctx context.Context, req *roomv1.ListRoomsRequest) (*
return mapServiceResult(s.svc.ListRooms(ctx, req))
}
// ListRoomsByOwners 代理到 owner 批量房间卡片读模型。
func (s *Server) ListRoomsByOwners(ctx context.Context, req *roomv1.ListRoomsByOwnersRequest) (*roomv1.ListRoomsResponse, error) {
// owner 集合由上游业务归属解析后注入room-service 只按房间 owner 投影批量读取卡片。
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.ListRoomsByOwners(ctx, req))
}
// ListRoomFeeds 代理到 Mine 页用户房间流读模型。
func (s *Server) ListRoomFeeds(ctx context.Context, req *roomv1.ListRoomFeedsRequest) (*roomv1.ListRoomsResponse, error) {
// feed 查询只读用户到房间索引,并 join 房间卡片投影,不扫描 Room Cell 内存集合。

View File

@ -811,6 +811,7 @@ CREATE TABLE IF NOT EXISTS agencies (
owner_user_id BIGINT NOT NULL COMMENT '房主用户 ID',
parent_bd_user_id BIGINT NOT NULL COMMENT '父级BD用户 ID',
name VARCHAR(128) NOT NULL COMMENT '名称',
avatar VARCHAR(512) NOT NULL DEFAULT '' COMMENT '公会头像',
status VARCHAR(32) NOT NULL COMMENT '业务状态',
join_enabled BOOLEAN NOT NULL COMMENT '加入是否启用',
max_hosts INT NOT NULL COMMENT '最大主播',

View File

@ -0,0 +1,8 @@
ALTER TABLE agencies
ADD COLUMN avatar VARCHAR(512) NOT NULL DEFAULT '' COMMENT '公会头像' AFTER name;
UPDATE agencies a
JOIN users u
ON u.app_code = a.app_code AND u.user_id = a.owner_user_id
SET a.avatar = COALESCE(u.avatar, '')
WHERE a.avatar = '';

View File

@ -128,6 +128,8 @@ const (
CommandTypeDeleteAgency = "admin_delete_agency"
// CommandTypeSetAgencyJoinEnabled 记录后台设置 Agency 入会开关命令。
CommandTypeSetAgencyJoinEnabled = "admin_set_agency_join_enabled"
// CommandTypeUpdateAgencyProfile 记录 Agency owner 修改独立资料的命令。
CommandTypeUpdateAgencyProfile = "update_agency_profile"
// CommandTypeAdminAddAgencyHost 记录后台直接把用户添加为 Agency Host 的命令。
CommandTypeAdminAddAgencyHost = "admin_add_agency_host"
// CommandTypeCreateCoinSeller 记录后台创建币商身份命令。
@ -171,7 +173,7 @@ type Agency struct {
RegionID int64
ParentBDUserID int64
Name string
OwnerAvatar string
Avatar string
ActiveHostCount int32
Status string
JoinEnabled bool

View File

@ -294,6 +294,18 @@ type User struct {
UpdatedAtMs int64
}
// RoomBasicUser 是房间首屏热路径使用的最小用户展示资料,不能混入国家、靓号详情或装扮字段。
type RoomBasicUser struct {
// UserID 是系统内部不可变用户 ID调用方用它和麦位/房主/当前用户状态关联。
UserID int64
// Username 是用户当前基础昵称;为空时客户端按 display_user_id 或 user_id 兜底。
Username string
// Avatar 是用户当前基础头像 URL装扮头像框不属于这个结构。
Avatar string
// DisplayUserID 是房间首屏展示短号;过期靓号在 SQL 内兜底为默认短号。
DisplayUserID string
}
// ProfileStats 是我的页顶部统计 read model只保存预聚合计数。
type ProfileStats struct {
// AppCode 是统计所属 App关系链和礼物统计都必须在 App 内隔离。

View File

@ -390,6 +390,28 @@ type SetAgencyJoinEnabledCommand struct {
NowMs int64
}
// UpdateAgencyProfileInput 是 Agency owner 修改独立组织资料的外部输入。
type UpdateAgencyProfileInput struct {
CommandID string
OperatorUserID int64
AgencyID int64
Name *string
Avatar *string
RequestID string
}
// UpdateAgencyProfileCommand 是修改 Agency 独立资料事务需要的完整命令。
type UpdateAgencyProfileCommand struct {
CommandID string
OperatorUserID int64
AgencyID int64
Name *string
Avatar *string
RequestID string
EventID int64
NowMs int64
}
// AdminAddAgencyHostInput 是后台直接添加 Agency Host 的外部输入。
type AdminAddAgencyHostInput struct {
CommandID string

View File

@ -45,6 +45,7 @@ type Repository interface {
ListActiveCoinSellersInMyRegion(ctx context.Context, userID int64) ([]hostdomain.CoinSellerListItem, error)
GetUserRoleSummary(ctx context.Context, userID int64) (hostdomain.UserRoleSummary, error)
GetAgency(ctx context.Context, agencyID int64) (hostdomain.Agency, error)
UpdateAgencyProfile(ctx context.Context, command UpdateAgencyProfileCommand) (hostdomain.Agency, error)
HasActiveAgencyOwner(ctx context.Context, userID int64) (int64, bool, error)
HasActiveManagerProfile(ctx context.Context, userID int64) (bool, error)
HasActiveManagerCapability(ctx context.Context, userID int64, capability string) (bool, error)
@ -208,15 +209,55 @@ func (s *Service) GetAgency(ctx context.Context, agencyID int64) (hostdomain.Age
return s.repository.GetAgency(ctx, agencyID)
}
// UpdateAgencyProfile 只修改 Agency 独立组织资料,不回写 owner 用户昵称或头像。
func (s *Service) UpdateAgencyProfile(ctx context.Context, command UpdateAgencyProfileInput) (hostdomain.Agency, error) {
if err := s.requireWriteDependencies(); err != nil {
return hostdomain.Agency{}, err
}
if strings.TrimSpace(command.CommandID) == "" || command.OperatorUserID <= 0 || command.AgencyID <= 0 {
return hostdomain.Agency{}, xerr.New(xerr.InvalidArgument, "command_id, operator_user_id and agency_id are required")
}
if command.Name == nil && command.Avatar == nil {
return hostdomain.Agency{}, xerr.New(xerr.InvalidArgument, "name or avatar is required")
}
var name *string
if command.Name != nil {
trimmed := strings.TrimSpace(*command.Name)
if trimmed == "" {
return hostdomain.Agency{}, xerr.New(xerr.InvalidArgument, "agency name is required")
}
name = &trimmed
}
var avatar *string
if command.Avatar != nil {
trimmed := strings.TrimSpace(*command.Avatar)
if trimmed == "" {
return hostdomain.Agency{}, xerr.New(xerr.InvalidArgument, "agency avatar is required")
}
avatar = &trimmed
}
return s.repository.UpdateAgencyProfile(ctx, UpdateAgencyProfileCommand{
CommandID: strings.TrimSpace(command.CommandID),
OperatorUserID: command.OperatorUserID,
AgencyID: command.AgencyID,
Name: name,
Avatar: avatar,
RequestID: strings.TrimSpace(command.RequestID),
EventID: s.nextEventIDRange(1),
NowMs: s.now().UnixMilli(),
})
}
// InviteAgency 由 BD 或 BD Leader 创建 Agency owner 邀请;目标用户接受前不创建 Agency 身份。
func (s *Service) InviteAgency(ctx context.Context, command InviteAgencyInput) (hostdomain.RoleInvitation, error) {
if err := s.requireWriteDependencies(); err != nil {
return hostdomain.RoleInvitation{}, err
}
// Agency 名称在邀请动作里固化,接受时按这份快照创建 Agency避免目标确认后前端再传入不同名称。
// Agency 名称允许邀请方显式覆盖;为空时在目标接受并真正创建 Agency 时复制 owner 当前昵称/短 ID
agencyName := strings.TrimSpace(command.AgencyName)
if strings.TrimSpace(command.CommandID) == "" || command.InviterUserID <= 0 || command.TargetUserID <= 0 || agencyName == "" {
return hostdomain.RoleInvitation{}, xerr.New(xerr.InvalidArgument, "command_id, inviter_user_id, target_user_id and agency_name are required")
if strings.TrimSpace(command.CommandID) == "" || command.InviterUserID <= 0 || command.TargetUserID <= 0 {
return hostdomain.RoleInvitation{}, xerr.New(xerr.InvalidArgument, "command_id, inviter_user_id and target_user_id are required")
}
nowMs := s.now().UnixMilli()
@ -501,8 +542,8 @@ func (s *Service) CreateAgency(ctx context.Context, command CreateAgencyInput) (
return hostdomain.CreateAgencyResult{}, err
}
name := strings.TrimSpace(command.Name)
if strings.TrimSpace(command.CommandID) == "" || command.AdminUserID <= 0 || command.OwnerUserID <= 0 || name == "" {
return hostdomain.CreateAgencyResult{}, xerr.New(xerr.InvalidArgument, "command_id, admin_user_id, owner_user_id and name are required")
if strings.TrimSpace(command.CommandID) == "" || command.AdminUserID <= 0 || command.OwnerUserID <= 0 {
return hostdomain.CreateAgencyResult{}, xerr.New(xerr.InvalidArgument, "command_id, admin_user_id and owner_user_id are required")
}
if command.ParentBDUserID < 0 {
return hostdomain.CreateAgencyResult{}, xerr.New(xerr.InvalidArgument, "parent_bd_user_id must not be negative")

View File

@ -1101,7 +1101,7 @@ func TestAdminCreateAgencyControlsAppSearch(t *testing.T) {
if err != nil {
t.Fatalf("SearchAgencies list failed: %v", err)
}
if len(agencies) != 1 || agencies[0].AgencyID != created.Agency.AgencyID || agencies[0].OwnerAvatar != "https://cdn.example/owner-901.png" || agencies[0].ActiveHostCount != 1 {
if len(agencies) != 1 || agencies[0].AgencyID != created.Agency.AgencyID || agencies[0].Avatar != "https://cdn.example/owner-901.png" || agencies[0].ActiveHostCount != 1 {
t.Fatalf("created agency list should be scoped by country with display fields: %+v", agencies)
}
if agencies[0].RegionID != 30 {
@ -1121,6 +1121,13 @@ func TestAdminCreateAgencyControlsAppSearch(t *testing.T) {
if len(agencies) != 1 || agencies[0].AgencyID != created.Agency.AgencyID {
t.Fatalf("created agency should be searchable by owner display id: %+v", agencies)
}
agencies, err = svc.SearchAgencies(ctx, 902, displayID(900), 20)
if err != nil {
t.Fatalf("SearchAgencies by non-owner parent BD display id failed: %v", err)
}
if len(agencies) != 0 {
t.Fatalf("parent BD display id must not expand to child agencies: %+v", agencies)
}
disabledJoin, err := svc.SetAgencyJoinEnabled(ctx, hostservice.SetAgencyJoinEnabledInput{
CommandID: "admin-disable-agency-join-901",
@ -1226,6 +1233,96 @@ func TestAdminCreateAgencyControlsAppSearch(t *testing.T) {
}
}
func TestAgencyProfileDefaultsAndStaysIndependent(t *testing.T) {
ctx := context.Background()
repository := mysqltest.NewRepository(t)
repository.PutUser(userdomain.User{
UserID: 921,
DefaultDisplayUserID: displayID(921),
CurrentDisplayUserID: displayID(921),
Username: "Owner Nick",
Avatar: "https://cdn.example/owner-old.png",
Country: "CN",
RegionID: 30,
ProfileCompleted: true,
ProfileCompletedAtMs: 1,
OnboardingStatus: userdomain.OnboardingStatusCompleted,
Status: userdomain.StatusActive,
})
seedActiveUser(t, repository, 922, 30)
svc := newHostService(repository, 5200, 6200)
created, err := svc.CreateAgency(ctx, hostservice.CreateAgencyInput{
CommandID: "admin-create-agency-default-profile",
AdminUserID: 1,
OwnerUserID: 921,
JoinEnabled: true,
RequestID: "req-agency-default-profile",
})
if err != nil {
t.Fatalf("CreateAgency with default profile failed: %v", err)
}
if created.Agency.Name != "Owner Nick" || created.Agency.Avatar != "https://cdn.example/owner-old.png" {
t.Fatalf("agency must copy owner profile once on create: %+v", created.Agency)
}
newName := "Agency New"
newAvatar := "https://cdn.example/agency-new.png"
updated, err := svc.UpdateAgencyProfile(ctx, hostservice.UpdateAgencyProfileInput{
CommandID: "agency-profile-update-owner",
OperatorUserID: 921,
AgencyID: created.Agency.AgencyID,
Name: &newName,
Avatar: &newAvatar,
RequestID: "req-agency-profile-update-owner",
})
if err != nil {
t.Fatalf("UpdateAgencyProfile by owner failed: %v", err)
}
if updated.Name != newName || updated.Avatar != newAvatar {
t.Fatalf("agency profile update mismatch: %+v", updated)
}
owner, err := repository.GetUser(ctx, 921)
if err != nil {
t.Fatalf("GetUser after agency update failed: %v", err)
}
if owner.Username != "Owner Nick" || owner.Avatar != "https://cdn.example/owner-old.png" {
t.Fatalf("agency profile update must not modify owner user: %+v", owner)
}
repository.PutUser(userdomain.User{
UserID: 921,
DefaultDisplayUserID: displayID(921),
CurrentDisplayUserID: displayID(921),
Username: "Owner New",
Avatar: "https://cdn.example/owner-new.png",
Country: "CN",
RegionID: 30,
ProfileCompleted: true,
ProfileCompletedAtMs: 1,
OnboardingStatus: userdomain.OnboardingStatusCompleted,
Status: userdomain.StatusActive,
})
afterUserChange, err := svc.GetAgency(ctx, created.Agency.AgencyID)
if err != nil {
t.Fatalf("GetAgency after owner profile change failed: %v", err)
}
if afterUserChange.Name != newName || afterUserChange.Avatar != newAvatar {
t.Fatalf("owner profile change must not sync back to agency: %+v", afterUserChange)
}
_, err = svc.UpdateAgencyProfile(ctx, hostservice.UpdateAgencyProfileInput{
CommandID: "agency-profile-update-non-owner",
OperatorUserID: 922,
AgencyID: created.Agency.AgencyID,
Name: &newName,
RequestID: "req-agency-profile-update-non-owner",
})
if got := xerr.CodeOf(err); got != xerr.PermissionDenied {
t.Fatalf("non owner agency update must be denied: got %s err=%v", got, err)
}
}
func TestAdminCreateAgencyWithoutParentBD(t *testing.T) {
ctx := context.Background()
repository := mysqltest.NewRepository(t)

View File

@ -216,6 +216,10 @@ func (r *fakeModerationRepository) BatchGetUsers(context.Context, []int64) (map[
return nil, nil
}
func (r *fakeModerationRepository) BatchGetRoomBasicUsers(context.Context, []int64) (map[int64]userdomain.RoomBasicUser, error) {
return nil, nil
}
func (r *fakeModerationRepository) FindInviteReferrerByCode(_ context.Context, appCode string, inviteCode string) (userdomain.User, error) {
r.lastFindAppCode = appCode
r.lastFindInviteCode = inviteCode

View File

@ -45,6 +45,8 @@ type UserRepository interface {
BusinessUserLookup(ctx context.Context, keyword string, numericKeyword bool, pageSize int32) ([]userdomain.BusinessUserLookupItem, error)
// BatchGetUsers 批量查询用户主状态,缺失用户不应返回占位对象。
BatchGetUsers(ctx context.Context, userIDs []int64) (map[int64]userdomain.User, error)
// BatchGetRoomBasicUsers 批量查询房间首屏最小用户资料,不触发完整用户主状态投影。
BatchGetRoomBasicUsers(ctx context.Context, userIDs []int64) (map[int64]userdomain.RoomBasicUser, error)
// GetInviteAttribution 按被邀请用户读取邀请归因快照;无关系时返回 Found=false。
GetInviteAttribution(ctx context.Context, invitedUserID int64) (invitedomain.Attribution, error)
// FindInviteReferrerByCode 按 active 邀请码读取归属用户,供 H5 绑定页搜索同区域邀请人。

View File

@ -94,6 +94,25 @@ func (s *Service) BatchGetUsers(ctx context.Context, userIDs []int64) (map[int64
return users, nil
}
// BatchGetRoomBasicUsers 批量查询房间首屏最小用户资料。
func (s *Service) BatchGetRoomBasicUsers(ctx context.Context, userIDs []int64) (map[int64]userdomain.RoomBasicUser, error) {
if len(userIDs) == 0 {
// 空批量请求返回空 mapgateway 可以直接输出空 profiles。
return map[int64]userdomain.RoomBasicUser{}, nil
}
for _, userID := range userIDs {
if userID <= 0 {
// room-service 只接受正 user_id这里继续对调用方输入做边界收敛。
return nil, xerr.New(xerr.InvalidArgument, "user_ids contains invalid value")
}
}
if s.userRepository == nil {
// 房间热路径也不能绕过 owner repository无存储时明确返回不可用。
return nil, xerr.New(xerr.Unavailable, "user repository is not configured")
}
return s.userRepository.BatchGetRoomBasicUsers(ctx, userIDs)
}
// ListUserIDs returns active user IDs for low-frequency backend fanout tasks.
func (s *Service) ListUserIDs(ctx context.Context, filter userdomain.UserIDPageFilter) ([]int64, int64, bool, error) {
if s.userRepository == nil {

View File

@ -315,8 +315,12 @@ func (r *Repository) CreateAgency(ctx context.Context, command hostservice.Creat
return hostdomain.CreateAgencyResult{}, err
}
agencyName, agencyAvatar, err := defaultAgencyProfileFromOwner(ctx, tx, command.OwnerUserID, command.Name)
if err != nil {
return hostdomain.CreateAgencyResult{}, err
}
// max_hosts 是历史兼容字段,当前业务不再限制主播数量,新建 Agency 固化为 0。
agency := hostdomain.Agency{AgencyID: command.AgencyID, OwnerUserID: command.OwnerUserID, RegionID: regionID, ParentBDUserID: command.ParentBDUserID, Name: command.Name, Status: hostdomain.AgencyStatusActive, JoinEnabled: command.JoinEnabled, MaxHosts: 0, CreatedByUserID: command.AdminUserID, CreatedAtMs: command.NowMs, UpdatedAtMs: command.NowMs}
agency := hostdomain.Agency{AgencyID: command.AgencyID, OwnerUserID: command.OwnerUserID, RegionID: regionID, ParentBDUserID: command.ParentBDUserID, Name: agencyName, Avatar: agencyAvatar, Status: hostdomain.AgencyStatusActive, JoinEnabled: command.JoinEnabled, MaxHosts: 0, CreatedByUserID: command.AdminUserID, CreatedAtMs: command.NowMs, UpdatedAtMs: command.NowMs}
if err := insertAgency(ctx, tx, agency); err != nil {
return hostdomain.CreateAgencyResult{}, mapHostDuplicateError(err)
}
@ -590,6 +594,53 @@ func (r *Repository) SetAgencyJoinEnabled(ctx context.Context, command hostservi
})
}
// UpdateAgencyProfile 只允许 active Agency owner 修改组织独立资料。
func (r *Repository) UpdateAgencyProfile(ctx context.Context, command hostservice.UpdateAgencyProfileCommand) (hostdomain.Agency, error) {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return hostdomain.Agency{}, err
}
defer tx.Rollback()
if existing, ok, err := commandResult(ctx, tx, command.CommandID); err != nil {
return hostdomain.Agency{}, err
} else if ok {
if err := requireCommandResult(existing, hostdomain.CommandTypeUpdateAgencyProfile, hostdomain.ResultTypeAgency); err != nil {
return hostdomain.Agency{}, err
}
return queryAgency(ctx, tx, "WHERE agency_id = ?", existing.ResultID)
}
agency, err := queryAgency(ctx, tx, "WHERE agency_id = ? FOR UPDATE", command.AgencyID)
if err != nil {
return hostdomain.Agency{}, err
}
if agency.Status != hostdomain.AgencyStatusActive || agency.OwnerUserID != command.OperatorUserID {
// Agency 资料的写权限只属于当前 active owner普通成员、关闭团队和历史 owner 都不能改。
return hostdomain.Agency{}, xerr.New(xerr.PermissionDenied, "permission denied")
}
if command.Name != nil {
agency.Name = *command.Name
}
if command.Avatar != nil {
agency.Avatar = *command.Avatar
}
agency.UpdatedAtMs = command.NowMs
if err := updateAgencyProfile(ctx, tx, agency); err != nil {
return hostdomain.Agency{}, err
}
if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{CommandID: command.CommandID, CommandType: hostdomain.CommandTypeUpdateAgencyProfile, ResultType: hostdomain.ResultTypeAgency, ResultID: agency.AgencyID, CreatedAtMs: command.NowMs}); err != nil {
return hostdomain.Agency{}, mapHostDuplicateError(err)
}
if err := insertHostOutbox(ctx, tx, command.EventID, "AgencyChanged", "agency", agency.AgencyID, command.NowMs); err != nil {
return hostdomain.Agency{}, err
}
if err := tx.Commit(); err != nil {
return hostdomain.Agency{}, err
}
return agency, nil
}
func (r *Repository) updateAgencyAdmin(ctx context.Context, commandID string, commandType string, agencyID int64, eventID int64, nowMs int64, mutate func(hostdomain.Agency) hostdomain.Agency) (hostdomain.Agency, error) {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {

View File

@ -50,8 +50,15 @@ var (
), 0),
source, first_became_host_at_ms, created_at_ms, updated_at_ms`, userRegionProjection("host_profiles", "user_id"))
agencyColumns = fmt.Sprintf(`
agency_id, owner_user_id, %s, parent_bd_user_id, name, status,
join_enabled, max_hosts, created_by_user_id, created_at_ms, updated_at_ms`, userRegionProjection("agencies", "owner_user_id"))
agency_id, owner_user_id, %s, parent_bd_user_id, name, avatar, status,
join_enabled, max_hosts, created_by_user_id, created_at_ms, updated_at_ms,
COALESCE((
SELECT COUNT(*)
FROM agency_memberships active_member
WHERE active_member.app_code = agencies.app_code
AND active_member.agency_id = agencies.agency_id
AND active_member.status = '%s'
), 0)`, userRegionProjection("agencies", "owner_user_id"), hostdomain.MembershipStatusActive)
bdProfileColumns = fmt.Sprintf(`
user_id, role, %s, COALESCE(parent_leader_user_id, 0), status,
created_by_user_id, created_at_ms, updated_at_ms`, userRegionProjection("bd_profiles", "user_id"))
@ -118,6 +125,29 @@ func userRegionValue(ctx context.Context, q sqlQueryer, userID int64, lockClause
return regionID, err
}
func defaultAgencyProfileFromOwner(ctx context.Context, tx *sql.Tx, ownerUserID int64, explicitName string) (string, string, error) {
snapshot, err := roleInvitationUserSnapshotByID(ctx, tx, ownerUserID)
if err != nil {
return "", "", err
}
name := strings.TrimSpace(explicitName)
if name == "" {
// Agency 的默认资料只在创建身份这一刻从 owner 用户资料复制一次;
// 后续用户资料和 Agency 资料完全分离,不能再在查询层动态兜底。
name = firstNonBlank(snapshot.Username, snapshot.DisplayUserID, fmt.Sprintf("%d", ownerUserID))
}
return name, strings.TrimSpace(snapshot.Avatar), nil
}
func firstNonBlank(values ...string) string {
for _, value := range values {
if trimmed := strings.TrimSpace(value); trimmed != "" {
return trimmed
}
}
return ""
}
func currentUserRegion(ctx context.Context, q sqlQueryer, userID int64, lockClause string) (int64, error) {
// Host 领域不拥有 users 表,但所有角色流转都依赖用户当前有效区域。
// 调用方需要串行化用户身份变化时传入 FOR UPDATE 来锁住 users 行。

View File

@ -512,12 +512,17 @@ func (r *Repository) acceptAgencyInvitation(ctx context.Context, tx *sql.Tx, com
// Agency、拥有者 host_profile、拥有者成员关系是同一业务事实的三张表投影
// 必须全部创建成功后邀请才能进入已接受状态。
agencyName, agencyAvatar, err := defaultAgencyProfileFromOwner(ctx, tx, invitation.TargetUserID, invitation.AgencyName)
if err != nil {
return false, false, err
}
agency := hostdomain.Agency{
AgencyID: command.AgencyID,
OwnerUserID: invitation.TargetUserID,
RegionID: targetRegionID,
ParentBDUserID: invitation.ParentBDUserID,
Name: invitation.AgencyName,
Name: agencyName,
Avatar: agencyAvatar,
Status: hostdomain.AgencyStatusActive,
JoinEnabled: true,
// max_hosts 是历史兼容字段,当前业务不再限制主播数量。

View File

@ -31,13 +31,13 @@ func (r *Repository) SearchAgencies(ctx context.Context, command hostservice.Sea
COALESCE(owner.region_id, 0),
a.parent_bd_user_id,
a.name,
a.avatar,
a.status,
a.join_enabled,
a.max_hosts,
a.created_by_user_id,
a.created_at_ms,
a.updated_at_ms,
COALESCE(owner.avatar, ''),
COUNT(active_member.membership_id)
FROM agencies a
INNER JOIN users owner
@ -65,13 +65,13 @@ func (r *Repository) SearchAgencies(ctx context.Context, command hostservice.Sea
owner.region_id,
a.parent_bd_user_id,
a.name,
a.avatar,
a.status,
a.join_enabled,
a.max_hosts,
a.created_by_user_id,
a.created_at_ms,
a.updated_at_ms,
owner.avatar
a.updated_at_ms
ORDER BY a.created_at_ms DESC, a.agency_id DESC
LIMIT ?
`
@ -618,12 +618,14 @@ func scanAgency(scanner rowScanner) (hostdomain.Agency, error) {
&agency.RegionID,
&agency.ParentBDUserID,
&agency.Name,
&agency.Avatar,
&agency.Status,
&agency.JoinEnabled,
&agency.MaxHosts,
&agency.CreatedByUserID,
&agency.CreatedAtMs,
&agency.UpdatedAtMs,
&agency.ActiveHostCount,
)
return agency, err
}
@ -636,13 +638,13 @@ func scanSearchAgency(scanner rowScanner) (hostdomain.Agency, error) {
&agency.RegionID,
&agency.ParentBDUserID,
&agency.Name,
&agency.Avatar,
&agency.Status,
&agency.JoinEnabled,
&agency.MaxHosts,
&agency.CreatedByUserID,
&agency.CreatedAtMs,
&agency.UpdatedAtMs,
&agency.OwnerAvatar,
&agency.ActiveHostCount,
)
return agency, err

View File

@ -192,10 +192,10 @@ func insertAgency(ctx context.Context, tx *sql.Tx, agency hostdomain.Agency) err
// 第一阶段只有接受 Agency 邀请时会创建 Agency。
_, err := tx.ExecContext(ctx, `
INSERT INTO agencies (
app_code, agency_id, owner_user_id, parent_bd_user_id, name, status,
app_code, agency_id, owner_user_id, parent_bd_user_id, name, avatar, status,
join_enabled, max_hosts, created_by_user_id, created_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, appcode.FromContext(ctx), agency.AgencyID, agency.OwnerUserID, agency.ParentBDUserID, agency.Name, agency.Status, agency.JoinEnabled, agency.MaxHosts, agency.CreatedByUserID, agency.CreatedAtMs, agency.UpdatedAtMs)
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, appcode.FromContext(ctx), agency.AgencyID, agency.OwnerUserID, agency.ParentBDUserID, agency.Name, agency.Avatar, agency.Status, agency.JoinEnabled, agency.MaxHosts, agency.CreatedByUserID, agency.CreatedAtMs, agency.UpdatedAtMs)
return err
}
@ -209,6 +209,16 @@ func updateAgencyAdminState(ctx context.Context, tx *sql.Tx, agency hostdomain.A
return err
}
func updateAgencyProfile(ctx context.Context, tx *sql.Tx, agency hostdomain.Agency) error {
// Agency 资料是独立组织资料;这里绝不更新 users 表,也不从 owner 用户资料重新派生。
_, err := tx.ExecContext(ctx, `
UPDATE agencies
SET name = ?, avatar = ?, updated_at_ms = ?
WHERE app_code = ? AND agency_id = ?
`, agency.Name, agency.Avatar, agency.UpdatedAtMs, appcode.FromContext(ctx), agency.AgencyID)
return err
}
func insertBDProfile(ctx context.Context, tx *sql.Tx, profile hostdomain.BDProfile) error {
// 普通 BD 身份行和 host_profile 相互独立BD 用户不会自动成为 Host也不会隐式拥有 BD Leader 权限。
_, err := tx.ExecContext(ctx, `

View File

@ -92,6 +92,49 @@ func (r *Repository) BatchGetUsers(ctx context.Context, userIDs []int64) (map[in
return result, rows.Err()
}
// BatchGetRoomBasicUsers 只读取房间首屏需要的基础展示字段,避免 JoinRoom lite 触发完整用户投影。
func (r *Repository) BatchGetRoomBasicUsers(ctx context.Context, userIDs []int64) (map[int64]userdomain.RoomBasicUser, error) {
placeholders := make([]string, 0, len(userIDs))
args := make([]any, 0, len(userIDs)+2)
nowMS := time.Now().UTC().UnixMilli()
args = append(args, nowMS)
for _, userID := range userIDs {
placeholders = append(placeholders, "?")
args = append(args, userID)
}
args = append(args, appcode.FromContext(ctx))
rows, err := r.db.QueryContext(ctx, fmt.Sprintf(`
SELECT user_id,
COALESCE(username, ''),
COALESCE(avatar, ''),
CASE
WHEN current_display_user_id_kind <> 'default'
AND COALESCE(current_display_user_id_expires_at_ms, 0) > 0
AND current_display_user_id_expires_at_ms <= ?
THEN default_display_user_id
ELSE current_display_user_id
END
FROM users FORCE INDEX (PRIMARY)
WHERE user_id IN (%s)
AND app_code = ?
`, strings.Join(placeholders, ",")), args...)
if err != nil {
return nil, err
}
defer rows.Close()
result := make(map[int64]userdomain.RoomBasicUser, len(userIDs))
for rows.Next() {
var user userdomain.RoomBasicUser
if err := rows.Scan(&user.UserID, &user.Username, &user.Avatar, &user.DisplayUserID); err != nil {
return nil, err
}
result[user.UserID] = user
}
return result, rows.Err()
}
// ListUserIDs returns active user IDs in ascending order for cursor-based backend fanout.
func (r *Repository) ListUserIDs(ctx context.Context, filter userdomain.UserIDPageFilter) ([]int64, error) {
conditions := []string{"app_code = ?", "status = ?", "user_id > ?"}

View File

@ -129,6 +129,11 @@ func (r *Repository) BatchGetUsers(ctx context.Context, userIDs []int64) (map[in
return r.Repository.UserRepository().BatchGetUsers(ctx, userIDs)
}
// BatchGetRoomBasicUsers 让测试 wrapper 继续满足房间首屏基础资料查询接口。
func (r *Repository) BatchGetRoomBasicUsers(ctx context.Context, userIDs []int64) (map[int64]userdomain.RoomBasicUser, error) {
return r.Repository.UserRepository().BatchGetRoomBasicUsers(ctx, userIDs)
}
// ListUserIDs 让测试 wrapper 继续满足后台 fanout 目标用户查询接口。
func (r *Repository) ListUserIDs(ctx context.Context, filter userdomain.UserIDPageFilter) ([]int64, error) {
return r.Repository.UserRepository().ListUserIDs(ctx, filter)
@ -866,18 +871,19 @@ func (r *Repository) PutAgency(agency hostdomain.Agency) {
_, err := r.schema.DB.ExecContext(context.Background(), `
INSERT INTO agencies (
agency_id, owner_user_id, parent_bd_user_id, name, status,
agency_id, owner_user_id, parent_bd_user_id, name, avatar, status,
join_enabled, max_hosts, created_by_user_id, created_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
owner_user_id = VALUES(owner_user_id),
parent_bd_user_id = VALUES(parent_bd_user_id),
name = VALUES(name),
avatar = VALUES(avatar),
status = VALUES(status),
join_enabled = VALUES(join_enabled),
max_hosts = VALUES(max_hosts),
updated_at_ms = VALUES(updated_at_ms)
`, agency.AgencyID, agency.OwnerUserID, agency.ParentBDUserID, agency.Name, agency.Status, agency.JoinEnabled, agency.MaxHosts, agency.CreatedByUserID, agency.CreatedAtMs, agency.UpdatedAtMs)
`, agency.AgencyID, agency.OwnerUserID, agency.ParentBDUserID, agency.Name, agency.Avatar, agency.Status, agency.JoinEnabled, agency.MaxHosts, agency.CreatedByUserID, agency.CreatedAtMs, agency.UpdatedAtMs)
if err != nil {
r.t.Fatalf("seed agency %d failed: %v", agency.AgencyID, err)
}

View File

@ -435,7 +435,7 @@ func toProtoAgency(agency hostdomain.Agency) *userv1.Agency {
CreatedByUserId: agency.CreatedByUserID,
CreatedAtMs: agency.CreatedAtMs,
UpdatedAtMs: agency.UpdatedAtMs,
Avatar: agency.OwnerAvatar,
Avatar: agency.Avatar,
HostCount: agency.ActiveHostCount,
}
}

View File

@ -393,6 +393,26 @@ func (s *Server) GetAgency(ctx context.Context, req *userv1.GetAgencyRequest) (*
return &userv1.GetAgencyResponse{Agency: toProtoAgency(agency)}, nil
}
// UpdateAgencyProfile 修改 Agency 独立资料;用户资料和 Agency 资料不会互相回写。
func (s *Server) UpdateAgencyProfile(ctx context.Context, req *userv1.UpdateAgencyProfileRequest) (*userv1.UpdateAgencyProfileResponse, error) {
if s.hostSvc == nil {
return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
}
ctx = contextWithApp(ctx, req.GetMeta())
agency, err := s.hostSvc.UpdateAgencyProfile(ctx, hostservice.UpdateAgencyProfileInput{
CommandID: req.GetCommandId(),
OperatorUserID: req.GetOperatorUserId(),
AgencyID: req.GetAgencyId(),
Name: req.Name,
Avatar: req.Avatar,
RequestID: req.GetMeta().GetRequestId(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &userv1.UpdateAgencyProfileResponse{Agency: toProtoAgency(agency)}, nil
}
// CheckBusinessCapability 返回 H5/App 业务入口可用性,具体身份规则由 host service 统一判断。
func (s *Server) CheckBusinessCapability(ctx context.Context, req *userv1.CheckBusinessCapabilityRequest) (*userv1.CheckBusinessCapabilityResponse, error) {
if s.hostSvc == nil {

View File

@ -660,6 +660,26 @@ func (s *Server) BatchGetUsers(ctx context.Context, req *userv1.BatchGetUsersReq
return &userv1.BatchGetUsersResponse{Users: result}, nil
}
// BatchGetRoomBasicUsers 批量返回房间首屏最小用户展示资料。
func (s *Server) BatchGetRoomBasicUsers(ctx context.Context, req *userv1.BatchGetRoomBasicUsersRequest) (*userv1.BatchGetRoomBasicUsersResponse, error) {
ctx = contextWithApp(ctx, req.GetMeta())
users, err := s.userSvc.BatchGetRoomBasicUsers(ctx, req.GetUserIds())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
result := make(map[int64]*userv1.RoomBasicUser, len(users))
for userID, user := range users {
// 只透出首屏绘制必需字段,避免 gateway lite 路径拿到完整用户主状态。
result[userID] = &userv1.RoomBasicUser{
UserId: user.UserID,
Username: user.Username,
Avatar: user.Avatar,
DisplayUserId: user.DisplayUserID,
}
}
return &userv1.BatchGetRoomBasicUsersResponse{Users: result}, nil
}
// ListUserIDs 为后台 fanout 任务返回稳定递增的用户 ID 游标页。
func (s *Server) ListUserIDs(ctx context.Context, req *userv1.ListUserIDsRequest) (*userv1.ListUserIDsResponse, error) {
ctx = contextWithApp(ctx, req.GetMeta())