完善我的页面房间关系接口

This commit is contained in:
zhx 2026-05-11 17:53:18 +08:00
parent b95224bff1
commit 8fcf491b74
20 changed files with 1763 additions and 1110 deletions

View File

@ -3187,6 +3187,8 @@ type ListRoomFeedsRequest struct {
Cursor string `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"` Cursor string `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"`
Limit int32 `protobuf:"varint,6,opt,name=limit,proto3" json:"limit,omitempty"` Limit int32 `protobuf:"varint,6,opt,name=limit,proto3" json:"limit,omitempty"`
Query string `protobuf:"bytes,7,opt,name=query,proto3" json:"query,omitempty"` Query string `protobuf:"bytes,7,opt,name=query,proto3" json:"query,omitempty"`
// related_users 只用于 friend/following关系事实由 gateway 从 user-service 读取后传入。
RelatedUsers []*RoomFeedRelatedUser `protobuf:"bytes,8,rep,name=related_users,json=relatedUsers,proto3" json:"related_users,omitempty"`
} }
func (x *ListRoomFeedsRequest) Reset() { func (x *ListRoomFeedsRequest) Reset() {
@ -3268,6 +3270,67 @@ func (x *ListRoomFeedsRequest) GetQuery() string {
return "" return ""
} }
func (x *ListRoomFeedsRequest) GetRelatedUsers() []*RoomFeedRelatedUser {
if x != nil {
return x.RelatedUsers
}
return nil
}
// RoomFeedRelatedUser 是 Mine 关系房间流的排序输入,不让 room-service 持有好友/关注事实。
type RoomFeedRelatedUser struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
RelationUpdatedAtMs int64 `protobuf:"varint,2,opt,name=relation_updated_at_ms,json=relationUpdatedAtMs,proto3" json:"relation_updated_at_ms,omitempty"`
}
func (x *RoomFeedRelatedUser) Reset() {
*x = RoomFeedRelatedUser{}
mi := &file_proto_room_v1_room_proto_msgTypes[48]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *RoomFeedRelatedUser) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RoomFeedRelatedUser) ProtoMessage() {}
func (x *RoomFeedRelatedUser) ProtoReflect() protoreflect.Message {
mi := &file_proto_room_v1_room_proto_msgTypes[48]
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 RoomFeedRelatedUser.ProtoReflect.Descriptor instead.
func (*RoomFeedRelatedUser) Descriptor() ([]byte, []int) {
return file_proto_room_v1_room_proto_rawDescGZIP(), []int{48}
}
func (x *RoomFeedRelatedUser) GetUserId() int64 {
if x != nil {
return x.UserId
}
return 0
}
func (x *RoomFeedRelatedUser) GetRelationUpdatedAtMs() int64 {
if x != nil {
return x.RelationUpdatedAtMs
}
return 0
}
// RoomListItem 是房间发现页卡片读模型,不承载完整 Room Cell 状态。 // RoomListItem 是房间发现页卡片读模型,不承载完整 Room Cell 状态。
type RoomListItem struct { type RoomListItem struct {
state protoimpl.MessageState state protoimpl.MessageState
@ -3292,7 +3355,7 @@ type RoomListItem struct {
func (x *RoomListItem) Reset() { func (x *RoomListItem) Reset() {
*x = RoomListItem{} *x = RoomListItem{}
mi := &file_proto_room_v1_room_proto_msgTypes[48] mi := &file_proto_room_v1_room_proto_msgTypes[49]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -3304,7 +3367,7 @@ func (x *RoomListItem) String() string {
func (*RoomListItem) ProtoMessage() {} func (*RoomListItem) ProtoMessage() {}
func (x *RoomListItem) ProtoReflect() protoreflect.Message { func (x *RoomListItem) ProtoReflect() protoreflect.Message {
mi := &file_proto_room_v1_room_proto_msgTypes[48] mi := &file_proto_room_v1_room_proto_msgTypes[49]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -3317,7 +3380,7 @@ func (x *RoomListItem) ProtoReflect() protoreflect.Message {
// Deprecated: Use RoomListItem.ProtoReflect.Descriptor instead. // Deprecated: Use RoomListItem.ProtoReflect.Descriptor instead.
func (*RoomListItem) Descriptor() ([]byte, []int) { func (*RoomListItem) Descriptor() ([]byte, []int) {
return file_proto_room_v1_room_proto_rawDescGZIP(), []int{48} return file_proto_room_v1_room_proto_rawDescGZIP(), []int{49}
} }
func (x *RoomListItem) GetRoomId() string { func (x *RoomListItem) GetRoomId() string {
@ -3430,7 +3493,7 @@ type ListRoomsResponse struct {
func (x *ListRoomsResponse) Reset() { func (x *ListRoomsResponse) Reset() {
*x = ListRoomsResponse{} *x = ListRoomsResponse{}
mi := &file_proto_room_v1_room_proto_msgTypes[49] mi := &file_proto_room_v1_room_proto_msgTypes[50]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -3442,7 +3505,7 @@ func (x *ListRoomsResponse) String() string {
func (*ListRoomsResponse) ProtoMessage() {} func (*ListRoomsResponse) ProtoMessage() {}
func (x *ListRoomsResponse) ProtoReflect() protoreflect.Message { func (x *ListRoomsResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_room_v1_room_proto_msgTypes[49] mi := &file_proto_room_v1_room_proto_msgTypes[50]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -3455,7 +3518,7 @@ func (x *ListRoomsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListRoomsResponse.ProtoReflect.Descriptor instead. // Deprecated: Use ListRoomsResponse.ProtoReflect.Descriptor instead.
func (*ListRoomsResponse) Descriptor() ([]byte, []int) { func (*ListRoomsResponse) Descriptor() ([]byte, []int) {
return file_proto_room_v1_room_proto_rawDescGZIP(), []int{49} return file_proto_room_v1_room_proto_rawDescGZIP(), []int{50}
} }
func (x *ListRoomsResponse) GetRooms() []*RoomListItem { func (x *ListRoomsResponse) GetRooms() []*RoomListItem {
@ -3485,7 +3548,7 @@ type GetMyRoomRequest struct {
func (x *GetMyRoomRequest) Reset() { func (x *GetMyRoomRequest) Reset() {
*x = GetMyRoomRequest{} *x = GetMyRoomRequest{}
mi := &file_proto_room_v1_room_proto_msgTypes[50] mi := &file_proto_room_v1_room_proto_msgTypes[51]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -3497,7 +3560,7 @@ func (x *GetMyRoomRequest) String() string {
func (*GetMyRoomRequest) ProtoMessage() {} func (*GetMyRoomRequest) ProtoMessage() {}
func (x *GetMyRoomRequest) ProtoReflect() protoreflect.Message { func (x *GetMyRoomRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_room_v1_room_proto_msgTypes[50] mi := &file_proto_room_v1_room_proto_msgTypes[51]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -3510,7 +3573,7 @@ func (x *GetMyRoomRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetMyRoomRequest.ProtoReflect.Descriptor instead. // Deprecated: Use GetMyRoomRequest.ProtoReflect.Descriptor instead.
func (*GetMyRoomRequest) Descriptor() ([]byte, []int) { func (*GetMyRoomRequest) Descriptor() ([]byte, []int) {
return file_proto_room_v1_room_proto_rawDescGZIP(), []int{50} return file_proto_room_v1_room_proto_rawDescGZIP(), []int{51}
} }
func (x *GetMyRoomRequest) GetMeta() *RequestMeta { func (x *GetMyRoomRequest) GetMeta() *RequestMeta {
@ -3541,7 +3604,7 @@ type GetMyRoomResponse struct {
func (x *GetMyRoomResponse) Reset() { func (x *GetMyRoomResponse) Reset() {
*x = GetMyRoomResponse{} *x = GetMyRoomResponse{}
mi := &file_proto_room_v1_room_proto_msgTypes[51] mi := &file_proto_room_v1_room_proto_msgTypes[52]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -3553,7 +3616,7 @@ func (x *GetMyRoomResponse) String() string {
func (*GetMyRoomResponse) ProtoMessage() {} func (*GetMyRoomResponse) ProtoMessage() {}
func (x *GetMyRoomResponse) ProtoReflect() protoreflect.Message { func (x *GetMyRoomResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_room_v1_room_proto_msgTypes[51] mi := &file_proto_room_v1_room_proto_msgTypes[52]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -3566,7 +3629,7 @@ func (x *GetMyRoomResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetMyRoomResponse.ProtoReflect.Descriptor instead. // Deprecated: Use GetMyRoomResponse.ProtoReflect.Descriptor instead.
func (*GetMyRoomResponse) Descriptor() ([]byte, []int) { func (*GetMyRoomResponse) Descriptor() ([]byte, []int) {
return file_proto_room_v1_room_proto_rawDescGZIP(), []int{51} return file_proto_room_v1_room_proto_rawDescGZIP(), []int{52}
} }
func (x *GetMyRoomResponse) GetHasRoom() bool { func (x *GetMyRoomResponse) GetHasRoom() bool {
@ -3603,7 +3666,7 @@ type GetCurrentRoomRequest struct {
func (x *GetCurrentRoomRequest) Reset() { func (x *GetCurrentRoomRequest) Reset() {
*x = GetCurrentRoomRequest{} *x = GetCurrentRoomRequest{}
mi := &file_proto_room_v1_room_proto_msgTypes[52] mi := &file_proto_room_v1_room_proto_msgTypes[53]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -3615,7 +3678,7 @@ func (x *GetCurrentRoomRequest) String() string {
func (*GetCurrentRoomRequest) ProtoMessage() {} func (*GetCurrentRoomRequest) ProtoMessage() {}
func (x *GetCurrentRoomRequest) ProtoReflect() protoreflect.Message { func (x *GetCurrentRoomRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_room_v1_room_proto_msgTypes[52] mi := &file_proto_room_v1_room_proto_msgTypes[53]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -3628,7 +3691,7 @@ func (x *GetCurrentRoomRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetCurrentRoomRequest.ProtoReflect.Descriptor instead. // Deprecated: Use GetCurrentRoomRequest.ProtoReflect.Descriptor instead.
func (*GetCurrentRoomRequest) Descriptor() ([]byte, []int) { func (*GetCurrentRoomRequest) Descriptor() ([]byte, []int) {
return file_proto_room_v1_room_proto_rawDescGZIP(), []int{52} return file_proto_room_v1_room_proto_rawDescGZIP(), []int{53}
} }
func (x *GetCurrentRoomRequest) GetMeta() *RequestMeta { func (x *GetCurrentRoomRequest) GetMeta() *RequestMeta {
@ -3665,7 +3728,7 @@ type GetCurrentRoomResponse struct {
func (x *GetCurrentRoomResponse) Reset() { func (x *GetCurrentRoomResponse) Reset() {
*x = GetCurrentRoomResponse{} *x = GetCurrentRoomResponse{}
mi := &file_proto_room_v1_room_proto_msgTypes[53] mi := &file_proto_room_v1_room_proto_msgTypes[54]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -3677,7 +3740,7 @@ func (x *GetCurrentRoomResponse) String() string {
func (*GetCurrentRoomResponse) ProtoMessage() {} func (*GetCurrentRoomResponse) ProtoMessage() {}
func (x *GetCurrentRoomResponse) ProtoReflect() protoreflect.Message { func (x *GetCurrentRoomResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_room_v1_room_proto_msgTypes[53] mi := &file_proto_room_v1_room_proto_msgTypes[54]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -3690,7 +3753,7 @@ func (x *GetCurrentRoomResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetCurrentRoomResponse.ProtoReflect.Descriptor instead. // Deprecated: Use GetCurrentRoomResponse.ProtoReflect.Descriptor instead.
func (*GetCurrentRoomResponse) Descriptor() ([]byte, []int) { func (*GetCurrentRoomResponse) Descriptor() ([]byte, []int) {
return file_proto_room_v1_room_proto_rawDescGZIP(), []int{53} return file_proto_room_v1_room_proto_rawDescGZIP(), []int{54}
} }
func (x *GetCurrentRoomResponse) GetHasCurrentRoom() bool { func (x *GetCurrentRoomResponse) GetHasCurrentRoom() bool {
@ -3770,7 +3833,7 @@ type GetRoomSnapshotRequest struct {
func (x *GetRoomSnapshotRequest) Reset() { func (x *GetRoomSnapshotRequest) Reset() {
*x = GetRoomSnapshotRequest{} *x = GetRoomSnapshotRequest{}
mi := &file_proto_room_v1_room_proto_msgTypes[54] mi := &file_proto_room_v1_room_proto_msgTypes[55]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -3782,7 +3845,7 @@ func (x *GetRoomSnapshotRequest) String() string {
func (*GetRoomSnapshotRequest) ProtoMessage() {} func (*GetRoomSnapshotRequest) ProtoMessage() {}
func (x *GetRoomSnapshotRequest) ProtoReflect() protoreflect.Message { func (x *GetRoomSnapshotRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_room_v1_room_proto_msgTypes[54] mi := &file_proto_room_v1_room_proto_msgTypes[55]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -3795,7 +3858,7 @@ func (x *GetRoomSnapshotRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetRoomSnapshotRequest.ProtoReflect.Descriptor instead. // Deprecated: Use GetRoomSnapshotRequest.ProtoReflect.Descriptor instead.
func (*GetRoomSnapshotRequest) Descriptor() ([]byte, []int) { func (*GetRoomSnapshotRequest) Descriptor() ([]byte, []int) {
return file_proto_room_v1_room_proto_rawDescGZIP(), []int{54} return file_proto_room_v1_room_proto_rawDescGZIP(), []int{55}
} }
func (x *GetRoomSnapshotRequest) GetMeta() *RequestMeta { func (x *GetRoomSnapshotRequest) GetMeta() *RequestMeta {
@ -3831,7 +3894,7 @@ type GetRoomSnapshotResponse struct {
func (x *GetRoomSnapshotResponse) Reset() { func (x *GetRoomSnapshotResponse) Reset() {
*x = GetRoomSnapshotResponse{} *x = GetRoomSnapshotResponse{}
mi := &file_proto_room_v1_room_proto_msgTypes[55] mi := &file_proto_room_v1_room_proto_msgTypes[56]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -3843,7 +3906,7 @@ func (x *GetRoomSnapshotResponse) String() string {
func (*GetRoomSnapshotResponse) ProtoMessage() {} func (*GetRoomSnapshotResponse) ProtoMessage() {}
func (x *GetRoomSnapshotResponse) ProtoReflect() protoreflect.Message { func (x *GetRoomSnapshotResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_room_v1_room_proto_msgTypes[55] mi := &file_proto_room_v1_room_proto_msgTypes[56]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -3856,7 +3919,7 @@ func (x *GetRoomSnapshotResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetRoomSnapshotResponse.ProtoReflect.Descriptor instead. // Deprecated: Use GetRoomSnapshotResponse.ProtoReflect.Descriptor instead.
func (*GetRoomSnapshotResponse) Descriptor() ([]byte, []int) { func (*GetRoomSnapshotResponse) Descriptor() ([]byte, []int) {
return file_proto_room_v1_room_proto_rawDescGZIP(), []int{55} return file_proto_room_v1_room_proto_rawDescGZIP(), []int{56}
} }
func (x *GetRoomSnapshotResponse) GetRoom() *RoomSnapshot { func (x *GetRoomSnapshotResponse) GetRoom() *RoomSnapshot {
@ -4351,7 +4414,7 @@ var file_proto_room_v1_room_proto_rawDesc = []byte{
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x12, 0x14, 0x0a,
0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69,
0x6d, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x6d, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01,
0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0xee, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0xb7, 0x02, 0x0a, 0x14, 0x4c, 0x69,
0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x46, 0x65, 0x65, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x46, 0x65, 0x65, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31,
@ -4366,245 +4429,256 @@ var file_proto_room_v1_room_proto_rawDesc = []byte{
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x12, 0x14, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x12, 0x14,
0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c,
0x69, 0x6d, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x07, 0x20, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x07, 0x20,
0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0xbd, 0x03, 0x0a, 0x0c, 0x52, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x47, 0x0a, 0x0d, 0x72, 0x65,
0x6f, 0x6f, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28,
0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x0b, 0x32, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76,
0x6f, 0x6d, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x46, 0x65, 0x65, 0x64, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x65,
0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x64, 0x55, 0x73, 0x65, 0x72, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x55, 0x73,
0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x72, 0x73, 0x22, 0x63, 0x0a, 0x13, 0x52, 0x6f, 0x6f, 0x6d, 0x46, 0x65, 0x65, 0x64, 0x52,
0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73,
0x68, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65,
0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x72, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x01, 0x28, 0x03, 0x52, 0x13, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x70, 0x64,
0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xbd, 0x03, 0x0a, 0x0c, 0x52, 0x6f, 0x6f,
0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f,
0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d,
0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x68, 0x65, 0x61, 0x74, 0x12, 0x21, 0x0a, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72,
0x0c, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72,
0x01, 0x28, 0x05, 0x52, 0x0b, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75,
0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x68, 0x6f,
0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c,
0x2e, 0x0a, 0x13, 0x6f, 0x63, 0x63, 0x75, 0x70, 0x69, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x1b,
0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x6f, 0x63, 0x0a, 0x09, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28,
0x63, 0x75, 0x70, 0x69, 0x65, 0x64, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x6d,
0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12,
0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,
0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, 0x61, 0x74, 0x18,
0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x68, 0x65, 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6f,
0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28,
0x68, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x05, 0x52, 0x0b, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d,
0x6f, 0x6f, 0x6d, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x22, 0x67, 0x0a, 0x11, 0x4c, 0x69, 0x0a, 0x0a, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01,
0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e, 0x0a,
0x31, 0x0a, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x13, 0x6f, 0x63, 0x63, 0x75, 0x70, 0x69, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x6f, 0x63, 0x63, 0x75,
0x6f, 0x6f, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x72, 0x6f, 0x6f, 0x70, 0x69, 0x65, 0x64, 0x53, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a,
0x6d, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f,
0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x65, 0x78, 0x74, 0x43, 0x75, 0x72, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c,
0x73, 0x6f, 0x72, 0x22, 0x66, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70,
0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70,
0x43, 0x6f, 0x64, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x68, 0x6f,
0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x6f, 0x6f,
0x6d, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x22, 0x67, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74,
0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a,
0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f,
0x6d, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73,
0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x65, 0x78, 0x74, 0x43, 0x75, 0x72, 0x73, 0x6f,
0x72, 0x22, 0x66, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d,
0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52,
0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75,
0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77,
0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x85, 0x01, 0x0a, 0x11, 0x47, 0x65,
0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x19, 0x0a, 0x08, 0x68, 0x61, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28,
0x08, 0x52, 0x07, 0x68, 0x61, 0x73, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f,
0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x4c, 0x69, 0x73,
0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x73,
0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20,
0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d,
0x73, 0x22, 0x60, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52,
0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65,
0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73,
0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65,
0x72, 0x49, 0x64, 0x22, 0xd6, 0x02, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65,
0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28,
0x0a, 0x10, 0x68, 0x61, 0x73, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x6f,
0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x68, 0x61, 0x73, 0x43, 0x75, 0x72,
0x72, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d,
0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49,
0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x56, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x69, 0x63, 0x5f,
0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0c, 0x6d, 0x69, 0x63, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23,
0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18,
0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x53, 0x74,
0x61, 0x74, 0x65, 0x12, 0x2b, 0x0a, 0x12, 0x6e, 0x65, 0x65, 0x64, 0x5f, 0x6a, 0x6f, 0x69, 0x6e,
0x5f, 0x69, 0x6d, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52,
0x0f, 0x6e, 0x65, 0x65, 0x64, 0x4a, 0x6f, 0x69, 0x6e, 0x49, 0x6d, 0x47, 0x72, 0x6f, 0x75, 0x70,
0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x65, 0x65, 0x64, 0x5f, 0x72, 0x74, 0x63, 0x5f, 0x74, 0x6f, 0x6b,
0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6e, 0x65, 0x65, 0x64, 0x52, 0x74,
0x63, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x87, 0x01, 0x0a,
0x16, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f,
0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74,
0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f,
0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64,
0x6f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x85, 0x01, 0x0a, 0x11, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f,
0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72,
0x65, 0x12, 0x19, 0x0a, 0x08, 0x68, 0x61, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x70, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f,
0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x61, 0x73, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x2f, 0x0a, 0x04, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x4c, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e,
0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x24, 0x0a, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f,
0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x6f, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d,
0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76,
0x65, 0x4d, 0x73, 0x22, 0x60, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x32, 0x9a, 0x0c, 0x0a, 0x12, 0x52, 0x6f, 0x6f,
0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x51, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x20, 0x2e,
0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72,
0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e,
0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xd6, 0x02, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x72, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x1e,
0x12, 0x28, 0x0a, 0x10, 0x68, 0x61, 0x73, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4a,
0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x68, 0x61, 0x73, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f,
0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4a,
0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6f, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x6d, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x5a, 0x0a, 0x0d, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x56, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x04, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x69, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f,
0x63, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62,
0x28, 0x09, 0x52, 0x0c, 0x6d, 0x69, 0x63, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x4c,
0x12, 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x61, 0x76, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x52, 0x6f,
0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x2b, 0x0a, 0x12, 0x6e, 0x65, 0x65, 0x64, 0x5f, 0x6a, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x69, 0x6e, 0x5f, 0x69, 0x6d, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x52,
0x08, 0x52, 0x0f, 0x6e, 0x65, 0x65, 0x64, 0x4a, 0x6f, 0x69, 0x6e, 0x49, 0x6d, 0x47, 0x72, 0x6f, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x43,
0x75, 0x70, 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x65, 0x65, 0x64, 0x5f, 0x72, 0x74, 0x63, 0x5f, 0x74, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6e, 0x65, 0x65, 0x64, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x6f,
0x52, 0x74, 0x63, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52,
0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x87, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x05, 0x4d,
0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x69, 0x63, 0x55, 0x70, 0x12, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f,
0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x63, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x74, 0x1a, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76,
0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x31, 0x2e, 0x4d, 0x69, 0x63, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x48, 0x0a, 0x07, 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x1d, 0x2e, 0x68, 0x79, 0x61,
0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x63, 0x44, 0x6f,
0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x76, 0x69, 0x65, 0x77, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77,
0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x70, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x52, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x43, 0x68, 0x61,
0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x67, 0x65, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61,
0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67,
0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x65, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e,
0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73,
0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d,
0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x32, 0x9a, 0x0c, 0x0a, 0x12, 0x52, 0x4d, 0x69, 0x63, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x12, 0x2a, 0x2e,
0x6f, 0x6f, 0x6d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f,
0x65, 0x12, 0x51, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x4d, 0x69, 0x63, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69,
0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x6d, 0x4d, 0x69, 0x63, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x52, 0x65,
0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x54, 0x43, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x54, 0x43,
0x2e, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68,
0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70,
0x2e, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x6c, 0x79, 0x52, 0x54, 0x43, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74,
0x61, 0x74, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x4c, 0x6f, 0x63, 0x6b, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f,
0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x4c,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61,
0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x72, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69,
0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x63, 0x53, 0x65, 0x61, 0x74, 0x4c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x09, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x53, 0x65, 0x74, 0x43, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x61, 0x62,
0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x6c, 0x65, 0x64, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d,
0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x68, 0x61,
0x09, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x69, 0x6e,
0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x71,
0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f,
0x05, 0x4d, 0x69, 0x63, 0x55, 0x70, 0x12, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x69,
0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x63, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x10, 0x54, 0x72, 0x61,
0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x26, 0x2e,
0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x63, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72,
0x65, 0x12, 0x48, 0x0a, 0x07, 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x1d, 0x2e, 0x68, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x63, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f,
0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x79, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x6f,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b,
0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x43, 0x0a, 0x08, 0x4d, 0x75, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61,
0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x12, 0x23, 0x2e, 0x68, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x75, 0x74, 0x65, 0x55,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61,
0x6e, 0x67, 0x65, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x75, 0x74, 0x65, 0x55,
0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x4b,
0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x52, 0x69, 0x63, 0x6b, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x69, 0x63, 0x6b, 0x55, 0x73, 0x65, 0x72,
0x72, 0x6d, 0x4d, 0x69, 0x63, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x12, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x69, 0x63, 0x6b, 0x55, 0x73, 0x65, 0x72,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x4d, 0x69, 0x63, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x55, 0x6e, 0x62, 0x61,
0x68, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52,
0x69, 0x72, 0x6d, 0x4d, 0x69, 0x63, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72,
0x79, 0x52, 0x54, 0x43, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x53, 0x65, 0x6e, 0x64,
0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x47, 0x69, 0x66, 0x74, 0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f,
0x54, 0x43, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x71,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f,
0x70, 0x70, 0x6c, 0x79, 0x52, 0x54, 0x43, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x73,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xee, 0x01, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x75,
0x61, 0x74, 0x4c, 0x6f, 0x63, 0x6b, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x61, 0x72, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x43, 0x68,
0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x65, 0x63, 0x6b, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69,
0x74, 0x4c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x6f, 0x6e, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x50, 0x65, 0x72,
0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x4c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b,
0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x53, 0x65, 0x74, 0x43, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43,
0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73,
0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x61, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x56,
0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x65, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76,
0x68, 0x61, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x72, 0x65, 0x73,
0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79,
0x69, 0x6e, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69,
0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x52, 0x66, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xcb, 0x03, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x51,
0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x75, 0x65, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x4c,
0x6d, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x10, 0x54, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f,
0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0d, 0x4c,
0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x46, 0x65, 0x65, 0x64, 0x73, 0x12, 0x23, 0x2e, 0x68,
0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73,
0x12, 0x4b, 0x0a, 0x08, 0x4d, 0x75, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x46, 0x65, 0x65, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x75, 0x74, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76,
0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x75, 0x74, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d,
0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31,
0x08, 0x4b, 0x69, 0x63, 0x6b, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x69, 0x63, 0x6b, 0x55, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76,
0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x69, 0x63, 0x6b, 0x55, 0x73, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e,
0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x55, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f,
0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74,
0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x79,
0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43,
0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x55, 0x73, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x53, 0x65, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61,
0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f,
0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x52, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68,
0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x52, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xee, 0x01, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70,
0x47, 0x75, 0x61, 0x72, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f,
0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x6f,
0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6f, 0x6d, 0x2f, 0x76, 0x31, 0x3b, 0x72, 0x6f, 0x6f, 0x6d, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72,
0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x50, 0x6f, 0x74, 0x6f, 0x33,
0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31,
0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x50, 0x65, 0x72, 0x6d, 0x69,
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a,
0x12, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x72, 0x65, 0x73, 0x65,
0x6e, 0x63, 0x65, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d,
0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x72,
0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65,
0x72, 0x69, 0x66, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xcb, 0x03, 0x0a, 0x10, 0x52, 0x6f, 0x6f,
0x6d, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4e, 0x0a,
0x09, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52,
0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a,
0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x46, 0x65, 0x65, 0x64, 0x73, 0x12, 0x23,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c,
0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x46, 0x65, 0x65, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d,
0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f,
0x6f, 0x6d, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e,
0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d,
0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72,
0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65,
0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65,
0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53,
0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53,
0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47,
0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,
0x72, 0x6f, 0x6f, 0x6d, 0x2f, 0x76, 0x31, 0x3b, 0x72, 0x6f, 0x6f, 0x6d, 0x76, 0x31, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
@ -4619,7 +4693,7 @@ func file_proto_room_v1_room_proto_rawDescGZIP() []byte {
return file_proto_room_v1_room_proto_rawDescData return file_proto_room_v1_room_proto_rawDescData
} }
var file_proto_room_v1_room_proto_msgTypes = make([]protoimpl.MessageInfo, 57) var file_proto_room_v1_room_proto_msgTypes = make([]protoimpl.MessageInfo, 58)
var file_proto_room_v1_room_proto_goTypes = []any{ var file_proto_room_v1_room_proto_goTypes = []any{
(*RequestMeta)(nil), // 0: hyapp.room.v1.RequestMeta (*RequestMeta)(nil), // 0: hyapp.room.v1.RequestMeta
(*CommandResult)(nil), // 1: hyapp.room.v1.CommandResult (*CommandResult)(nil), // 1: hyapp.room.v1.CommandResult
@ -4669,21 +4743,22 @@ var file_proto_room_v1_room_proto_goTypes = []any{
(*VerifyRoomPresenceResponse)(nil), // 45: hyapp.room.v1.VerifyRoomPresenceResponse (*VerifyRoomPresenceResponse)(nil), // 45: hyapp.room.v1.VerifyRoomPresenceResponse
(*ListRoomsRequest)(nil), // 46: hyapp.room.v1.ListRoomsRequest (*ListRoomsRequest)(nil), // 46: hyapp.room.v1.ListRoomsRequest
(*ListRoomFeedsRequest)(nil), // 47: hyapp.room.v1.ListRoomFeedsRequest (*ListRoomFeedsRequest)(nil), // 47: hyapp.room.v1.ListRoomFeedsRequest
(*RoomListItem)(nil), // 48: hyapp.room.v1.RoomListItem (*RoomFeedRelatedUser)(nil), // 48: hyapp.room.v1.RoomFeedRelatedUser
(*ListRoomsResponse)(nil), // 49: hyapp.room.v1.ListRoomsResponse (*RoomListItem)(nil), // 49: hyapp.room.v1.RoomListItem
(*GetMyRoomRequest)(nil), // 50: hyapp.room.v1.GetMyRoomRequest (*ListRoomsResponse)(nil), // 50: hyapp.room.v1.ListRoomsResponse
(*GetMyRoomResponse)(nil), // 51: hyapp.room.v1.GetMyRoomResponse (*GetMyRoomRequest)(nil), // 51: hyapp.room.v1.GetMyRoomRequest
(*GetCurrentRoomRequest)(nil), // 52: hyapp.room.v1.GetCurrentRoomRequest (*GetMyRoomResponse)(nil), // 52: hyapp.room.v1.GetMyRoomResponse
(*GetCurrentRoomResponse)(nil), // 53: hyapp.room.v1.GetCurrentRoomResponse (*GetCurrentRoomRequest)(nil), // 53: hyapp.room.v1.GetCurrentRoomRequest
(*GetRoomSnapshotRequest)(nil), // 54: hyapp.room.v1.GetRoomSnapshotRequest (*GetCurrentRoomResponse)(nil), // 54: hyapp.room.v1.GetCurrentRoomResponse
(*GetRoomSnapshotResponse)(nil), // 55: hyapp.room.v1.GetRoomSnapshotResponse (*GetRoomSnapshotRequest)(nil), // 55: hyapp.room.v1.GetRoomSnapshotRequest
nil, // 56: hyapp.room.v1.RoomSnapshot.RoomExtEntry (*GetRoomSnapshotResponse)(nil), // 56: hyapp.room.v1.GetRoomSnapshotResponse
nil, // 57: hyapp.room.v1.RoomSnapshot.RoomExtEntry
} }
var file_proto_room_v1_room_proto_depIdxs = []int32{ var file_proto_room_v1_room_proto_depIdxs = []int32{
3, // 0: hyapp.room.v1.RoomSnapshot.mic_seats:type_name -> hyapp.room.v1.SeatState 3, // 0: hyapp.room.v1.RoomSnapshot.mic_seats:type_name -> hyapp.room.v1.SeatState
2, // 1: hyapp.room.v1.RoomSnapshot.online_users:type_name -> hyapp.room.v1.RoomUser 2, // 1: hyapp.room.v1.RoomSnapshot.online_users:type_name -> hyapp.room.v1.RoomUser
4, // 2: hyapp.room.v1.RoomSnapshot.gift_rank:type_name -> hyapp.room.v1.RankItem 4, // 2: hyapp.room.v1.RoomSnapshot.gift_rank:type_name -> hyapp.room.v1.RankItem
56, // 3: hyapp.room.v1.RoomSnapshot.room_ext:type_name -> hyapp.room.v1.RoomSnapshot.RoomExtEntry 57, // 3: hyapp.room.v1.RoomSnapshot.room_ext:type_name -> hyapp.room.v1.RoomSnapshot.RoomExtEntry
0, // 4: hyapp.room.v1.CreateRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta 0, // 4: hyapp.room.v1.CreateRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta
1, // 5: hyapp.room.v1.CreateRoomResponse.result:type_name -> hyapp.room.v1.CommandResult 1, // 5: hyapp.room.v1.CreateRoomResponse.result:type_name -> hyapp.room.v1.CommandResult
5, // 6: hyapp.room.v1.CreateRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot 5, // 6: hyapp.room.v1.CreateRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot
@ -4743,67 +4818,68 @@ var file_proto_room_v1_room_proto_depIdxs = []int32{
5, // 60: hyapp.room.v1.SendGiftResponse.room:type_name -> hyapp.room.v1.RoomSnapshot 5, // 60: hyapp.room.v1.SendGiftResponse.room:type_name -> hyapp.room.v1.RoomSnapshot
0, // 61: hyapp.room.v1.ListRoomsRequest.meta:type_name -> hyapp.room.v1.RequestMeta 0, // 61: hyapp.room.v1.ListRoomsRequest.meta:type_name -> hyapp.room.v1.RequestMeta
0, // 62: hyapp.room.v1.ListRoomFeedsRequest.meta:type_name -> hyapp.room.v1.RequestMeta 0, // 62: hyapp.room.v1.ListRoomFeedsRequest.meta:type_name -> hyapp.room.v1.RequestMeta
48, // 63: hyapp.room.v1.ListRoomsResponse.rooms:type_name -> hyapp.room.v1.RoomListItem 48, // 63: hyapp.room.v1.ListRoomFeedsRequest.related_users:type_name -> hyapp.room.v1.RoomFeedRelatedUser
0, // 64: hyapp.room.v1.GetMyRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta 49, // 64: hyapp.room.v1.ListRoomsResponse.rooms:type_name -> hyapp.room.v1.RoomListItem
48, // 65: hyapp.room.v1.GetMyRoomResponse.room:type_name -> hyapp.room.v1.RoomListItem 0, // 65: hyapp.room.v1.GetMyRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta
0, // 66: hyapp.room.v1.GetCurrentRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta 49, // 66: hyapp.room.v1.GetMyRoomResponse.room:type_name -> hyapp.room.v1.RoomListItem
0, // 67: hyapp.room.v1.GetRoomSnapshotRequest.meta:type_name -> hyapp.room.v1.RequestMeta 0, // 67: hyapp.room.v1.GetCurrentRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta
5, // 68: hyapp.room.v1.GetRoomSnapshotResponse.room:type_name -> hyapp.room.v1.RoomSnapshot 0, // 68: hyapp.room.v1.GetRoomSnapshotRequest.meta:type_name -> hyapp.room.v1.RequestMeta
6, // 69: hyapp.room.v1.RoomCommandService.CreateRoom:input_type -> hyapp.room.v1.CreateRoomRequest 5, // 69: hyapp.room.v1.GetRoomSnapshotResponse.room:type_name -> hyapp.room.v1.RoomSnapshot
8, // 70: hyapp.room.v1.RoomCommandService.JoinRoom:input_type -> hyapp.room.v1.JoinRoomRequest 6, // 70: hyapp.room.v1.RoomCommandService.CreateRoom:input_type -> hyapp.room.v1.CreateRoomRequest
10, // 71: hyapp.room.v1.RoomCommandService.RoomHeartbeat:input_type -> hyapp.room.v1.RoomHeartbeatRequest 8, // 71: hyapp.room.v1.RoomCommandService.JoinRoom:input_type -> hyapp.room.v1.JoinRoomRequest
12, // 72: hyapp.room.v1.RoomCommandService.LeaveRoom:input_type -> hyapp.room.v1.LeaveRoomRequest 10, // 72: hyapp.room.v1.RoomCommandService.RoomHeartbeat:input_type -> hyapp.room.v1.RoomHeartbeatRequest
14, // 73: hyapp.room.v1.RoomCommandService.CloseRoom:input_type -> hyapp.room.v1.CloseRoomRequest 12, // 73: hyapp.room.v1.RoomCommandService.LeaveRoom:input_type -> hyapp.room.v1.LeaveRoomRequest
16, // 74: hyapp.room.v1.RoomCommandService.MicUp:input_type -> hyapp.room.v1.MicUpRequest 14, // 74: hyapp.room.v1.RoomCommandService.CloseRoom:input_type -> hyapp.room.v1.CloseRoomRequest
18, // 75: hyapp.room.v1.RoomCommandService.MicDown:input_type -> hyapp.room.v1.MicDownRequest 16, // 75: hyapp.room.v1.RoomCommandService.MicUp:input_type -> hyapp.room.v1.MicUpRequest
20, // 76: hyapp.room.v1.RoomCommandService.ChangeMicSeat:input_type -> hyapp.room.v1.ChangeMicSeatRequest 18, // 76: hyapp.room.v1.RoomCommandService.MicDown:input_type -> hyapp.room.v1.MicDownRequest
22, // 77: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:input_type -> hyapp.room.v1.ConfirmMicPublishingRequest 20, // 77: hyapp.room.v1.RoomCommandService.ChangeMicSeat:input_type -> hyapp.room.v1.ChangeMicSeatRequest
24, // 78: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:input_type -> hyapp.room.v1.ApplyRTCEventRequest 22, // 78: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:input_type -> hyapp.room.v1.ConfirmMicPublishingRequest
26, // 79: hyapp.room.v1.RoomCommandService.SetMicSeatLock:input_type -> hyapp.room.v1.SetMicSeatLockRequest 24, // 79: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:input_type -> hyapp.room.v1.ApplyRTCEventRequest
28, // 80: hyapp.room.v1.RoomCommandService.SetChatEnabled:input_type -> hyapp.room.v1.SetChatEnabledRequest 26, // 80: hyapp.room.v1.RoomCommandService.SetMicSeatLock:input_type -> hyapp.room.v1.SetMicSeatLockRequest
30, // 81: hyapp.room.v1.RoomCommandService.SetRoomAdmin:input_type -> hyapp.room.v1.SetRoomAdminRequest 28, // 81: hyapp.room.v1.RoomCommandService.SetChatEnabled:input_type -> hyapp.room.v1.SetChatEnabledRequest
32, // 82: hyapp.room.v1.RoomCommandService.TransferRoomHost:input_type -> hyapp.room.v1.TransferRoomHostRequest 30, // 82: hyapp.room.v1.RoomCommandService.SetRoomAdmin:input_type -> hyapp.room.v1.SetRoomAdminRequest
34, // 83: hyapp.room.v1.RoomCommandService.MuteUser:input_type -> hyapp.room.v1.MuteUserRequest 32, // 83: hyapp.room.v1.RoomCommandService.TransferRoomHost:input_type -> hyapp.room.v1.TransferRoomHostRequest
36, // 84: hyapp.room.v1.RoomCommandService.KickUser:input_type -> hyapp.room.v1.KickUserRequest 34, // 84: hyapp.room.v1.RoomCommandService.MuteUser:input_type -> hyapp.room.v1.MuteUserRequest
38, // 85: hyapp.room.v1.RoomCommandService.UnbanUser:input_type -> hyapp.room.v1.UnbanUserRequest 36, // 85: hyapp.room.v1.RoomCommandService.KickUser:input_type -> hyapp.room.v1.KickUserRequest
40, // 86: hyapp.room.v1.RoomCommandService.SendGift:input_type -> hyapp.room.v1.SendGiftRequest 38, // 86: hyapp.room.v1.RoomCommandService.UnbanUser:input_type -> hyapp.room.v1.UnbanUserRequest
42, // 87: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:input_type -> hyapp.room.v1.CheckSpeakPermissionRequest 40, // 87: hyapp.room.v1.RoomCommandService.SendGift:input_type -> hyapp.room.v1.SendGiftRequest
44, // 88: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:input_type -> hyapp.room.v1.VerifyRoomPresenceRequest 42, // 88: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:input_type -> hyapp.room.v1.CheckSpeakPermissionRequest
46, // 89: hyapp.room.v1.RoomQueryService.ListRooms:input_type -> hyapp.room.v1.ListRoomsRequest 44, // 89: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:input_type -> hyapp.room.v1.VerifyRoomPresenceRequest
47, // 90: hyapp.room.v1.RoomQueryService.ListRoomFeeds:input_type -> hyapp.room.v1.ListRoomFeedsRequest 46, // 90: hyapp.room.v1.RoomQueryService.ListRooms:input_type -> hyapp.room.v1.ListRoomsRequest
50, // 91: hyapp.room.v1.RoomQueryService.GetMyRoom:input_type -> hyapp.room.v1.GetMyRoomRequest 47, // 91: hyapp.room.v1.RoomQueryService.ListRoomFeeds:input_type -> hyapp.room.v1.ListRoomFeedsRequest
52, // 92: hyapp.room.v1.RoomQueryService.GetCurrentRoom:input_type -> hyapp.room.v1.GetCurrentRoomRequest 51, // 92: hyapp.room.v1.RoomQueryService.GetMyRoom:input_type -> hyapp.room.v1.GetMyRoomRequest
54, // 93: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:input_type -> hyapp.room.v1.GetRoomSnapshotRequest 53, // 93: hyapp.room.v1.RoomQueryService.GetCurrentRoom:input_type -> hyapp.room.v1.GetCurrentRoomRequest
7, // 94: hyapp.room.v1.RoomCommandService.CreateRoom:output_type -> hyapp.room.v1.CreateRoomResponse 55, // 94: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:input_type -> hyapp.room.v1.GetRoomSnapshotRequest
9, // 95: hyapp.room.v1.RoomCommandService.JoinRoom:output_type -> hyapp.room.v1.JoinRoomResponse 7, // 95: hyapp.room.v1.RoomCommandService.CreateRoom:output_type -> hyapp.room.v1.CreateRoomResponse
11, // 96: hyapp.room.v1.RoomCommandService.RoomHeartbeat:output_type -> hyapp.room.v1.RoomHeartbeatResponse 9, // 96: hyapp.room.v1.RoomCommandService.JoinRoom:output_type -> hyapp.room.v1.JoinRoomResponse
13, // 97: hyapp.room.v1.RoomCommandService.LeaveRoom:output_type -> hyapp.room.v1.LeaveRoomResponse 11, // 97: hyapp.room.v1.RoomCommandService.RoomHeartbeat:output_type -> hyapp.room.v1.RoomHeartbeatResponse
15, // 98: hyapp.room.v1.RoomCommandService.CloseRoom:output_type -> hyapp.room.v1.CloseRoomResponse 13, // 98: hyapp.room.v1.RoomCommandService.LeaveRoom:output_type -> hyapp.room.v1.LeaveRoomResponse
17, // 99: hyapp.room.v1.RoomCommandService.MicUp:output_type -> hyapp.room.v1.MicUpResponse 15, // 99: hyapp.room.v1.RoomCommandService.CloseRoom:output_type -> hyapp.room.v1.CloseRoomResponse
19, // 100: hyapp.room.v1.RoomCommandService.MicDown:output_type -> hyapp.room.v1.MicDownResponse 17, // 100: hyapp.room.v1.RoomCommandService.MicUp:output_type -> hyapp.room.v1.MicUpResponse
21, // 101: hyapp.room.v1.RoomCommandService.ChangeMicSeat:output_type -> hyapp.room.v1.ChangeMicSeatResponse 19, // 101: hyapp.room.v1.RoomCommandService.MicDown:output_type -> hyapp.room.v1.MicDownResponse
23, // 102: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:output_type -> hyapp.room.v1.ConfirmMicPublishingResponse 21, // 102: hyapp.room.v1.RoomCommandService.ChangeMicSeat:output_type -> hyapp.room.v1.ChangeMicSeatResponse
25, // 103: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:output_type -> hyapp.room.v1.ApplyRTCEventResponse 23, // 103: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:output_type -> hyapp.room.v1.ConfirmMicPublishingResponse
27, // 104: hyapp.room.v1.RoomCommandService.SetMicSeatLock:output_type -> hyapp.room.v1.SetMicSeatLockResponse 25, // 104: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:output_type -> hyapp.room.v1.ApplyRTCEventResponse
29, // 105: hyapp.room.v1.RoomCommandService.SetChatEnabled:output_type -> hyapp.room.v1.SetChatEnabledResponse 27, // 105: hyapp.room.v1.RoomCommandService.SetMicSeatLock:output_type -> hyapp.room.v1.SetMicSeatLockResponse
31, // 106: hyapp.room.v1.RoomCommandService.SetRoomAdmin:output_type -> hyapp.room.v1.SetRoomAdminResponse 29, // 106: hyapp.room.v1.RoomCommandService.SetChatEnabled:output_type -> hyapp.room.v1.SetChatEnabledResponse
33, // 107: hyapp.room.v1.RoomCommandService.TransferRoomHost:output_type -> hyapp.room.v1.TransferRoomHostResponse 31, // 107: hyapp.room.v1.RoomCommandService.SetRoomAdmin:output_type -> hyapp.room.v1.SetRoomAdminResponse
35, // 108: hyapp.room.v1.RoomCommandService.MuteUser:output_type -> hyapp.room.v1.MuteUserResponse 33, // 108: hyapp.room.v1.RoomCommandService.TransferRoomHost:output_type -> hyapp.room.v1.TransferRoomHostResponse
37, // 109: hyapp.room.v1.RoomCommandService.KickUser:output_type -> hyapp.room.v1.KickUserResponse 35, // 109: hyapp.room.v1.RoomCommandService.MuteUser:output_type -> hyapp.room.v1.MuteUserResponse
39, // 110: hyapp.room.v1.RoomCommandService.UnbanUser:output_type -> hyapp.room.v1.UnbanUserResponse 37, // 110: hyapp.room.v1.RoomCommandService.KickUser:output_type -> hyapp.room.v1.KickUserResponse
41, // 111: hyapp.room.v1.RoomCommandService.SendGift:output_type -> hyapp.room.v1.SendGiftResponse 39, // 111: hyapp.room.v1.RoomCommandService.UnbanUser:output_type -> hyapp.room.v1.UnbanUserResponse
43, // 112: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:output_type -> hyapp.room.v1.CheckSpeakPermissionResponse 41, // 112: hyapp.room.v1.RoomCommandService.SendGift:output_type -> hyapp.room.v1.SendGiftResponse
45, // 113: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:output_type -> hyapp.room.v1.VerifyRoomPresenceResponse 43, // 113: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:output_type -> hyapp.room.v1.CheckSpeakPermissionResponse
49, // 114: hyapp.room.v1.RoomQueryService.ListRooms:output_type -> hyapp.room.v1.ListRoomsResponse 45, // 114: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:output_type -> hyapp.room.v1.VerifyRoomPresenceResponse
49, // 115: hyapp.room.v1.RoomQueryService.ListRoomFeeds:output_type -> hyapp.room.v1.ListRoomsResponse 50, // 115: hyapp.room.v1.RoomQueryService.ListRooms:output_type -> hyapp.room.v1.ListRoomsResponse
51, // 116: hyapp.room.v1.RoomQueryService.GetMyRoom:output_type -> hyapp.room.v1.GetMyRoomResponse 50, // 116: hyapp.room.v1.RoomQueryService.ListRoomFeeds:output_type -> hyapp.room.v1.ListRoomsResponse
53, // 117: hyapp.room.v1.RoomQueryService.GetCurrentRoom:output_type -> hyapp.room.v1.GetCurrentRoomResponse 52, // 117: hyapp.room.v1.RoomQueryService.GetMyRoom:output_type -> hyapp.room.v1.GetMyRoomResponse
55, // 118: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:output_type -> hyapp.room.v1.GetRoomSnapshotResponse 54, // 118: hyapp.room.v1.RoomQueryService.GetCurrentRoom:output_type -> hyapp.room.v1.GetCurrentRoomResponse
94, // [94:119] is the sub-list for method output_type 56, // 119: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:output_type -> hyapp.room.v1.GetRoomSnapshotResponse
69, // [69:94] is the sub-list for method input_type 95, // [95:120] is the sub-list for method output_type
69, // [69:69] is the sub-list for extension type_name 70, // [70:95] is the sub-list for method input_type
69, // [69:69] is the sub-list for extension extendee 70, // [70:70] is the sub-list for extension type_name
0, // [0:69] is the sub-list for field type_name 70, // [70:70] is the sub-list for extension extendee
0, // [0:70] is the sub-list for field type_name
} }
func init() { file_proto_room_v1_room_proto_init() } func init() { file_proto_room_v1_room_proto_init() }
@ -4817,7 +4893,7 @@ func file_proto_room_v1_room_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proto_room_v1_room_proto_rawDesc, RawDescriptor: file_proto_room_v1_room_proto_rawDesc,
NumEnums: 0, NumEnums: 0,
NumMessages: 57, NumMessages: 58,
NumExtensions: 0, NumExtensions: 0,
NumServices: 3, NumServices: 3,
}, },

View File

@ -393,6 +393,14 @@ message ListRoomFeedsRequest {
string cursor = 5; string cursor = 5;
int32 limit = 6; int32 limit = 6;
string query = 7; string query = 7;
// related_users friend/following gateway user-service
repeated RoomFeedRelatedUser related_users = 8;
}
// RoomFeedRelatedUser Mine room-service /
message RoomFeedRelatedUser {
int64 user_id = 1;
int64 relation_updated_at_ms = 2;
} }
// RoomListItem Room Cell // RoomListItem Room Cell

File diff suppressed because it is too large Load Diff

View File

@ -254,6 +254,9 @@ message ListFollowingRequest {
int64 user_id = 2; int64 user_id = 2;
int32 page = 3; int32 page = 3;
int32 page_size = 4; int32 page_size = 4;
// cursor_updated_at_ms/cursor_user_id HTTP 使 page/page_size
int64 cursor_updated_at_ms = 5;
int64 cursor_user_id = 6;
} }
message ListFollowingResponse { message ListFollowingResponse {
@ -303,6 +306,9 @@ message ListFriendsRequest {
int64 user_id = 2; int64 user_id = 2;
int32 page = 3; int32 page = 3;
int32 page_size = 4; int32 page_size = 4;
// cursor_updated_at_ms/cursor_user_id HTTP 使 page/page_size
int64 cursor_updated_at_ms = 5;
int64 cursor_user_id = 6;
} }
message ListFriendsResponse { message ListFriendsResponse {

View File

@ -948,7 +948,7 @@ paths:
- rooms - rooms
summary: Mine 房间关系流 summary: Mine 房间关系流
operationId: listRoomFeeds operationId: listRoomFeeds
description: 查询 Mine 页下方 Visited/Friend/Following 房间流;该接口读取当前用户的房间 feed 索引,再返回房间卡片和 `im_group_id`,不作为公共房间发现列表。 description: 查询 Mine 页下方 Visited/Friend/Following 房间流;`visited` 读取 JoinRoom 写入的房间访问索引,`friend/following` 由 gateway 先读取 user-service 关系事实,再让 room-service 返回这些关系用户的 active 房间卡片和 `im_group_id`,不作为公共房间发现列表。
security: security:
- BearerAuth: [] - BearerAuth: []
parameters: parameters:
@ -972,7 +972,7 @@ paths:
format: int32 format: int32
responses: responses:
"200": "200":
description: 查询成功;`visited` 由 JoinRoom 成功写入,`friend/following` 依赖关系流读模型填充 description: 查询成功;`visited` 由 JoinRoom 成功写入,`friend/following` 同步使用 user-service 好友/关注关系,不依赖 Redis 缓存命中
schema: schema:
$ref: "#/definitions/RoomListEnvelope" $ref: "#/definitions/RoomListEnvelope"
"400": "400":

View File

@ -578,7 +578,7 @@ activity-service 额外需要:
推荐顺序: 推荐顺序:
1. 写 `docs/grpc错误模型.md` 或把本文件错误模型抽成独立文档。 1. 写 `docs/gRPC错误模型.md` 或把本文件错误模型抽成独立文档。
2. 扩展 `pkg/xerr`,支持 reason 和 transport 映射。 2. 扩展 `pkg/xerr`,支持 reason 和 transport 映射。
3. 新增 `api/proto/user/v1/auth.proto``user.proto` 3. 新增 `api/proto/user/v1/auth.proto``user.proto`
4. 补 `user-service` 的 app/service/domain/storage/transport 骨架。 4. 补 `user-service` 的 app/service/domain/storage/transport 骨架。

View File

@ -105,6 +105,13 @@ sequenceDiagram
计数更新和关系边写入必须在同一个 MySQL 事务中完成。`user_profile_stats` 是首屏计数来源,不能在 `/users/me/overview` 里实时扫关系表。 计数更新和关系边写入必须在同一个 MySQL 事务中完成。`user_profile_stats` 是首屏计数来源,不能在 `/users/me/overview` 里实时扫关系表。
## Room Feed Integration
- `GET /api/v1/rooms/feeds?tab=visited` 是房间访问历史,只由 JoinRoom 成功后写入 room-service 的房间 feed 索引;不要使用 `/users/{user_id}/visit` 用户主页访问记录填充。
- `GET /api/v1/rooms/feeds?tab=following` 由 gateway 调用 `ListFollowing` 读取关注用户,再把关系用户和 `followed_at_ms` 传给 room-service 查询 active 房间卡片。
- `GET /api/v1/rooms/feeds?tab=friend` 由 gateway 调用 `ListFriends` 读取好友用户,再把关系用户和 `friended_at_ms` 传给 room-service 查询 active 房间卡片。
- 好友/关注关系事实仍归 user-serviceroom-service 只拥有房间状态和房间列表卡片,不保存好友/关注关系作为事实。
## App Notes ## App Notes
- 好友申请成功后App 发送腾讯 IM 自定义消息给对方;消息里携带 `requester_user_id` 和申请状态展示参数。 - 好友申请成功后App 发送腾讯 IM 自定义消息给对方;消息里携带 `requester_user_id` 和申请状态展示参数。

View File

@ -26,6 +26,7 @@
sequenceDiagram sequenceDiagram
participant C as Client participant C as Client
participant G as gateway-service participant G as gateway-service
participant U as user-service
participant R as room-service participant R as room-service
participant IM as Tencent IM SDK participant IM as Tencent IM SDK
participant RTC as Tencent RTC SDK participant RTC as Tencent RTC SDK
@ -40,7 +41,11 @@ sequenceDiagram
G-->>C: has_room + room card + im_group_id G-->>C: has_room + room card + im_group_id
C->>G: GET /api/v1/rooms/feeds?tab=visited|friend|following C->>G: GET /api/v1/rooms/feeds?tab=visited|friend|following
G->>R: ListRoomFeeds(viewer region from user-service) opt tab=friend|following
G->>U: ListFriends/ListFollowing(cursor)
U-->>G: related user ids + relation times
end
G->>R: ListRoomFeeds(viewer region + related users)
R-->>G: room cards R-->>G: room cards
G-->>C: room cards + im_group_id G-->>C: room cards + im_group_id
@ -70,7 +75,7 @@ sequenceDiagram
| 1 | `GET /api/v1/im/usersig` | yes | 用户级 IM 登录票据,可在 App 启动后预取并按 TTL 复用 | | 1 | `GET /api/v1/im/usersig` | yes | 用户级 IM 登录票据,可在 App 启动后预取并按 TTL 复用 |
| 2 | Tencent IM SDK `login` | yes | 客户端先建立 IM 身份,房间群仍等 JoinRoom 成功后再进 | | 2 | Tencent IM SDK `login` | yes | 客户端先建立 IM 身份,房间群仍等 JoinRoom 成功后再进 |
| 3 | `GET /api/v1/rooms/me` | yes for Mine top card | 直接读取 owner 房间权威元数据,不依赖发现页投影 | | 3 | `GET /api/v1/rooms/me` | yes for Mine top card | 直接读取 owner 房间权威元数据,不依赖发现页投影 |
| 4 | `GET /api/v1/rooms/feeds?tab=visited\|friend\|following` | yes for Mine lists | Mine 列表 tab`visited` 由 JoinRoom 成功写入,`friend/following` 读取关系流读模型 | | 4 | `GET /api/v1/rooms/feeds?tab=visited\|friend\|following` | yes for Mine lists | Mine 列表 tab`visited` 由 JoinRoom 成功写入,`friend/following` 由 gateway 先查 user-service 关系,再让 room-service 返回这些关系用户的 active 房间卡片 |
| 5 | `POST /api/v1/rooms/join` | yes | 建立业务 presence并返回首屏房间数据和内嵌 RTC token | | 5 | `POST /api/v1/rooms/join` | yes | 建立业务 presence并返回首屏房间数据和内嵌 RTC token |
| 6 | Tencent IM SDK `joinGroup(im_group_id)` | yes | 必须在 JoinRoom 成功后执行IM 回调守卫会校验 room-service presence | | 6 | Tencent IM SDK `joinGroup(im_group_id)` | yes | 必须在 JoinRoom 成功后执行IM 回调守卫会校验 room-service presence |
| 7 | Tencent RTC SDK `enterRoom` | yes for voice room audio | 使用 JoinRoom 响应里的 `rtc.token` 进入真实音频频道旁听 | | 7 | Tencent RTC SDK `enterRoom` | yes for voice room audio | 使用 JoinRoom 响应里的 `rtc.token` 进入真实音频频道旁听 |
@ -78,6 +83,19 @@ sequenceDiagram
如果 JoinRoom 响应里的 `rtc.available=false`,客户端可以先展示房间页并重试 `POST /api/v1/rtc/token`RTC 失败不能回滚已经成功的业务 presence。 如果 JoinRoom 响应里的 `rtc.available=false`,客户端可以先展示房间页并重试 `POST /api/v1/rtc/token`RTC 失败不能回滚已经成功的业务 presence。
### Mine Feed Cache Boundary
当前 `friend/following` 不强制加 Redis。原因是关注、取关、同意好友和删除好友都要求列表立即反映关系事实首版直接读 user-service MySQL 关系表,再查 room-service active 房间卡片,正确性更直接。
后续可以加 Redis 的位置:
- `hot/new` 公共列表room-service 用 Redis ZSET 缓存排序 room_idmiss 后回源 `room_list_entries`
- 房间卡片短 TTL 缓存key 可按 `app_code + region_id + room_id`,但 MySQL `room_list_entries` 仍是事实来源。
- `friend/following` 关系 ID 短 TTL 缓存:只能放在 gateway 或 user-service 读优化层,`follow/unfollow/accept/delete` 成功后必须主动失效。
- `visited` room_id 列表缓存:可以由 JoinRoom 成功后刷新Redis 写失败不能影响 JoinRoom。
不建议首版缓存 `friend/following` 完整房间卡片列表。这个列表同时依赖 user-service 关系事实、room-service 房间 active 状态、区域过滤和搜索条件,失效面较大;先保持同步查询,等 QPS 或延迟数据证明需要缓存再加。
## Core Interfaces ## Core Interfaces
### Join Room ### Join Room

View File

@ -1363,11 +1363,15 @@ func TestListRoomFeedsSupportsMineFeedTabs(t *testing.T) {
for _, tab := range []string{"visited", "friend", "following"} { for _, tab := range []string{"visited", "friend", "following"} {
t.Run(tab, func(t *testing.T) { t.Run(tab, func(t *testing.T) {
profileClient := &fakeUserProfileClient{regionID: 1001} profileClient := &fakeUserProfileClient{regionID: 1001}
socialClient := &fakeUserSocialClient{}
queryClient := &fakeRoomQueryClient{feedsResp: &roomv1.ListRoomsResponse{ queryClient := &fakeRoomQueryClient{feedsResp: &roomv1.ListRoomsResponse{
Rooms: []*roomv1.RoomListItem{{RoomId: "room-feed", VisibleRegionId: 1001, Heat: 7}}, Rooms: []*roomv1.RoomListItem{{RoomId: "room-feed", VisibleRegionId: 1001, Heat: 7}},
}} }}
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient) handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient)
handler.SetRoomQueryClient(queryClient) handler.SetRoomQueryClient(queryClient)
if tab != "visited" {
handler.SetUserSocialClient(socialClient)
}
router := handler.Routes(auth.NewVerifier("secret")) router := handler.Routes(auth.NewVerifier("secret"))
request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms/feeds?tab="+tab+"&limit=3", nil) request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms/feeds?tab="+tab+"&limit=3", nil)
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
@ -1382,10 +1386,60 @@ func TestListRoomFeedsSupportsMineFeedTabs(t *testing.T) {
if queryClient.lastFeeds == nil || queryClient.lastFeeds.GetTab() != tab || queryClient.lastFeeds.GetViewerUserId() != 42 || queryClient.lastFeeds.GetVisibleRegionId() != 1001 { if queryClient.lastFeeds == nil || queryClient.lastFeeds.GetTab() != tab || queryClient.lastFeeds.GetViewerUserId() != 42 || queryClient.lastFeeds.GetVisibleRegionId() != 1001 {
t.Fatalf("feed tab was not forwarded with authenticated user and region: %+v", queryClient.lastFeeds) t.Fatalf("feed tab was not forwarded with authenticated user and region: %+v", queryClient.lastFeeds)
} }
switch tab {
case "visited":
if len(queryClient.lastFeeds.GetRelatedUsers()) != 0 || socialClient.lastFollowing != nil || socialClient.lastFriends != nil {
t.Fatalf("visited feed must not load social relations: feeds=%+v social=%+v/%+v", queryClient.lastFeeds, socialClient.lastFollowing, socialClient.lastFriends)
}
case "following":
if socialClient.lastFollowing == nil || socialClient.lastFollowing.GetUserId() != 42 {
t.Fatalf("following feed must load following relation facts: %+v", socialClient.lastFollowing)
}
if got := queryClient.lastFeeds.GetRelatedUsers(); len(got) != 1 || got[0].GetUserId() != 10002 || got[0].GetRelationUpdatedAtMs() != 8000 {
t.Fatalf("following related users mismatch: %+v", got)
}
case "friend":
if socialClient.lastFriends == nil || socialClient.lastFriends.GetUserId() != 42 {
t.Fatalf("friend feed must load friend relation facts: %+v", socialClient.lastFriends)
}
if got := queryClient.lastFeeds.GetRelatedUsers(); len(got) != 1 || got[0].GetUserId() != 10002 || got[0].GetRelationUpdatedAtMs() != 10000 {
t.Fatalf("friend related users mismatch: %+v", got)
}
}
}) })
} }
} }
func TestListRoomFeedsRelationCursorContinuesUserSocialScan(t *testing.T) {
profileClient := &fakeUserProfileClient{regionID: 1001}
socialClient := &fakeUserSocialClient{}
queryClient := &fakeRoomQueryClient{}
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient)
handler.SetRoomQueryClient(queryClient)
handler.SetUserSocialClient(socialClient)
router := handler.Routes(auth.NewVerifier("secret"))
cursorPayload, _ := json.Marshal(roomFeedRelationCursor{
Tab: "following",
Query: "music",
UpdatedAtMS: 8000,
SubjectUserID: 10002,
RoomID: "room-feed",
})
cursor := base64.RawURLEncoding.EncodeToString(cursorPayload)
request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms/feeds?tab=following&q=music&cursor="+cursor, nil)
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
}
if socialClient.lastFollowing == nil || socialClient.lastFollowing.GetCursorUpdatedAtMs() != 8000 || socialClient.lastFollowing.GetCursorUserId() != 10002 {
t.Fatalf("following cursor was not propagated to user-service: %+v", socialClient.lastFollowing)
}
}
func TestGetMyRoomUsesAuthenticatedOwnerAndReturnsIMGroup(t *testing.T) { func TestGetMyRoomUsesAuthenticatedOwnerAndReturnsIMGroup(t *testing.T) {
queryClient := &fakeRoomQueryClient{myRoomResp: &roomv1.GetMyRoomResponse{ queryClient := &fakeRoomQueryClient{myRoomResp: &roomv1.GetMyRoomResponse{
HasRoom: true, HasRoom: true,

View File

@ -1,6 +1,8 @@
package http package http
import ( import (
"encoding/base64"
"encoding/json"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -9,9 +11,23 @@ import (
userv1 "hyapp.local/api/proto/user/v1" userv1 "hyapp.local/api/proto/user/v1"
"hyapp/pkg/appcode" "hyapp/pkg/appcode"
"hyapp/pkg/roomid" "hyapp/pkg/roomid"
"hyapp/pkg/xerr"
"hyapp/services/gateway-service/internal/auth" "hyapp/services/gateway-service/internal/auth"
) )
const (
defaultRoomFeedRelationPageSize = 100
maxRoomFeedRelationScanCount = 500
)
type roomFeedRelationCursor struct {
Tab string `json:"tab"`
Query string `json:"query,omitempty"`
UpdatedAtMS int64 `json:"updated_at_ms,omitempty"`
SubjectUserID int64 `json:"subject_user_id,omitempty"`
RoomID string `json:"room_id"`
}
// listRooms 按当前登录用户的服务端 region_id 查询房间发现列表。 // listRooms 按当前登录用户的服务端 region_id 查询房间发现列表。
func (h *Handler) listRooms(writer http.ResponseWriter, request *http.Request) { func (h *Handler) listRooms(writer http.ResponseWriter, request *http.Request) {
if h.userProfileClient == nil || h.roomQueryClient == nil { if h.userProfileClient == nil || h.roomQueryClient == nil {
@ -87,6 +103,16 @@ func (h *Handler) listRoomFeeds(writer http.ResponseWriter, request *http.Reques
return return
} }
relatedUsers, ok, err := h.roomFeedRelatedUsers(request, tab, roomListQuery(request), request.URL.Query().Get("cursor"))
if !ok {
writeError(writer, request, http.StatusBadRequest, codeInvalidArgument, "invalid argument")
return
}
if err != nil {
writeRPCError(writer, request, err)
return
}
resp, err := h.roomQueryClient.ListRoomFeeds(request.Context(), &roomv1.ListRoomFeedsRequest{ resp, err := h.roomQueryClient.ListRoomFeeds(request.Context(), &roomv1.ListRoomFeedsRequest{
Meta: meta(request, "", ""), Meta: meta(request, "", ""),
ViewerUserId: viewerUserID, ViewerUserId: viewerUserID,
@ -95,6 +121,7 @@ func (h *Handler) listRoomFeeds(writer http.ResponseWriter, request *http.Reques
Cursor: request.URL.Query().Get("cursor"), Cursor: request.URL.Query().Get("cursor"),
Limit: limit, Limit: limit,
Query: roomListQuery(request), Query: roomListQuery(request),
RelatedUsers: relatedUsers,
}) })
if err != nil { if err != nil {
writeRPCError(writer, request, err) writeRPCError(writer, request, err)
@ -103,6 +130,105 @@ func (h *Handler) listRoomFeeds(writer http.ResponseWriter, request *http.Reques
writeOK(writer, request, roomListDataFromProto(resp)) writeOK(writer, request, roomListDataFromProto(resp))
} }
func (h *Handler) roomFeedRelatedUsers(request *http.Request, tab string, query string, rawCursor string) ([]*roomv1.RoomFeedRelatedUser, bool, error) {
if tab == "visited" {
return nil, true, nil
}
if h.userSocialClient == nil {
// friend/following 必须以 user-service 关系事实为准,不能退回旧 room feed 投影。
return nil, true, xerr.New(xerr.Unavailable, "user social client is not configured")
}
cursor, ok := decodeRoomFeedRelationCursor(tab, query, rawCursor)
if !ok {
return nil, false, nil
}
viewerUserID := auth.UserIDFromContext(request.Context())
relatedUsers := make([]*roomv1.RoomFeedRelatedUser, 0, defaultRoomFeedRelationPageSize)
for len(relatedUsers) < maxRoomFeedRelationScanCount {
batch, nextCursor, err := h.roomFeedRelatedUserBatch(request, tab, viewerUserID, cursor)
if err != nil {
return nil, true, err
}
relatedUsers = append(relatedUsers, batch...)
if len(batch) < defaultRoomFeedRelationPageSize {
break
}
cursor = nextCursor
}
return relatedUsers, true, nil
}
func (h *Handler) roomFeedRelatedUserBatch(request *http.Request, tab string, viewerUserID int64, cursor roomFeedRelationCursor) ([]*roomv1.RoomFeedRelatedUser, roomFeedRelationCursor, error) {
switch tab {
case "following":
resp, err := h.userSocialClient.ListFollowing(request.Context(), &userv1.ListFollowingRequest{
Meta: authRequestMeta(request, ""),
UserId: viewerUserID,
Page: 1,
PageSize: defaultRoomFeedRelationPageSize,
CursorUpdatedAtMs: cursor.UpdatedAtMS,
CursorUserId: cursor.SubjectUserID,
})
if err != nil {
return nil, cursor, err
}
relatedUsers := make([]*roomv1.RoomFeedRelatedUser, 0, len(resp.GetRecords()))
for _, record := range resp.GetRecords() {
relatedUsers = append(relatedUsers, &roomv1.RoomFeedRelatedUser{
UserId: record.GetFolloweeUserId(),
RelationUpdatedAtMs: record.GetFollowedAtMs(),
})
cursor.UpdatedAtMS = record.GetFollowedAtMs()
cursor.SubjectUserID = record.GetFolloweeUserId()
}
return relatedUsers, cursor, nil
case "friend":
resp, err := h.userSocialClient.ListFriends(request.Context(), &userv1.ListFriendsRequest{
Meta: authRequestMeta(request, ""),
UserId: viewerUserID,
Page: 1,
PageSize: defaultRoomFeedRelationPageSize,
CursorUpdatedAtMs: cursor.UpdatedAtMS,
CursorUserId: cursor.SubjectUserID,
})
if err != nil {
return nil, cursor, err
}
relatedUsers := make([]*roomv1.RoomFeedRelatedUser, 0, len(resp.GetRecords()))
for _, record := range resp.GetRecords() {
relatedUsers = append(relatedUsers, &roomv1.RoomFeedRelatedUser{
UserId: record.GetFriendUserId(),
RelationUpdatedAtMs: record.GetFriendedAtMs(),
})
cursor.UpdatedAtMS = record.GetFriendedAtMs()
cursor.SubjectUserID = record.GetFriendUserId()
}
return relatedUsers, cursor, nil
default:
return nil, cursor, xerr.New(xerr.InvalidArgument, "tab is invalid")
}
}
func decodeRoomFeedRelationCursor(tab string, query string, raw string) (roomFeedRelationCursor, bool) {
raw = strings.TrimSpace(raw)
if raw == "" {
return roomFeedRelationCursor{Tab: tab, Query: query}, true
}
payload, err := base64.RawURLEncoding.DecodeString(raw)
if err != nil {
return roomFeedRelationCursor{}, false
}
var cursor roomFeedRelationCursor
if err := json.Unmarshal(payload, &cursor); err != nil {
return roomFeedRelationCursor{}, false
}
if cursor.Tab != tab || cursor.Query != query || cursor.RoomID == "" || cursor.UpdatedAtMS <= 0 || cursor.SubjectUserID <= 0 {
return roomFeedRelationCursor{}, false
}
return cursor, true
}
func parsePublicRoomListTab(raw string) (string, bool) { func parsePublicRoomListTab(raw string) (string, bool) {
switch strings.ToLower(strings.TrimSpace(raw)) { switch strings.ToLower(strings.TrimSpace(raw)) {
case "", "hot": case "", "hot":

View File

@ -27,12 +27,13 @@ const (
// roomListCursor 是服务端生成的不透明翻页游标。 // roomListCursor 是服务端生成的不透明翻页游标。
// 客户端只能原样传回不能依赖内部字段tab 写入游标用于拒绝跨 tab 复用。 // 客户端只能原样传回不能依赖内部字段tab 写入游标用于拒绝跨 tab 复用。
type roomListCursor struct { type roomListCursor struct {
Tab string `json:"tab"` Tab string `json:"tab"`
Query string `json:"query,omitempty"` Query string `json:"query,omitempty"`
SortScore int64 `json:"sort_score,omitempty"` SortScore int64 `json:"sort_score,omitempty"`
CreatedAtMS int64 `json:"created_at_ms,omitempty"` CreatedAtMS int64 `json:"created_at_ms,omitempty"`
UpdatedAtMS int64 `json:"updated_at_ms,omitempty"` UpdatedAtMS int64 `json:"updated_at_ms,omitempty"`
RoomID string `json:"room_id"` SubjectUserID int64 `json:"subject_user_id,omitempty"`
RoomID string `json:"room_id"`
} }
// ListRooms 查询当前用户区域内的公共房间发现列表读模型。 // ListRooms 查询当前用户区域内的公共房间发现列表读模型。
@ -99,17 +100,35 @@ func (s *Service) ListRoomFeeds(ctx context.Context, req *roomv1.ListRoomFeedsRe
if err != nil { if err != nil {
return nil, err return nil, err
} }
if tab != roomListTabVisited && strings.TrimSpace(req.GetCursor()) != "" && cursor.SubjectUserID <= 0 {
return nil, xerr.New(xerr.InvalidArgument, "cursor is invalid")
}
entries, err := s.repository.ListRoomUserFeedEntries(ctx, RoomUserFeedQuery{ var entries []RoomListEntry
AppCode: appcode.FromContext(ctx), if tab == roomListTabVisited {
UserID: viewerUserID, entries, err = s.repository.ListRoomUserFeedEntries(ctx, RoomUserFeedQuery{
FeedType: tab, AppCode: appcode.FromContext(ctx),
VisibleRegionID: normalizeVisibleRegionID(req.GetVisibleRegionId()), UserID: viewerUserID,
Query: query, FeedType: tab,
Limit: limit + 1, VisibleRegionID: normalizeVisibleRegionID(req.GetVisibleRegionId()),
CursorUpdatedAtMS: cursor.UpdatedAtMS, Query: query,
CursorRoomID: cursor.RoomID, Limit: limit + 1,
}) CursorUpdatedAtMS: cursor.UpdatedAtMS,
CursorRoomID: cursor.RoomID,
})
} else {
// friend/following 的关系事实属于 user-serviceroom-service 只根据 gateway 传入的一页关系用户查询 active 房间卡片。
entries, err = s.repository.ListRoomRelatedFeedEntries(ctx, RoomRelatedFeedQuery{
AppCode: appcode.FromContext(ctx),
VisibleRegionID: normalizeVisibleRegionID(req.GetVisibleRegionId()),
RelatedUsers: roomFeedRelatedUsersFromProto(req.GetRelatedUsers()),
Query: query,
Limit: limit + 1,
CursorUpdatedAtMS: cursor.UpdatedAtMS,
CursorSubjectUserID: cursor.SubjectUserID,
CursorRoomID: cursor.RoomID,
})
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -167,6 +186,20 @@ func roomListResponseFromEntries(tab string, query string, entries []RoomListEnt
} }
} }
func roomFeedRelatedUsersFromProto(items []*roomv1.RoomFeedRelatedUser) []RoomFeedRelatedUser {
relatedUsers := make([]RoomFeedRelatedUser, 0, len(items))
for _, item := range items {
if item == nil || item.GetUserId() <= 0 || item.GetRelationUpdatedAtMs() <= 0 {
continue
}
relatedUsers = append(relatedUsers, RoomFeedRelatedUser{
UserID: item.GetUserId(),
RelationUpdatedAtMS: item.GetRelationUpdatedAtMs(),
})
}
return relatedUsers
}
// projectRoomListBestEffort 根据最新快照刷新房间列表投影。 // projectRoomListBestEffort 根据最新快照刷新房间列表投影。
func (s *Service) projectRoomListBestEffort(ctx context.Context, snapshot *roomv1.RoomSnapshot) { func (s *Service) projectRoomListBestEffort(ctx context.Context, snapshot *roomv1.RoomSnapshot) {
if snapshot == nil || snapshot.GetRoomId() == "" { if snapshot == nil || snapshot.GetRoomId() == "" {
@ -320,12 +353,13 @@ func decodeRoomListCursor(tab string, query string, encoded string) (roomListCur
// cursor 不暴露契约语义,后续调整排序字段时可以只兼容服务端解析逻辑。 // cursor 不暴露契约语义,后续调整排序字段时可以只兼容服务端解析逻辑。
func encodeRoomListCursor(tab string, query string, entry RoomListEntry) string { func encodeRoomListCursor(tab string, query string, entry RoomListEntry) string {
cursor := roomListCursor{ cursor := roomListCursor{
Tab: tab, Tab: tab,
Query: query, Query: query,
SortScore: entry.SortScore, SortScore: entry.SortScore,
CreatedAtMS: entry.CreatedAtMS, CreatedAtMS: entry.CreatedAtMS,
UpdatedAtMS: entry.UpdatedAtMS, UpdatedAtMS: entry.UpdatedAtMS,
RoomID: entry.RoomID, SubjectUserID: entry.FeedSubjectUserID,
RoomID: entry.RoomID,
} }
payload, err := json.Marshal(cursor) payload, err := json.Marshal(cursor)

View File

@ -113,6 +113,8 @@ type RoomListEntry struct {
CreatedAtMS int64 CreatedAtMS int64
// UpdatedAtMS 是投影最后更新时间。 // UpdatedAtMS 是投影最后更新时间。
UpdatedAtMS int64 UpdatedAtMS int64
// FeedSubjectUserID 是关系房间流命中的好友/关注用户;只用于生成稳定 cursor不出现在 HTTP 卡片里。
FeedSubjectUserID int64
} }
// RoomPresence 是当前用户可恢复房间的轻量读模型。 // RoomPresence 是当前用户可恢复房间的轻量读模型。
@ -201,6 +203,35 @@ type RoomUserFeedQuery struct {
CursorRoomID string CursorRoomID string
} }
// RoomFeedRelatedUser 是 gateway 从 user-service 读取到的关系事实快照。
// room-service 只使用它做 active 房间卡片查询和排序,不保存好友/关注关系。
type RoomFeedRelatedUser struct {
// UserID 是好友或关注对象的用户 ID。
UserID int64
// RelationUpdatedAtMS 是该关系的排序时间followed_at_ms/friended_at_ms 都映射到这里。
RelationUpdatedAtMS int64
}
// RoomRelatedFeedQuery 根据一批关系用户查询他们当前 active 房间卡片。
type RoomRelatedFeedQuery struct {
// AppCode 是 feed 查询所属 App。
AppCode string
// VisibleRegionID 沿用房间发现页区域桶,避免关系流跨区域露出。
VisibleRegionID int64
// RelatedUsers 是 user-service 已授权返回的好友/关注对象。
RelatedUsers []RoomFeedRelatedUser
// Query 非空时按 room_id、room_short_id 或 title 做包含匹配。
Query string
// Limit 是实际查询数量service 层会限制最大值。
Limit int
// CursorUpdatedAtMS 是上一页最后一条关系排序时间。
CursorUpdatedAtMS int64
// CursorSubjectUserID 是关系排序时间相同时的好友/关注用户边界。
CursorSubjectUserID int64
// CursorRoomID 是同时间下的稳定翻页边界。
CursorRoomID string
}
// Repository 聚合 room-service 在首版需要的全部持久化读写。 // Repository 聚合 room-service 在首版需要的全部持久化读写。
type Repository interface { type Repository interface {
// SaveRoomMeta 保存房间基础元数据,创建房间时必须先建立它。 // SaveRoomMeta 保存房间基础元数据,创建房间时必须先建立它。
@ -239,6 +270,8 @@ type Repository interface {
UpsertRoomUserFeedEntry(ctx context.Context, entry RoomUserFeedEntry) error UpsertRoomUserFeedEntry(ctx context.Context, entry RoomUserFeedEntry) error
// ListRoomUserFeedEntries 按用户关系流读取房间卡片,不访问 Room Cell 内存集合。 // ListRoomUserFeedEntries 按用户关系流读取房间卡片,不访问 Room Cell 内存集合。
ListRoomUserFeedEntries(ctx context.Context, query RoomUserFeedQuery) ([]RoomListEntry, error) ListRoomUserFeedEntries(ctx context.Context, query RoomUserFeedQuery) ([]RoomListEntry, error)
// ListRoomRelatedFeedEntries 根据 user-service 关系事实查询 active 房间卡片,不持久化关系状态。
ListRoomRelatedFeedEntries(ctx context.Context, query RoomRelatedFeedQuery) ([]RoomListEntry, error)
// ProjectRoomPresence 用最新快照刷新用户当前房间读模型;失败不应破坏 Room Cell 已提交状态。 // ProjectRoomPresence 用最新快照刷新用户当前房间读模型;失败不应破坏 Room Cell 已提交状态。
ProjectRoomPresence(ctx context.Context, snapshot RoomPresenceSnapshot) error ProjectRoomPresence(ctx context.Context, snapshot RoomPresenceSnapshot) error
// GetCurrentRoomPresence 查询用户当前 active 房间 presence不扫描房间列表或快照。 // GetCurrentRoomPresence 查询用户当前 active 房间 presence不扫描房间列表或快照。

View File

@ -414,6 +414,89 @@ func TestListRoomFeedsVisitedUsesJoinRoomFeed(t *testing.T) {
} }
} }
func TestListRoomFeedsFriendFollowingUsesGatewayRelations(t *testing.T) {
ctx := context.Background()
repository := mysqltest.NewRepository(t)
svc := newRoomService("node-a", 1, router.NewMemoryDirectory(), repository, &fakeSyncPublisher{}, &fakeOutboxPublisher{})
for _, room := range []struct {
roomID string
ownerID int64
regionID int64
title string
}{
{roomID: "room-related-low", ownerID: 10002, regionID: 1001, title: "Low Relation"},
{roomID: "room-related-high", ownerID: 10003, regionID: 1001, title: "High Relation"},
{roomID: "room-related-other-region", ownerID: 10004, regionID: 2002, title: "Other Region"},
} {
if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{
Meta: roomservice.NewRequestMeta(room.roomID, room.ownerID),
SeatCount: 4,
Mode: "voice",
RoomName: room.title,
VisibleRegionId: room.regionID,
}); err != nil {
t.Fatalf("CreateRoom %s failed: %v", room.roomID, err)
}
}
resp, err := svc.ListRoomFeeds(ctx, &roomv1.ListRoomFeedsRequest{
Meta: &roomv1.RequestMeta{AppCode: appcode.Default},
ViewerUserId: 42,
VisibleRegionId: 1001,
Tab: "following",
Limit: 1,
RelatedUsers: []*roomv1.RoomFeedRelatedUser{
{UserId: 10002, RelationUpdatedAtMs: 8000},
{UserId: 10003, RelationUpdatedAtMs: 9000},
{UserId: 10004, RelationUpdatedAtMs: 10000},
},
})
if err != nil {
t.Fatalf("ListRoomFeeds following failed: %v", err)
}
if len(resp.GetRooms()) != 1 || resp.GetRooms()[0].GetRoomId() != "room-related-high" {
t.Fatalf("following feed should sort by user-service relation time and filter region: %+v", resp.GetRooms())
}
if resp.GetNextCursor() == "" {
t.Fatal("following feed should return cursor when more related rooms exist")
}
nextResp, err := svc.ListRoomFeeds(ctx, &roomv1.ListRoomFeedsRequest{
Meta: &roomv1.RequestMeta{AppCode: appcode.Default},
ViewerUserId: 42,
VisibleRegionId: 1001,
Tab: "following",
Limit: 20,
Cursor: resp.GetNextCursor(),
RelatedUsers: []*roomv1.RoomFeedRelatedUser{
{UserId: 10002, RelationUpdatedAtMs: 8000},
{UserId: 10003, RelationUpdatedAtMs: 9000},
{UserId: 10004, RelationUpdatedAtMs: 10000},
},
})
if err != nil {
t.Fatalf("ListRoomFeeds following next page failed: %v", err)
}
if len(nextResp.GetRooms()) != 1 || nextResp.GetRooms()[0].GetRoomId() != "room-related-low" {
t.Fatalf("following cursor should continue after previous relation subject: %+v", nextResp.GetRooms())
}
emptyResp, err := svc.ListRoomFeeds(ctx, &roomv1.ListRoomFeedsRequest{
Meta: &roomv1.RequestMeta{AppCode: appcode.Default},
ViewerUserId: 42,
VisibleRegionId: 1001,
Tab: "friend",
Limit: 20,
})
if err != nil {
t.Fatalf("ListRoomFeeds friend with empty related users failed: %v", err)
}
if len(emptyResp.GetRooms()) != 0 {
t.Fatalf("friend feed without user-service relations must be empty: %+v", emptyResp.GetRooms())
}
}
func TestListRoomsRejectsUserFeedTabs(t *testing.T) { func TestListRoomsRejectsUserFeedTabs(t *testing.T) {
ctx := context.Background() ctx := context.Background()
repository := mysqltest.NewRepository(t) repository := mysqltest.NewRepository(t)

View File

@ -5,6 +5,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"sort"
"strings" "strings"
"time" "time"
@ -890,6 +891,74 @@ func (r *Repository) ListRoomUserFeedEntries(ctx context.Context, query roomserv
return entries, nil return entries, nil
} }
// ListRoomRelatedFeedEntries 根据 user-service 给出的好友/关注用户查询 active 房间卡片。
func (r *Repository) ListRoomRelatedFeedEntries(ctx context.Context, query roomservice.RoomRelatedFeedQuery) ([]roomservice.RoomListEntry, error) {
if query.Limit <= 0 {
query.Limit = 20
}
query.AppCode = normalizedRecordAppCode(ctx, query.AppCode)
relations := normalizeRoomFeedRelations(query.RelatedUsers)
if len(relations) == 0 {
return []roomservice.RoomListEntry{}, nil
}
sqlText, args := buildRoomRelatedFeedQuerySQL(query, relations)
rows, err := r.db.QueryContext(ctx, sqlText, args...)
if err != nil {
return nil, err
}
defer rows.Close()
entries := make([]roomservice.RoomListEntry, 0, query.Limit)
for rows.Next() {
var entry roomservice.RoomListEntry
if err := rows.Scan(
&entry.AppCode,
&entry.RoomID,
&entry.RoomShortID,
&entry.VisibleRegionID,
&entry.OwnerUserID,
&entry.HostUserID,
&entry.Title,
&entry.CoverURL,
&entry.Mode,
&entry.Status,
&entry.Heat,
&entry.OnlineCount,
&entry.SeatCount,
&entry.OccupiedSeatCount,
&entry.SortScore,
&entry.CreatedAtMS,
&entry.UpdatedAtMS,
); err != nil {
return nil, err
}
entry.FeedSubjectUserID, entry.UpdatedAtMS = relatedFeedSortKey(entry, relations)
if entry.FeedSubjectUserID == 0 || !roomRelatedFeedAfterCursor(entry, query.CursorUpdatedAtMS, query.CursorSubjectUserID, query.CursorRoomID) {
continue
}
entries = append(entries, entry)
}
if err := rows.Err(); err != nil {
return nil, err
}
sort.SliceStable(entries, func(i, j int) bool {
if entries[i].UpdatedAtMS != entries[j].UpdatedAtMS {
return entries[i].UpdatedAtMS > entries[j].UpdatedAtMS
}
if entries[i].FeedSubjectUserID != entries[j].FeedSubjectUserID {
return entries[i].FeedSubjectUserID > entries[j].FeedSubjectUserID
}
return entries[i].RoomID < entries[j].RoomID
})
if len(entries) > query.Limit {
entries = entries[:query.Limit]
}
return entries, nil
}
const roomListSelectColumns = ` const roomListSelectColumns = `
SELECT app_code, room_id, room_short_id, visible_region_id, owner_user_id, host_user_id, title, cover_url, mode, status, SELECT app_code, room_id, room_short_id, visible_region_id, owner_user_id, host_user_id, title, cover_url, mode, status,
heat, online_count, seat_count, occupied_seat_count, sort_score, created_at_ms, updated_at_ms heat, online_count, seat_count, occupied_seat_count, sort_score, created_at_ms, updated_at_ms
@ -946,6 +1015,73 @@ func buildRoomUserFeedQuerySQL(query roomservice.RoomUserFeedQuery) (string, []a
return roomUserFeedSelectColumns + "\n\tWHERE " + strings.Join(where, " AND ") + "\n\tORDER BY f.updated_at_ms DESC, f.room_id ASC\n\tLIMIT ?", args return roomUserFeedSelectColumns + "\n\tWHERE " + strings.Join(where, " AND ") + "\n\tORDER BY f.updated_at_ms DESC, f.room_id ASC\n\tLIMIT ?", args
} }
func buildRoomRelatedFeedQuerySQL(query roomservice.RoomRelatedFeedQuery, relations map[int64]int64) (string, []any) {
where := []string{"app_code = ?", "visible_region_id = ?", "status = ?"}
args := []any{appcode.Normalize(query.AppCode), query.VisibleRegionID, "active"}
placeholders := make([]string, 0, len(relations))
relatedIDs := make([]any, 0, len(relations))
for userID := range relations {
placeholders = append(placeholders, "?")
relatedIDs = append(relatedIDs, userID)
}
inClause := strings.Join(placeholders, ",")
where = append(where, "(owner_user_id IN ("+inClause+") OR host_user_id IN ("+inClause+"))")
args = append(args, relatedIDs...)
args = append(args, relatedIDs...)
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)
}
return roomListSelectColumns + "\n\tWHERE " + strings.Join(where, " AND "), args
}
func normalizeRoomFeedRelations(items []roomservice.RoomFeedRelatedUser) map[int64]int64 {
relations := make(map[int64]int64, len(items))
for _, item := range items {
if item.UserID <= 0 || item.RelationUpdatedAtMS <= 0 {
continue
}
if item.RelationUpdatedAtMS > relations[item.UserID] {
relations[item.UserID] = item.RelationUpdatedAtMS
}
}
return relations
}
func relatedFeedSortKey(entry roomservice.RoomListEntry, relations map[int64]int64) (int64, int64) {
bestUserID, bestUpdatedAtMS := int64(0), int64(0)
choose := func(userID int64) {
updatedAtMS := relations[userID]
if updatedAtMS == 0 {
return
}
if updatedAtMS > bestUpdatedAtMS || (updatedAtMS == bestUpdatedAtMS && userID > bestUserID) {
bestUserID = userID
bestUpdatedAtMS = updatedAtMS
}
}
choose(entry.OwnerUserID)
choose(entry.HostUserID)
return bestUserID, bestUpdatedAtMS
}
func roomRelatedFeedAfterCursor(entry roomservice.RoomListEntry, cursorUpdatedAtMS int64, cursorSubjectUserID int64, cursorRoomID string) bool {
if cursorUpdatedAtMS <= 0 || cursorSubjectUserID <= 0 || cursorRoomID == "" {
return true
}
if entry.UpdatedAtMS != cursorUpdatedAtMS {
return entry.UpdatedAtMS < cursorUpdatedAtMS
}
if entry.FeedSubjectUserID != cursorSubjectUserID {
return entry.FeedSubjectUserID < cursorSubjectUserID
}
return entry.RoomID > cursorRoomID
}
// ProjectRoomPresence 用最新 RoomSnapshot 投影用户当前房间读模型。 // ProjectRoomPresence 用最新 RoomSnapshot 投影用户当前房间读模型。
func (r *Repository) ProjectRoomPresence(ctx context.Context, snapshot roomservice.RoomPresenceSnapshot) error { func (r *Repository) ProjectRoomPresence(ctx context.Context, snapshot roomservice.RoomPresenceSnapshot) error {
appCode := normalizedRecordAppCode(ctx, snapshot.AppCode) appCode := normalizedRecordAppCode(ctx, snapshot.AppCode)

View File

@ -24,16 +24,16 @@ type UserRepository interface {
FollowUser(ctx context.Context, followerUserID int64, followeeUserID int64, nowMs int64) (userdomain.ProfileStats, error) FollowUser(ctx context.Context, followerUserID int64, followeeUserID int64, nowMs int64) (userdomain.ProfileStats, error)
// UnfollowUser 取消单向关注,并更新关注计数。 // UnfollowUser 取消单向关注,并更新关注计数。
UnfollowUser(ctx context.Context, followerUserID int64, followeeUserID int64, nowMs int64) (userdomain.ProfileStats, error) UnfollowUser(ctx context.Context, followerUserID int64, followeeUserID int64, nowMs int64) (userdomain.ProfileStats, error)
// ListFollowing 分页读取当前用户关注的人。 // ListFollowing 读取当前用户关注的人;公开列表使用 page/pageSize房间关系流可用 cursor 做稳定续扫
ListFollowing(ctx context.Context, userID int64, page int32, pageSize int32) ([]userdomain.FollowRecord, int64, error) ListFollowing(ctx context.Context, userID int64, page int32, pageSize int32, cursorUpdatedAtMS int64, cursorUserID int64) ([]userdomain.FollowRecord, int64, error)
// ApplyFriend 创建好友申请;已有好友时不重复建申请。 // ApplyFriend 创建好友申请;已有好友时不重复建申请。
ApplyFriend(ctx context.Context, requesterUserID int64, targetUserID int64, nowMs int64) (userdomain.FriendApplication, bool, error) ApplyFriend(ctx context.Context, requesterUserID int64, targetUserID int64, nowMs int64) (userdomain.FriendApplication, bool, error)
// AcceptFriendApplication 同意指定 requester 发来的好友申请,并写双向好友关系。 // AcceptFriendApplication 同意指定 requester 发来的好友申请,并写双向好友关系。
AcceptFriendApplication(ctx context.Context, accepterUserID int64, requesterUserID int64, nowMs int64) (userdomain.FriendRecord, error) AcceptFriendApplication(ctx context.Context, accepterUserID int64, requesterUserID int64, nowMs int64) (userdomain.FriendRecord, error)
// DeleteFriend 删除双向好友关系。 // DeleteFriend 删除双向好友关系。
DeleteFriend(ctx context.Context, userID int64, friendUserID int64, nowMs int64) (bool, error) DeleteFriend(ctx context.Context, userID int64, friendUserID int64, nowMs int64) (bool, error)
// ListFriends 分页读取好友列表。 // ListFriends 读取好友列表;公开列表使用 page/pageSize房间关系流可用 cursor 做稳定续扫
ListFriends(ctx context.Context, userID int64, page int32, pageSize int32) ([]userdomain.FriendRecord, int64, error) ListFriends(ctx context.Context, userID int64, page int32, pageSize int32, cursorUpdatedAtMS int64, cursorUserID int64) ([]userdomain.FriendRecord, int64, error)
// ListFriendApplications 分页读取好友申请。 // ListFriendApplications 分页读取好友申请。
ListFriendApplications(ctx context.Context, userID int64, direction string, page int32, pageSize int32) ([]userdomain.FriendApplication, int64, error) ListFriendApplications(ctx context.Context, userID int64, direction string, page int32, pageSize int32) ([]userdomain.FriendApplication, int64, error)
// BatchGetUsers 批量查询用户主状态,缺失用户不应返回占位对象。 // BatchGetUsers 批量查询用户主状态,缺失用户不应返回占位对象。

View File

@ -194,6 +194,7 @@ func TestSocialRelationsUseReadModelsAndIdempotentEdges(t *testing.T) {
t.Fatalf("visitors mismatch: visitors=%+v total=%d err=%v", visitors, total, err) t.Fatalf("visitors mismatch: visitors=%+v total=%d err=%v", visitors, total, err)
} }
now = time.UnixMilli(8000)
stats, err = svc.FollowUser(context.Background(), 10001, 10002) stats, err = svc.FollowUser(context.Background(), 10001, 10002)
if err != nil || stats.FollowingCount != 1 { if err != nil || stats.FollowingCount != 1 {
t.Fatalf("follow failed: stats=%+v err=%v", stats, err) t.Fatalf("follow failed: stats=%+v err=%v", stats, err)
@ -202,24 +203,33 @@ func TestSocialRelationsUseReadModelsAndIdempotentEdges(t *testing.T) {
if err != nil || stats.FollowingCount != 1 { if err != nil || stats.FollowingCount != 1 {
t.Fatalf("repeat follow should be idempotent: stats=%+v err=%v", stats, err) t.Fatalf("repeat follow should be idempotent: stats=%+v err=%v", stats, err)
} }
following, total, err := svc.ListFollowing(context.Background(), 10001, 1, 20) now = time.UnixMilli(9000)
if err != nil || total != 1 || len(following) != 1 || following[0].FolloweeUserID != 10002 { stats, err = svc.FollowUser(context.Background(), 10001, 10003)
if err != nil || stats.FollowingCount != 2 {
t.Fatalf("second follow failed: stats=%+v err=%v", stats, err)
}
following, total, err := svc.ListFollowing(context.Background(), 10001, 1, 20, 0, 0)
if err != nil || total != 2 || len(following) != 2 || following[0].FolloweeUserID != 10003 || following[1].FolloweeUserID != 10002 {
t.Fatalf("following mismatch: following=%+v total=%d err=%v", following, total, err) t.Fatalf("following mismatch: following=%+v total=%d err=%v", following, total, err)
} }
following, total, err = svc.ListFollowing(context.Background(), 10001, 1, 20, 9000, 10003)
if err != nil || total != 2 || len(following) != 1 || following[0].FolloweeUserID != 10002 {
t.Fatalf("following cursor mismatch: following=%+v total=%d err=%v", following, total, err)
}
stats, err = svc.UnfollowUser(context.Background(), 10001, 10002) stats, err = svc.UnfollowUser(context.Background(), 10001, 10002)
if err != nil || stats.FollowingCount != 0 { if err != nil || stats.FollowingCount != 1 {
t.Fatalf("unfollow failed: stats=%+v err=%v", stats, err) t.Fatalf("unfollow failed: stats=%+v err=%v", stats, err)
} }
stats, err = svc.UnfollowUser(context.Background(), 10001, 10002) stats, err = svc.UnfollowUser(context.Background(), 10001, 10002)
if err != nil || stats.FollowingCount != 0 { if err != nil || stats.FollowingCount != 1 {
t.Fatalf("repeat unfollow should stay zero: stats=%+v err=%v", stats, err) t.Fatalf("repeat unfollow should stay unchanged: stats=%+v err=%v", stats, err)
} }
application, alreadyFriends, err := svc.ApplyFriend(context.Background(), 10001, 10002) application, alreadyFriends, err := svc.ApplyFriend(context.Background(), 10001, 10002)
if err != nil || alreadyFriends || application.Status != userdomain.FriendApplicationStatusPending { if err != nil || alreadyFriends || application.Status != userdomain.FriendApplicationStatusPending {
t.Fatalf("apply friend mismatch: app=%+v already=%v err=%v", application, alreadyFriends, err) t.Fatalf("apply friend mismatch: app=%+v already=%v err=%v", application, alreadyFriends, err)
} }
friends, total, err := svc.ListFriends(context.Background(), 10001, 1, 20) friends, total, err := svc.ListFriends(context.Background(), 10001, 1, 20, 0, 0)
if err != nil || total != 0 || len(friends) != 0 { if err != nil || total != 0 || len(friends) != 0 {
t.Fatalf("friend application must not create friendship before accept: friends=%+v total=%d err=%v", friends, total, err) t.Fatalf("friend application must not create friendship before accept: friends=%+v total=%d err=%v", friends, total, err)
} }
@ -236,7 +246,7 @@ func TestSocialRelationsUseReadModelsAndIdempotentEdges(t *testing.T) {
t.Fatalf("repeat accept should be idempotent: friend=%+v err=%v", friend, err) t.Fatalf("repeat accept should be idempotent: friend=%+v err=%v", friend, err)
} }
for _, userID := range []int64{10001, 10002} { for _, userID := range []int64{10001, 10002} {
friends, total, err = svc.ListFriends(context.Background(), userID, 1, 20) friends, total, err = svc.ListFriends(context.Background(), userID, 1, 20, 0, 0)
if err != nil || total != 1 || len(friends) != 1 { if err != nil || total != 1 || len(friends) != 1 {
t.Fatalf("bidirectional friendship missing for user %d: friends=%+v total=%d err=%v", userID, friends, total, err) t.Fatalf("bidirectional friendship missing for user %d: friends=%+v total=%d err=%v", userID, friends, total, err)
} }
@ -254,7 +264,7 @@ func TestSocialRelationsUseReadModelsAndIdempotentEdges(t *testing.T) {
t.Fatalf("repeat delete should be idempotent false: deleted=%v err=%v", deleted, err) t.Fatalf("repeat delete should be idempotent false: deleted=%v err=%v", deleted, err)
} }
for _, userID := range []int64{10001, 10002} { for _, userID := range []int64{10001, 10002} {
friends, total, err = svc.ListFriends(context.Background(), userID, 1, 20) friends, total, err = svc.ListFriends(context.Background(), userID, 1, 20, 0, 0)
if err != nil || total != 0 || len(friends) != 0 { if err != nil || total != 0 || len(friends) != 0 {
t.Fatalf("friend should be removed for user %d: friends=%+v total=%d err=%v", userID, friends, total, err) t.Fatalf("friend should be removed for user %d: friends=%+v total=%d err=%v", userID, friends, total, err)
} }

View File

@ -64,7 +64,7 @@ func (s *Service) UnfollowUser(ctx context.Context, followerUserID int64, follow
return s.userRepository.UnfollowUser(ctx, followerUserID, followeeUserID, s.now().UnixMilli()) return s.userRepository.UnfollowUser(ctx, followerUserID, followeeUserID, s.now().UnixMilli())
} }
func (s *Service) ListFollowing(ctx context.Context, userID int64, page int32, pageSize int32) ([]userdomain.FollowRecord, int64, error) { func (s *Service) ListFollowing(ctx context.Context, userID int64, page int32, pageSize int32, cursorUpdatedAtMS int64, cursorUserID int64) ([]userdomain.FollowRecord, int64, error) {
if userID <= 0 { if userID <= 0 {
return nil, 0, xerr.New(xerr.InvalidArgument, "user_id is required") return nil, 0, xerr.New(xerr.InvalidArgument, "user_id is required")
} }
@ -72,7 +72,7 @@ func (s *Service) ListFollowing(ctx context.Context, userID int64, page int32, p
return nil, 0, xerr.New(xerr.Unavailable, "user repository is not configured") return nil, 0, xerr.New(xerr.Unavailable, "user repository is not configured")
} }
page, pageSize = normalizeSocialPage(page, pageSize) page, pageSize = normalizeSocialPage(page, pageSize)
return s.userRepository.ListFollowing(ctx, userID, page, pageSize) return s.userRepository.ListFollowing(ctx, userID, page, pageSize, cursorUpdatedAtMS, cursorUserID)
} }
func (s *Service) ApplyFriend(ctx context.Context, requesterUserID int64, targetUserID int64) (userdomain.FriendApplication, bool, error) { func (s *Service) ApplyFriend(ctx context.Context, requesterUserID int64, targetUserID int64) (userdomain.FriendApplication, bool, error) {
@ -114,7 +114,7 @@ func (s *Service) DeleteFriend(ctx context.Context, userID int64, friendUserID i
return s.userRepository.DeleteFriend(ctx, userID, friendUserID, s.now().UnixMilli()) return s.userRepository.DeleteFriend(ctx, userID, friendUserID, s.now().UnixMilli())
} }
func (s *Service) ListFriends(ctx context.Context, userID int64, page int32, pageSize int32) ([]userdomain.FriendRecord, int64, error) { func (s *Service) ListFriends(ctx context.Context, userID int64, page int32, pageSize int32, cursorUpdatedAtMS int64, cursorUserID int64) ([]userdomain.FriendRecord, int64, error) {
if userID <= 0 { if userID <= 0 {
return nil, 0, xerr.New(xerr.InvalidArgument, "user_id is required") return nil, 0, xerr.New(xerr.InvalidArgument, "user_id is required")
} }
@ -122,7 +122,7 @@ func (s *Service) ListFriends(ctx context.Context, userID int64, page int32, pag
return nil, 0, xerr.New(xerr.Unavailable, "user repository is not configured") return nil, 0, xerr.New(xerr.Unavailable, "user repository is not configured")
} }
page, pageSize = normalizeSocialPage(page, pageSize) page, pageSize = normalizeSocialPage(page, pageSize)
return s.userRepository.ListFriends(ctx, userID, page, pageSize) return s.userRepository.ListFriends(ctx, userID, page, pageSize, cursorUpdatedAtMS, cursorUserID)
} }
func (s *Service) ListFriendApplications(ctx context.Context, userID int64, direction string, page int32, pageSize int32) ([]userdomain.FriendApplication, int64, error) { func (s *Service) ListFriendApplications(ctx context.Context, userID int64, direction string, page int32, pageSize int32) ([]userdomain.FriendApplication, int64, error) {

View File

@ -3,6 +3,7 @@ package user
import ( import (
"context" "context"
"database/sql" "database/sql"
"strings"
"hyapp/pkg/appcode" "hyapp/pkg/appcode"
"hyapp/pkg/xerr" "hyapp/pkg/xerr"
@ -191,7 +192,7 @@ func (r *Repository) UnfollowUser(ctx context.Context, followerUserID int64, fol
return stats, tx.Commit() return stats, tx.Commit()
} }
func (r *Repository) ListFollowing(ctx context.Context, userID int64, page int32, pageSize int32) ([]userdomain.FollowRecord, int64, error) { func (r *Repository) ListFollowing(ctx context.Context, userID int64, page int32, pageSize int32, cursorUpdatedAtMS int64, cursorUserID int64) ([]userdomain.FollowRecord, int64, error) {
appCode := appcode.FromContext(ctx) appCode := appcode.FromContext(ctx)
total, err := countRows(ctx, r.db, ` total, err := countRows(ctx, r.db, `
SELECT COUNT(*) FROM user_follows SELECT COUNT(*) FROM user_follows
@ -201,13 +202,21 @@ func (r *Repository) ListFollowing(ctx context.Context, userID int64, page int32
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
where := []string{"app_code = ?", "follower_user_id = ?", "status = ?"}
args := []any{appCode, userID, userdomain.SocialStatusActive}
if cursorUpdatedAtMS > 0 && cursorUserID > 0 {
// 关系流游标沿用列表排序时间倒序user_id 倒序;避免 OFFSET 在关系变更时漂移。
where = append(where, "(followed_at_ms < ? OR (followed_at_ms = ? AND followee_user_id < ?))")
args = append(args, cursorUpdatedAtMS, cursorUpdatedAtMS, cursorUserID)
}
args = append(args, pageSize, socialOffset(page, pageSize))
rows, err := r.db.QueryContext(ctx, ` rows, err := r.db.QueryContext(ctx, `
SELECT follower_user_id, followee_user_id, followed_at_ms SELECT follower_user_id, followee_user_id, followed_at_ms
FROM user_follows FROM user_follows
WHERE app_code = ? AND follower_user_id = ? AND status = ? WHERE `+strings.Join(where, " AND ")+`
ORDER BY followed_at_ms DESC, followee_user_id DESC ORDER BY followed_at_ms DESC, followee_user_id DESC
LIMIT ? OFFSET ?`, LIMIT ? OFFSET ?`,
appCode, userID, userdomain.SocialStatusActive, pageSize, socialOffset(page, pageSize), args...,
) )
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
@ -305,7 +314,7 @@ func (r *Repository) DeleteFriend(ctx context.Context, userID int64, friendUserI
return true, tx.Commit() return true, tx.Commit()
} }
func (r *Repository) ListFriends(ctx context.Context, userID int64, page int32, pageSize int32) ([]userdomain.FriendRecord, int64, error) { func (r *Repository) ListFriends(ctx context.Context, userID int64, page int32, pageSize int32, cursorUpdatedAtMS int64, cursorUserID int64) ([]userdomain.FriendRecord, int64, error) {
appCode := appcode.FromContext(ctx) appCode := appcode.FromContext(ctx)
total, err := countRows(ctx, r.db, ` total, err := countRows(ctx, r.db, `
SELECT COUNT(*) FROM user_friendships SELECT COUNT(*) FROM user_friendships
@ -315,13 +324,21 @@ func (r *Repository) ListFriends(ctx context.Context, userID int64, page int32,
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
where := []string{"app_code = ?", "user_id = ?", "status = ?"}
args := []any{appCode, userID, userdomain.SocialStatusActive}
if cursorUpdatedAtMS > 0 && cursorUserID > 0 {
// 好友表同样按建立时间倒序、friend_user_id 倒序续扫,避免 Mine 房间流翻页漏项。
where = append(where, "(friended_at_ms < ? OR (friended_at_ms = ? AND friend_user_id < ?))")
args = append(args, cursorUpdatedAtMS, cursorUpdatedAtMS, cursorUserID)
}
args = append(args, pageSize, socialOffset(page, pageSize))
rows, err := r.db.QueryContext(ctx, ` rows, err := r.db.QueryContext(ctx, `
SELECT user_id, friend_user_id, friended_at_ms SELECT user_id, friend_user_id, friended_at_ms
FROM user_friendships FROM user_friendships
WHERE app_code = ? AND user_id = ? AND status = ? WHERE `+strings.Join(where, " AND ")+`
ORDER BY friended_at_ms DESC, friend_user_id DESC ORDER BY friended_at_ms DESC, friend_user_id DESC
LIMIT ? OFFSET ?`, LIMIT ? OFFSET ?`,
appCode, userID, userdomain.SocialStatusActive, pageSize, socialOffset(page, pageSize), args...,
) )
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err

View File

@ -87,8 +87,8 @@ func (r *Repository) UnfollowUser(ctx context.Context, followerUserID int64, fol
return r.Repository.UserRepository().UnfollowUser(ctx, followerUserID, followeeUserID, nowMs) return r.Repository.UserRepository().UnfollowUser(ctx, followerUserID, followeeUserID, nowMs)
} }
func (r *Repository) ListFollowing(ctx context.Context, userID int64, page int32, pageSize int32) ([]userdomain.FollowRecord, int64, error) { func (r *Repository) ListFollowing(ctx context.Context, userID int64, page int32, pageSize int32, cursorUpdatedAtMS int64, cursorUserID int64) ([]userdomain.FollowRecord, int64, error) {
return r.Repository.UserRepository().ListFollowing(ctx, userID, page, pageSize) return r.Repository.UserRepository().ListFollowing(ctx, userID, page, pageSize, cursorUpdatedAtMS, cursorUserID)
} }
func (r *Repository) ApplyFriend(ctx context.Context, requesterUserID int64, targetUserID int64, nowMs int64) (userdomain.FriendApplication, bool, error) { func (r *Repository) ApplyFriend(ctx context.Context, requesterUserID int64, targetUserID int64, nowMs int64) (userdomain.FriendApplication, bool, error) {
@ -103,8 +103,8 @@ func (r *Repository) DeleteFriend(ctx context.Context, userID int64, friendUserI
return r.Repository.UserRepository().DeleteFriend(ctx, userID, friendUserID, nowMs) return r.Repository.UserRepository().DeleteFriend(ctx, userID, friendUserID, nowMs)
} }
func (r *Repository) ListFriends(ctx context.Context, userID int64, page int32, pageSize int32) ([]userdomain.FriendRecord, int64, error) { func (r *Repository) ListFriends(ctx context.Context, userID int64, page int32, pageSize int32, cursorUpdatedAtMS int64, cursorUserID int64) ([]userdomain.FriendRecord, int64, error) {
return r.Repository.UserRepository().ListFriends(ctx, userID, page, pageSize) return r.Repository.UserRepository().ListFriends(ctx, userID, page, pageSize, cursorUpdatedAtMS, cursorUserID)
} }
func (r *Repository) ListFriendApplications(ctx context.Context, userID int64, direction string, page int32, pageSize int32) ([]userdomain.FriendApplication, int64, error) { func (r *Repository) ListFriendApplications(ctx context.Context, userID int64, direction string, page int32, pageSize int32) ([]userdomain.FriendApplication, int64, error) {

View File

@ -235,7 +235,7 @@ func (s *Server) UnfollowUser(ctx context.Context, req *userv1.UnfollowUserReque
// ListFollowing 返回当前用户正在关注的人。 // ListFollowing 返回当前用户正在关注的人。
func (s *Server) ListFollowing(ctx context.Context, req *userv1.ListFollowingRequest) (*userv1.ListFollowingResponse, error) { func (s *Server) ListFollowing(ctx context.Context, req *userv1.ListFollowingRequest) (*userv1.ListFollowingResponse, error) {
ctx = contextWithApp(ctx, req.GetMeta()) ctx = contextWithApp(ctx, req.GetMeta())
records, total, err := s.userSvc.ListFollowing(ctx, req.GetUserId(), req.GetPage(), req.GetPageSize()) records, total, err := s.userSvc.ListFollowing(ctx, req.GetUserId(), req.GetPage(), req.GetPageSize(), req.GetCursorUpdatedAtMs(), req.GetCursorUserId())
if err != nil { if err != nil {
return nil, xerr.ToGRPCError(err) return nil, xerr.ToGRPCError(err)
} }
@ -279,7 +279,7 @@ func (s *Server) DeleteFriend(ctx context.Context, req *userv1.DeleteFriendReque
// ListFriends 返回好友列表。 // ListFriends 返回好友列表。
func (s *Server) ListFriends(ctx context.Context, req *userv1.ListFriendsRequest) (*userv1.ListFriendsResponse, error) { func (s *Server) ListFriends(ctx context.Context, req *userv1.ListFriendsRequest) (*userv1.ListFriendsResponse, error) {
ctx = contextWithApp(ctx, req.GetMeta()) ctx = contextWithApp(ctx, req.GetMeta())
records, total, err := s.userSvc.ListFriends(ctx, req.GetUserId(), req.GetPage(), req.GetPageSize()) records, total, err := s.userSvc.ListFriends(ctx, req.GetUserId(), req.GetPage(), req.GetPageSize(), req.GetCursorUpdatedAtMs(), req.GetCursorUserId())
if err != nil { if err != nil {
return nil, xerr.ToGRPCError(err) return nil, xerr.ToGRPCError(err)
} }